snoutdata 0.19.1 → 0.21.0

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 gf=Object.defineProperty;var D=(e,t)=>()=>(e&&(t=e(e=0)),t);var ci=(e,t)=>{for(var n in t)gf(e,n,{get:t[n],enumerable:!0})};function di(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]=wf(o);if(hf.has(s)){let c=a??e[++r];if(c===void 0)throw new g(`--${s} needs a value`);n[s]=c;continue}if(a!==void 0)throw new g(`--${s} does not take a value`);if(!yf.has(s))throw new g(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function wf(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function E(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function M(e,t){return e.flags[t]===!0}function Ie(e,t){let n=E(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,hf,yf,xe=D(()=>{"use strict";g=class extends Error{},hf=new Set(["name","sort","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","schemas","min-bytes","interval-ms","instances","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms","source","model","base-url","price-in","price-out","access-key-id","auth-header","models","amount","period","by","prompt"]);yf=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","soft"])});import{chmodSync as bf,existsSync as mi,mkdirSync as fi,readFileSync as gi,rmSync as Sf,writeFileSync as hi}from"node:fs";import{homedir as Hn}from"node:os";import{dirname as Mo,join as yi,parse as _f,resolve as kf}from"node:path";function jo(e=Hn()){return yi(e,".snoutdata","auth.json")}function Kn(e=Hn()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=jo(e);if(!mi(n))return null;try{let r=JSON.parse(gi(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function qe(e,t=Hn()){let n=jo(t);fi(Mo(n),{recursive:!0}),hi(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),bf(n,384)}function wi(e=Hn()){Sf(jo(e),{force:!0})}function bi(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Si(e){return yi(e,".snoutdata","project.json")}function Mt(e){let t=kf(e),n=_f(t).root;for(;;){let r=Si(t);if(mi(r))try{let o=JSON.parse(gi(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=Mo(t)}}function jt(e,t){let n=Si(e);return fi(Mo(n),{recursive:!0}),hi(n,`${JSON.stringify(t,null,2)}
4
- `),n}function Gn(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Mt(e.cwd??process.cwd())?.ref??null}function _i(e){let t=e.environment??process.env,n=Mt(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 ct=D(()=>{"use strict"});import{existsSync as Lo,readFileSync as ki}from"node:fs";import{homedir as vf}from"node:os";import{isAbsolute as $f,join as dt,resolve as Lt}from"node:path";function Pf(e=process.env,t=process.platform,n=vf()){let r;return t==="win32"?r=e.APPDATA??dt(n,"AppData","Roaming"):t==="darwin"?r=dt(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??dt(n,".config"),["snoutdata","snoutdata-dev"].map(o=>dt(r,o,"localProjects.json"))}function qn(e=Pf()){let t=[];for(let n of e)if(Lo(n))try{let r=JSON.parse(ki(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 Wn(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 an(e){return Lo(dt(e,"compose.yaml"))&&Lo(dt(e,".env"))}function ut(e,t=null){let n=Lt(e);if(!an(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=Wn(ki(dt(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${dt(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,c=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:c,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function vi(e,t=process.cwd(),n=qn()){if(!e){if(an(t))return ut(t);let[i]=n;if(n.length===1&&i)return ut(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=$f(e)?e:Lt(t,e);if(an(r)){let i=n.find(u=>Lt(u.folder)===Lt(r));return ut(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 ut(a.folder,a.name);let c=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${c.length?`. The local projects are ${c.join(", ")}`:""}`)}function $i(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 Pi(e,t,n=qn()){if(t&&an(t))try{let r=n.find(s=>Lt(s.folder)===Lt(t)),o=ut(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&an(r.folder))return ut(r.folder,r.name);return null}var ln=D(()=>{"use strict"});function V(e,t,n){throw new T(e,t,n)}function rt(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||t==="provider_billing"?"quota":t==="timeout"?"timeout":t!=null&&xf.has(t)?"server":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function xi(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 zn(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(Ef.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 nt,T,xf,Ef,we=D(()=>{"use strict";nt={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},T=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return nt[this.code]}};xf=new Set(["provider_auth","upstream_error","transport","egress_refused"]);Ef=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function Jn(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 Af){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 Ei(e){Uo=e}function be(){return(Uo??Jn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Ft(){return(Uo??Jn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Af,Uo,pt=D(()=>{"use strict";Af=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];Uo=null});import{createHmac as Rf,randomBytes as Tf,timingSafeEqual as Of}from"node:crypto";import{existsSync as Nf,readFileSync as If}from"node:fs";import{homedir as Fo,hostname as Cf}from"node:os";import{join as Bt}from"node:path";function Df(e,t,n){if(t==="win32"){let r=n.APPDATA??Bt(Fo(),"AppData","Roaming");return Bt(r,e)}return t==="darwin"?Bt(Fo(),"Library","Application Support",e):Bt(n.XDG_CONFIG_HOME??Bt(Fo(),".config"),e)}function Ri(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>Bt(Df(n,e,t),"mcp.json"))}function Mf(e=process.platform,t=process.env){return Ri(e,t)[0]}function jf(e=Mf()){if(!Nf(e))return null;try{let t=JSON.parse(If(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Lf(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Ff(e,t,n){return Rf("sha256",e).update(`${Uf}
2
+ var wg=Object.defineProperty;var W=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ji=(e,t)=>{for(var n in t)wg(e,n,{get:t[n],enumerable:!0})};function Vi(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]=kg(o);if(bg.has(s)){let c=a??e[++r];if(c===void 0)throw new h(`--${s} needs a value`);n[s]=c;continue}if(a!==void 0)throw new h(`--${s} does not take a value`);if(!Sg.has(s))throw new h(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function kg(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function E(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function K(e,t){return e.flags[t]===!0}function Fe(e,t){let n=E(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new h(`--${t} must be a whole number, not "${n}"`);return r}var h,bg,Sg,Ae=W(()=>{"use strict";h=class extends Error{},bg=new Set(["name","sort","at","region","servers","max-size","size","ref","profile","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","distribution-key","keyspace","schemas","min-bytes","interval-ms","instances","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms","source","model","base-url","price-in","price-out","access-key-id","auth-header","models","amount","period","by","prompt"]);Sg=new Set(["json","all","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","soft"])});import{chmodSync as _g,existsSync as ur,mkdirSync as Zi,readdirSync as vg,readFileSync as Qi,rmSync as $g,writeFileSync as el}from"node:fs";import{homedir as Yt}from"node:os";import{dirname as us,join as pr,parse as Pg,resolve as xg}from"node:path";function xn(e){if(!tl.test(e))throw new ds(`"${e}" is not a profile name: use letters, digits, - and _ (up to 40), starting with a letter or digit`);return e}function nl(e){kt=xn(e)}function De(){return kt}function rl(e){let t=e.environment??process.env;if(e.flag!==void 0)return{name:xn(e.flag),from:"flag"};if(t.SNOUTDATA_PROFILE)return{name:xn(t.SNOUTDATA_PROFILE),from:"environment"};let n=Dt(e.cwd??process.cwd())?.profile;return n?{name:xn(n),from:"link"}:{name:Me,from:"default"}}function ps(e=Yt(),t=kt){return pr(e,".snoutdata",t===Me?"auth.json":`auth.${xn(t)}.json`)}function ms(e=Yt()){let t=pr(e,".snoutdata");if(!ur(t))return[];let n=vg(t).map(r=>/^auth\.(.+)\.json$/.exec(r)?.[1]).filter(r=>r!==void 0&&tl.test(r)&&r!==Me).sort();return[...ur(pr(t,"auth.json"))?[Me]:[],...n]}function fs(e=Yt(),t=kt){let n=ps(e,t);if(!ur(n))return null;try{let r=JSON.parse(Qi(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function mr(e=Yt(),t=kt){let n=process.env.SNOUTDATA_ACCESS_TOKEN;return n?{accessToken:n}:fs(e,t)}function nt(e,t=Yt(),n=kt){let r=ps(t,n);Zi(us(r),{recursive:!0}),el(r,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),_g(r,384)}function gs(e=Yt(),t=kt){$g(ps(e,t),{force:!0})}function ol(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function sl(e){return pr(e,".snoutdata","project.json")}function Dt(e){let t=xg(e),n=Pg(t).root;for(;;){let r=sl(t);if(ur(r))try{let o=JSON.parse(Qi(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return{ref:o.ref,directory:t,...typeof o.local=="string"&&o.local?{local:o.local}:{},...typeof o.profile=="string"&&o.profile?{profile:o.profile}:{}}}catch{}if(t===n)return null;t=us(t)}}function Xt(e,t,n=kt){let r=sl(e);Zi(us(r),{recursive:!0});let o={ref:t.ref,...t.name!==void 0?{name:t.name}:{},...t.local!==void 0?{local:t.local}:{},...n!==Me?{profile:n}:{}};return el(r,`${JSON.stringify(o,null,2)}
4
+ `),r}function fr(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Dt(e.cwd??process.cwd())?.ref??null}function al(e){let t=e.environment??process.env,n=Dt(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,tl,ds,kt,_t=W(()=>{"use strict";Me="default",tl=/^[A-Za-z0-9][A-Za-z0-9_-]{0,39}$/,ds=class extends Error{};kt=Me});import{existsSync as hs,readFileSync as il}from"node:fs";import{homedir as Eg}from"node:os";import{isAbsolute as Ag,join as vt,resolve as Zt}from"node:path";function Rg(e=process.env,t=process.platform,n=Eg()){let r;return t==="win32"?r=e.APPDATA??vt(n,"AppData","Roaming"):t==="darwin"?r=vt(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??vt(n,".config"),["snoutdata","snoutdata-dev"].map(o=>vt(r,o,"localProjects.json"))}function gr(e=Rg()){let t=[];for(let n of e)if(hs(n))try{let r=JSON.parse(il(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 hr(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 En(e){return hs(vt(e,"compose.yaml"))&&hs(vt(e,".env"))}function $t(e,t=null){let n=Zt(e);if(!En(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=hr(il(vt(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${vt(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,c=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:c,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function ll(e,t=process.cwd(),n=gr()){if(!e){if(En(t))return $t(t);let[i]=n;if(n.length===1&&i)return $t(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=Ag(e)?e:Zt(t,e);if(En(r)){let i=n.find(u=>Zt(u.folder)===Zt(r));return $t(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 $t(a.folder,a.name);let c=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${c.length?`. The local projects are ${c.join(", ")}`:""}`)}function cl(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 dl(e,t,n=gr()){if(t&&En(t))try{let r=n.find(s=>Zt(s.folder)===Zt(t)),o=$t(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&En(r.folder))return $t(r.folder,r.name);return null}var An=W(()=>{"use strict"});function te(e,t,n){throw new C(e,t,n)}function pt(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||t==="provider_billing"?"quota":t==="timeout"?"timeout":t!=null&&Tg.has(t)?"server":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function ul(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 yr(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(Og.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 rt,C,Tg,Og,be=W(()=>{"use strict";rt={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},C=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return rt[this.code]}};Tg=new Set(["provider_auth","upstream_error","transport","egress_refused"]);Og=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function wr(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 Ng){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 Qt(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 pl(e){ys=e}function Se(){return(ys??wr({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function en(){return(ys??wr({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Ng,ys,Pt=W(()=>{"use strict";Ng=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];ys=null});import{createHmac as Ig,randomBytes as Cg,timingSafeEqual as Mg}from"node:crypto";import{existsSync as Dg,readFileSync as Lg}from"node:fs";import{homedir as ws,hostname as jg}from"node:os";import{join as tn}from"node:path";function Ug(e,t,n){if(t==="win32"){let r=n.APPDATA??tn(ws(),"AppData","Roaming");return tn(r,e)}return t==="darwin"?tn(ws(),"Library","Application Support",e):tn(n.XDG_CONFIG_HOME??tn(ws(),".config"),e)}function fl(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>tn(Ug(n,e,t),"mcp.json"))}function Fg(e=process.platform,t=process.env){return fl(e,t)[0]}function Bg(e=Fg()){if(!Dg(e))return null;try{let t=JSON.parse(Lg(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Hg(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Gg(e,t,n){return Ig("sha256",e).update(`${Kg}
5
5
  ${t}
6
- ${n}`).digest("base64url")}function Bf(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&Of(n,r)}async function Oi(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=Tf(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(Ti)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&Bf(o.proof,Ff(e.token,t,n))}catch{return!1}}async function Ho(e,t,n,r=Oi){if(!await r(e).catch(()=>!1))throw new Vn(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function Yn(e,t=Oi){if(e!==void 0)return Ai(e,t);let n="no-config";for(let r of Ri()){let o=jf(r);if(!o)continue;let s=await Ai(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 Ai(e,t){let n=Lf(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Ho(r,"/cli/whoami",{signal:AbortSignal.timeout(Ti)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof Vn?{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 Ni(e,t){let n=await Ho(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 Xn(){return Cf()}async function Bo(e,t,n,r){let o=await Ho(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 Ii(e){return await Bo(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await Bo(e,"tools/list",{},3e3)).tools??[]}async function Ci(e,t,n){return Bo(e,"tools/call",{name:t,arguments:n},12e4)}var Ti,Uf,Vn,Zn=D(()=>{"use strict";Ti=1500,Uf="snoutdata-studio-proof-v1";Vn=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 Ko(e){try{return new URL(e).host}catch{return e}}function er(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function mt(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function Gf(e){return Qn.has(Ko(e))}function qf(e,t,n){return t===void 0?e:{...e,[n?Kf:Hf]:t}}async function We(e,t,n={},r=fetch){let o=Ko(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:qf(s,n.key,u),body:n.body})}if(Qn.has(o))return a(!0);let c=await a(!1);if(c.status!==404)return c;let i=await a(!0);return i.status===404?c:(Qn.add(o),i)}async function Mi(e,t,n,r=fetch){if(!Qn.has(Ko(e)))try{await We(e,mt("/health","/health"),{key:n},r)}catch{}return`${e}${Gf(e)?t.oldPath:t.path}`}var Hf,Kf,Qn,tr=D(()=>{"use strict";Hf="Snout-Key",Kf="apikey",Qn=new Set});import{basename as Wf}from"node:path";function rr(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Wf(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 zf(e=rr(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?ot:cn}}function Go(e={}){let t=e.kind??rr(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?ot:`${cn} (or ${ot})`;return`snoutdata ${Se} is installed and ${r}. Run \`${zf(t,n)}\`, or install again: ${o}`}function ji(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 Se,nr,cn,ot,dn=D(()=>{"use strict";Se="0.19.1",nr=`snoutdata-cli/${Se}`,cn="curl -fsSL https://snoutdata.com/install.sh | sh",ot="npm install -g snoutdata@latest"});async function Li(e,t){let n=await We(ze,er(e),{method:"POST",key:Je,headers:{"Content-Type":"application/json","x-client-info":nr},body:JSON.stringify(t)});ar(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw ir(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function or(e={}){let{status:t,body:n}=await Li("cloud-device-start",{client:e});return t===429&&V("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&V("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)&&V("server",n.error??`could not start a pairing (${t})`),n}async function sr(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&V("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 Li("cloud-device-poll",{secret:e});switch(o===404&&V("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&V("usage",s.error??"that is not a pairing secret"),o!==200&&V("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":V("forbidden",s.error??"that pairing was refused in the browser");case"used":V("conflict",s.error??"that pairing has already been completed");case"expired":V("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 qo=D(()=>{"use strict";pe();tr();we();dn()});function pn(e){un=e}function Fi(e){Ui=e}function lr(){return un}function b(e){return Bi?`\x1B[2m${e}\x1B[0m`:e}function O(e){return Bi?`\x1B[1m${e}\x1B[0m`:e}function w(e,t){if(un){process.stdout.write(`${JSON.stringify(e,null,2)}
7
- `);return}t()}function d(e){!un&&!Ui&&process.stderr.write(`${e}
6
+ ${n}`).digest("base64url")}function qg(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&Mg(n,r)}async function hl(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=Cg(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(gl)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&qg(o.proof,Gg(e.token,t,n))}catch{return!1}}async function Ss(e,t,n,r=hl){if(!await r(e).catch(()=>!1))throw new br(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function Sr(e,t=hl){if(e!==void 0)return ml(e,t);let n="no-config";for(let r of fl()){let o=Bg(r);if(!o)continue;let s=await ml(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 ml(e,t){let n=Hg(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Ss(r,"/cli/whoami",{signal:AbortSignal.timeout(gl)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof br?{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 yl(e,t){let n=await Ss(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 kr(){return jg()}async function bs(e,t,n,r){let o=await Ss(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 wl(e){return await bs(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await bs(e,"tools/list",{},3e3)).tools??[]}async function bl(e,t,n){return bs(e,"tools/call",{name:t,arguments:n},12e4)}var gl,Kg,br,_r=W(()=>{"use strict";gl=1500,Kg="snoutdata-studio-proof-v1";br=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 ks(e){try{return new URL(e).host}catch{return e}}function $r(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function kl(e){return{path:`/v1/data${e}`,oldPath:`/rest/v1${e}`}}function xt(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function Jg(e){return vr.has(ks(e))}function Vg(e,t,n){return t===void 0?e:{...e,[n?Wg:zg]:t}}async function Xe(e,t,n={},r=fetch){let o=ks(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:Vg(s,n.key,u),body:n.body})}if(vr.has(o))return a(!0);let c=await a(!1);if(c.status!==404)return c;let i=await a(!0);return i.status===404?c:(vr.add(o),i)}async function _l(e,t,n,r=fetch){if(!vr.has(ks(e)))try{await Xe(e,xt("/health","/health"),{key:n},r)}catch{}return`${e}${Jg(e)?t.oldPath:t.path}`}var zg,Wg,vr,Pr=W(()=>{"use strict";zg="Snout-Key",Wg="apikey",vr=new Set});import{basename as Yg}from"node:path";function Er(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Yg(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 Xg(e=Er(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?mt:Rn}}function _s(e={}){let t=e.kind??Er(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?mt:`${Rn} (or ${mt})`;return`snoutdata ${ke} is installed and ${r}. Run \`${Xg(t,n)}\`, or install again: ${o}`}function vl(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 ke,xr,Rn,mt,Tn=W(()=>{"use strict";ke="0.21.0",xr=`snoutdata-cli/${ke}`,Rn="curl -fsSL https://snoutdata.com/install.sh | sh",mt="npm install -g snoutdata@latest"});async function $l(e,t){let n=await Xe(Ze,$r(e),{method:"POST",key:Qe,headers:{"Content-Type":"application/json","x-client-info":xr},body:JSON.stringify(t)});Tr(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw Or(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function Ar(e={}){let{status:t,body:n}=await $l("cloud-device-start",{client:e});return t===429&&te("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&te("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)&&te("server",n.error??`could not start a pairing (${t})`),n}async function Rr(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&te("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 $l("cloud-device-poll",{secret:e});switch(o===404&&te("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&te("usage",s.error??"that is not a pairing secret"),o!==200&&te("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":te("forbidden",s.error??"that pairing was refused in the browser");case"used":te("conflict",s.error??"that pairing has already been completed");case"expired":te("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 vs=W(()=>{"use strict";pe();Pr();be();Tn()});function Nn(e){On=e}function xl(e){Pl=e}function Nr(){return On}function v(e){return El?`\x1B[2m${e}\x1B[0m`:e}function M(e){return El?`\x1B[1m${e}\x1B[0m`:e}function b(e,t){if(On){process.stdout.write(`${JSON.stringify(e,null,2)}
7
+ `);return}t()}function d(e){!On&&!Pl&&process.stderr.write(`${e}
8
8
  `)}function ft(e,t,n){process.stderr.write(`${t}
9
- `),un&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
- `)}function ie(e){process.stderr.write(`${e}
11
- `)}function W(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 st(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 c=`${s} ${a}${s===1?"":"s"}`;return n?`in ${c}`:`${c} ago`}var un,Ui,Bi,le=D(()=>{"use strict";un=!1,Ui=!1;Bi=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Gi,randomBytes as Wo}from"node:crypto";import{createServer as Vf}from"node:http";import{createInterface as Yf}from"node:readline/promises";import{spawn as Xf}from"node:child_process";function cr(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Hi(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.":fg(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
9
+ `),On&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
+ `)}function ce(e){process.stderr.write(`${e}
11
+ `)}function X(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 gt(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 c=`${s} ${a}${s===1?"":"s"}`;return n?`in ${c}`:`${c} ago`}var On,Pl,El,de=W(()=>{"use strict";On=!1,Pl=!1;El=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Tl,randomBytes as $s}from"node:crypto";import{createServer as Qg}from"node:http";import{createInterface as eh}from"node:readline/promises";import{spawn as th}from"node:child_process";function Ir(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Al(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.":kh(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,12 +21,12 @@ ${n}`).digest("base64url")}function Bf(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 zo(){let e=cr(Wo(32)),t=cr(Gi("sha256").update(e).digest());return{verifier:e,challenge:t}}function Zf(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 Qf(e){let{command:t,args:n,verbatim:r}=Zf(process.platform,e),o=Xf(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function Jo(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function ng(){return"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"}function rg(e){let t=e??cr(Wo(32));return{raw:t,hashed:Gi("sha256").update(t).digest("hex")}}function og(e){return`${eg}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:Jo(),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 sg(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 Vo(e){return new Promise((t,n)=>{let r="",o=Vf((a,c)=>{let i=sg(a.url??"/",e.state);if(i.kind==="ignore"){c.writeHead(404).end();return}c.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),c.end(i.kind==="ok"?Hi("ok"):Hi("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."),Qf(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 qi(e,t){let n=await We(ze,mt("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Je,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 Wi(await n.json())}async function ag(e,t,n){let r=await fetch(tg,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Jo(),client_secret:ng(),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 ig(e){if(!Jo())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}=zo(),{raw:r,hashed:o}=rg(),s=cr(Wo(16)),{code:a,redirectUri:c}=await Vo({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:l=>og({redirectUri:l,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await ag(a,t,c),u=await We(ze,mt("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Je,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 Wi(await u.json())}function Wi(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return qe(n),n}function Ki(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 lg(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 cg(e,t,n){let r=lg(t);return e===404||/no sso provider/i.test(r)?new T("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 T("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new T(rt(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function dg(e){let t=await We(ze,mt("/sso/start","/sso"),{method:"POST",key:Je,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 cg(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new T("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 ug(e){if(e!==void 0){let r=Ki(e);if(!r)throw new g(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!be()){let r=Ft();throw new g(`login --sso needs --domain <your work email or company domain>${r?`, because ${Ut(r)}`:""}`)}let t=Yf({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=Ki(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 pg(e){let t=await ug(e.domain),{verifier:n,challenge:r}=zo();d(`Signing in with the identity provider for ${O(t)}.`);let{code:o}=await Vo({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>dg({domain:t,redirectTo:s,challenge:r})});return qi(o,n)}async function dr(e={}){return Yo(e.email,await mg(e))}function Yo(e,t){if(!e)return t;let n={...t,expectedEmail:e};return qe(n),t.email&&!Xo(t.email,e)&&ie(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Xo(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function mg(e){if(e.sso)return pg({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return ig(e);let{verifier:n,challenge:r}=zo(),{code:o}=await Vo({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Mi(ze,mt("/provider/start","/authorize"),Je));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 qi(o,n)}function fg(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var eg,tg,ur=D(()=>{"use strict";pe();tr();xe();ct();we();pt();le();eg="https://accounts.google.com/o/oauth2/v2/auth",tg="https://oauth2.googleapis.com/token"});import{createInterface as gg}from"node:readline/promises";async function Zo(e){let t=gg({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 hg(){let e=await Yn();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(b("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(d(""),d(`SnoutData Studio is open here, signed in as ${O(t)}.`),d(b("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(b("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Zo("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await Ni(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 qe(r),d(`Signed in with "${n.name}".`),r}async function yg(){if(!await Zo("Sign in with a browser?"))return null;try{let e=await dr({});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 wg(){if(!await Zo("Sign in by typing a code into a browser on another machine?"))return null;let e=await or({name:Xn(),platform:process.platform});d(""),d(` Open ${O(e.verifyUrl)}`),d(` Type ${O(e.code)}`),d(""),d(b("The code is typed rather than in the link on purpose: it is what proves the person")),d(b("at the browser is the person at this terminal. Waiting\u2026"));let t=await sr(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return qe(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function zi(){if(!be())return null;let e=await hg();if(e)return e;let t=await yg();return t||wg()}var Ji=D(()=>{"use strict";ct();Zn();qo();pt();ur();le()});var Ke={};ci(Ke,{ACCOUNTS_URL:()=>ze,APP_KEY:()=>Je,ApiError:()=>$e,NotSignedIn:()=>gt,call:()=>P,connection:()=>Ce,errorSentence:()=>Zi,exportStatus:()=>Ht,isSettled:()=>Xi,listProjects:()=>je,listTeams:()=>mr,localStack:()=>at,noticeFrom:()=>ar,outdated:()=>ir,requestExport:()=>mn,serverCodeOf:()=>Yi,unreadableAnswer:()=>fr,waitForReady:()=>gn,waitForSettled:()=>fn,whoami:()=>Et,withKeyNames:()=>pr});function bg(){let e=Ft();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 Sg(){let e=Kn();if(e||(e=await zi()),!e)throw new gt;if(!bi(e)||!e.refreshToken)return e.accessToken;let t=await We(ze,mt("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Je,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new gt;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return qe(r),r.accessToken}async function P(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=at(r);if(u)throw new T("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 Sg(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await We(ze,er(e,s),{method:n,key:Je,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":nr},body:n==="GET"?void 0:JSON.stringify(t)});ar(a);let c=await a.text(),i;try{i=c?JSON.parse(c):{}}catch{throw new $e(a.status,fr(a.status,a.statusText,c))}if(!a.ok){let u=Zi(i)??`${a.status} ${a.statusText}`;throw a.status===401?new gt(u):a.status===410?ir(a,u):new $e(a.status,u,Yi(i),i)}return i}function Yi(e){if(typeof e!="object"||e===null)return null;let{code:t,error:n}=e;return typeof t=="string"?t:typeof n=="object"&&n!==null&&typeof n.code=="string"?n.code:null}function ar(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Vi||(Vi=!0,ie(`${t} ${Go({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function ir(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new T("outdated",`${t.replace(/\s+$/,"")} ${Go({minimum:n})}`,{status:410,installed:Se,minimum:n??null})}async function Et(){return P("cloud-whoami")}async function je(){return P("cloud-project-list")}async function Ce(e){let t=at(e);return t?$i(t):pr(await P("cloud-project-connection",{ref:e}))}function pr(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function at(e){return Pi(e,Mt(process.cwd())?.local??null)}async function mr(){return P("cloud-team-list")}async function mn(e){return P("cloud-project-export",{ref:e})}async function Ht(e){return P("cloud-project-export",{ref:e},"GET")}async function fn(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await je(),s=o.find(c=>c.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Xi(t,a))return s;if(Date.now()>=r)throw new $e(504,`${e} is still ${a} after waiting`);await new Promise(c=>setTimeout(c,2e3))}}function Xi(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function gn(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await je(),o=r.find(s=>s.ref===e);if(!o)throw new $e(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new $e(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}function Zi(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 fr(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 ze,Je,$e,gt,Vi,pe=D(()=>{"use strict";ct();ln();we();pt();Ji();le();dn();tr();ze=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Je=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",$e=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},gt=class extends Error{constructor(t){super(t??bg())}};Vi=!1});var Wd,Pb,zd=D(()=>{"use strict";Wd="snoutpod_isolation",Pb=`table bridge ${Wd}
26
- delete table bridge ${Wd}
27
- `});function Zs(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Vd(e,t,n){let r="";for(;r.length<n;)r+=Zs(e,t);return r}var Xs,Jd,Yd=D(()=>{"use strict";Xs="0123456789abcdefghjkmnpqrstvwxyz",Jd="abcdefghjkmnpqrstvwxyz"});function Qd(e){return Zs(e,Xd)+Vd(e,Xs,Zd-1)}function Eb(e){return xb.test(e)}function ae(e){if(!Eb(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function B(e,t=Ab){return ae(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Xd,Zd,xb,Ab,Te=D(()=>{"use strict";Yd();Xd=Jd,Zd=13,xb=new RegExp(`^[${Xd}][${Xs}]{${Zd-1}}$`);Ab="db.snoutdata.com"});var eu=D(()=>{"use strict";Te()});function nu(e,t){let n=tu(e,7),r=tu(t,24);return Math.max(2,Math.ceil(n*24/r))}function tu(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Qs=D(()=>{"use strict"});function ea(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var ta=D(()=>{"use strict"});var ru=D(()=>{"use strict"});function su(e){let n=(e.trim().split(`
28
- `)[0]??"").split("|");if(n.length!==7||n[0]!=="t"&&n[0]!=="f"||n[5]!=="t"&&n[5]!=="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(","),lastArchivedWal:/^[0-9A-F]{24}$/.test(n[4])?n[4]:"",keeperReady:n[5]==="t",certSha256:/^[0-9a-f]{64}$/.test(n[6])?n[6]:""}}function Nb(e){if(!Rb.test(e))throw new Error(`not a keeper name: ${e}`);return`keeper_${e.replace(/-/g,"_")}`}function au(e){let t=e.map(r=>`'${Nb(r)}'`).join(", "),n=`from pg_replication_slots where slot_name like 'keeper\\_%'${e.length===0?"":` and slot_name not in (${t})`}`;return[`select pg_drop_replication_slot(slot_name) ${n} and not active`,`select count(*) ${n} and active`]}function iu(e){if(e!==""&&!Ob.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()"]}function Cb(e){return typeof e=="string"&&Ib.test(e)}function Db(e){return e===qr||Cb(e)}function lu(e){if(!Db(e))throw new Error(`not an IPv4 address or ${qr}: ${JSON.stringify(e)}`);return`hostssl replication ${Yt} ${e===qr?qr:`${e}/32`} scram-sha-256`}function cu(e){if(!ra.test(e))throw new Error("not a SCRAM-SHA-256 verifier");return[`do $$ begin if not exists (select 1 from pg_roles where rolname = '${Yt}') then create role ${Yt} with login replication; end if; end $$`,`alter role ${Yt} with login replication noinherit password '${e}'`,`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${na}' and wal_status = 'lost' and not active`,`select pg_create_physical_replication_slot('${na}', true) where not exists (select 1 from pg_replication_slots where slot_name = '${na}')`]}var Rb,Tb,St,ra,ou,Wr,Ob,Yt,na,Ib,qr,zr=D(()=>{"use strict";Te();Rb=/^[a-z0-9][a-z0-9-]*$/,Tb=20,St="snoutpod_keeper",ra=/^SCRAM-SHA-256\$[0-9]{4,6}:[A-Za-z0-9+/]+=*\$[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/,ou=`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 '${Tb} seconds')), ''), coalesce((select last_archived_wal from pg_stat_archiver), ''), exists (select 1 from pg_roles where rolname = '${St}' and rolreplication) and exists (select 1 from pg_hba_file_rules where database = '{replication}' and '${St}' = any (user_name)) and current_setting('ssl') = 'on', case when current_setting('ssl') = 'on' then encode(sha256(pg_read_binary_file(current_setting('ssl_cert_file'))), 'hex') else '' end`;Wr=/^-----BEGIN CERTIFICATE-----\n[A-Za-z0-9+/=\n]{64,12000}-----END CERTIFICATE-----\n$/,Ob=/^(?:"[a-z0-9-]+"|(?:ANY|FIRST) [1-9] \("[a-z0-9-]+"(?:, "[a-z0-9-]+")+\))$/;Yt="snoutpod_replica",na="snoutpod_replica",Ib=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;qr="samehost"});function du(e,t){let n=null,r=!1;for(let o of e.split(`
29
- `)){let s=o.trim();s.startsWith("swap-error")?n=s.slice(10).trim()||"the swap failed":s==="swap-done"&&(r=!0)}return n||(r&&t===0?null:`the swap ended without finishing (exit ${t})`)}var Mb,oa,sa,aa=D(()=>{"use strict";Mb='snoutpod-pgbackrest archive-get %f "%p"',oa=String.raw`set -u
24
+ </body></html>`}function Ps(){let e=Ir($s(32)),t=Ir(Tl("sha256").update(e).digest());return{verifier:e,challenge:t}}function nh(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 rh(e){let{command:t,args:n,verbatim:r}=nh(process.platform,e),o=th(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function xs(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function ah(){return"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"}function ih(e){let t=e??Ir($s(32));return{raw:t,hashed:Tl("sha256").update(t).digest("hex")}}function lh(e){return`${oh}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:xs(),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 ch(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 Es(e){return new Promise((t,n)=>{let r="",o=Qg((a,c)=>{let i=ch(a.url??"/",e.state);if(i.kind==="ignore"){c.writeHead(404).end();return}c.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),c.end(i.kind==="ok"?Al("ok"):Al("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."),rh(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 Ol(e,t){let n=await Xe(Ze,xt("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Qe,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 Nl(await n.json())}async function dh(e,t,n){let r=await fetch(sh,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:xs(),client_secret:ah(),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 uh(e){if(!xs())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}=Ps(),{raw:r,hashed:o}=ih(),s=Ir($s(16)),{code:a,redirectUri:c}=await Es({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:l=>lh({redirectUri:l,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await dh(a,t,c),u=await Xe(Ze,xt("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Qe,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 Nl(await u.json())}function Nl(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return nt(n),n}function Rl(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 ph(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 mh(e,t,n){let r=ph(t);return e===404||/no sso provider/i.test(r)?new C("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 C("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new C(pt(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function fh(e){let t=await Xe(Ze,xt("/sso/start","/sso"),{method:"POST",key:Qe,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 mh(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new C("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 gh(e){if(e!==void 0){let r=Rl(e);if(!r)throw new h(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!Se()){let r=en();throw new h(`login --sso needs --domain <your work email or company domain>${r?`, because ${Qt(r)}`:""}`)}let t=eh({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new h("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=Rl(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 h("no domain to sign in with: pass --domain <your work email or company domain>")}async function hh(e){let t=await gh(e.domain),{verifier:n,challenge:r}=Ps();d(`Signing in with the identity provider for ${M(t)}.`);let{code:o}=await Es({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>fh({domain:t,redirectTo:s,challenge:r})});return Ol(o,n)}function yh(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}async function bh(e,t=yh(),n=fetch){if(!t||!e)return null;let r=Xe(Ze,kl("/rpc/set_my_timezone"),{method:"POST",key:Qe,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({want_timezone:t,want_source:"signin"})},n).then(async a=>a.ok?null:`HTTP ${a.status}: ${(await a.text().catch(()=>"")).slice(0,200)}`,a=>a instanceof Error?a.message:String(a)),o,s=new Promise(a=>{o=setTimeout(()=>a("timed out"),wh),o.unref()});try{return await Promise.race([r,s])}finally{clearTimeout(o)}}async function Cr(e={}){let t=As(e.email,await Sh(e)),n=await bh(t.accessToken);return n&&process.env.SNOUTDATA_DEBUG&&ce(`could not record the timezone: ${n}`),t}function As(e,t){if(!e)return t;let n={...t,expectedEmail:e};return nt(n),t.email&&!Rs(t.email,e)&&ce(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Rs(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Sh(e){if(e.sso)return hh({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return uh(e);let{verifier:n,challenge:r}=Ps(),{code:o}=await Es({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await _l(Ze,xt("/provider/start","/authorize"),Qe));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 Ol(o,n)}function kh(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var oh,sh,wh,Mr=W(()=>{"use strict";pe();Pr();Ae();_t();be();Pt();de();oh="https://accounts.google.com/o/oauth2/v2/auth",sh="https://oauth2.googleapis.com/token";wh=3e3});import{createInterface as _h}from"node:readline/promises";async function Ts(e){let t=_h({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 vh(){let e=await Sr();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(v("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";d("");let n=De();if(d(`SnoutData Studio is open here, signed in as ${M(t)}.`),n!==Me&&d(`Its token would be kept as profile ${M(n)}.`),d(v("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(v("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Ts("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let r=await yl(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in r)return d(`The app did not create a token: ${r.reason}`),null;let o={accessToken:r.token};return nt(o),d(`Signed in with "${r.name}".`),o}async function $h(){if(!await Ts("Sign in with a browser?"))return null;try{let e=await Cr({});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 Ph(){if(!await Ts("Sign in by typing a code into a browser on another machine?"))return null;let e=await Ar({name:kr(),platform:process.platform});d(""),d(` Open ${M(e.verifyUrl)}`),d(` Type ${M(e.code)}`),d(""),d(v("The code is typed rather than in the link on purpose: it is what proves the person")),d(v("at the browser is the person at this terminal. Waiting\u2026"));let t=await Rr(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return nt(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Il(){if(!Se())return null;let e=await vh();if(e)return e;let t=await $h();return t||Ph()}var Cl=W(()=>{"use strict";_t();_r();vs();Pt();Mr();de()});var et={};Ji(et,{ACCOUNTS_URL:()=>Ze,APP_KEY:()=>Qe,ApiError:()=>Re,NotSignedIn:()=>Et,call:()=>R,connection:()=>He,errorSentence:()=>jl,exportStatus:()=>nn,isSettled:()=>Ll,listProjects:()=>We,listTeams:()=>Lr,localStack:()=>ht,noticeFrom:()=>Tr,outdated:()=>Or,requestExport:()=>In,serverCodeOf:()=>Dl,unreadableAnswer:()=>jr,waitForReady:()=>Mn,waitForSettled:()=>Cn,whoami:()=>Lt,withKeyNames:()=>Dr});function xh(){let e=en(),t=De(),n=t===Me?"":` as profile ${t}`;return e?`not signed in${n}, and ${Qt(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${n}: run \`snoutdata login${t===Me?"":` --profile ${t}`}\`, or set SNOUTDATA_ACCESS_TOKEN`}async function Eh(){let e=mr();if(e||(e=await Il()),!e)throw new Et;if(!ol(e)||!e.refreshToken)return e.accessToken;let t=await Xe(Ze,xt("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Qe,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Et;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return nt(r),r.accessToken}async function R(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=ht(r);if(u)throw new C("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 Eh(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await Xe(Ze,$r(e,s),{method:n,key:Qe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":xr},body:n==="GET"?void 0:JSON.stringify(t)});Tr(a);let c=await a.text(),i;try{i=c?JSON.parse(c):{}}catch{throw new Re(a.status,jr(a.status,a.statusText,c))}if(!a.ok){let u=jl(i)??`${a.status} ${a.statusText}`;throw a.status===401?new Et(u):a.status===410?Or(a,u):new Re(a.status,u,Dl(i),i)}return i}function Dl(e){if(typeof e!="object"||e===null)return null;let{code:t,error:n}=e;return typeof t=="string"?t:typeof n=="object"&&n!==null&&typeof n.code=="string"?n.code:null}function Tr(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Ml||(Ml=!0,ce(`${t} ${_s({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function Or(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new C("outdated",`${t.replace(/\s+$/,"")} ${_s({minimum:n})}`,{status:410,installed:ke,minimum:n??null})}async function Lt(){return R("cloud-whoami")}async function We(){return R("cloud-project-list")}async function He(e){let t=ht(e);return t?cl(t):Dr(await R("cloud-project-connection",{ref:e}))}function Dr(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function ht(e){return dl(e,Dt(process.cwd())?.local??null)}async function Lr(){return R("cloud-team-list")}async function In(e){return R("cloud-project-export",{ref:e})}async function nn(e){return R("cloud-project-export",{ref:e},"GET")}async function Cn(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await We(),s=o.find(c=>c.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ll(t,a))return s;if(Date.now()>=r)throw new Re(504,`${e} is still ${a} after waiting`);await new Promise(c=>setTimeout(c,2e3))}}function Ll(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Mn(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await We(),o=r.find(s=>s.ref===e);if(!o)throw new Re(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new Re(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}function jl(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 jr(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 Ze,Qe,Re,Et,Ml,pe=W(()=>{"use strict";_t();An();be();Pt();Cl();de();Tn();Pr();Ze=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Qe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",Re=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Et=class extends Error{constructor(t){super(t??xh())}};Ml=!1});var Nu,NS,Iu=W(()=>{"use strict";Nu="snoutpod_isolation",NS=`table bridge ${Nu}
26
+ delete table bridge ${Nu}
27
+ `});function Ta(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Mu(e,t,n){let r="";for(;r.length<n;)r+=Ta(e,t);return r}var Ra,Cu,Du=W(()=>{"use strict";Ra="0123456789abcdefghjkmnpqrstvwxyz",Cu="abcdefghjkmnpqrstvwxyz"});function Uu(e){return Ta(e,Lu)+Mu(e,Ra,ju-1)}function CS(e){return IS.test(e)}function se(e){if(!CS(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function H(e,t=MS){return se(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Lu,ju,IS,MS,Le=W(()=>{"use strict";Du();Lu=Cu,ju=13,IS=new RegExp(`^[${Lu}][${Ra}]{${ju-1}}$`);MS="db.snoutdata.com"});var Fu=W(()=>{"use strict";Le()});function Hu(e,t){let n=Bu(e,7),r=Bu(t,24);return Math.max(2,Math.ceil(n*24/r))}function Bu(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Oa=W(()=>{"use strict"});function Na(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Ia=W(()=>{"use strict"});var Ku=W(()=>{"use strict"});function qu(e){let n=(e.trim().split(`
28
+ `)[0]??"").split("|");if(n.length!==7||n[0]!=="t"&&n[0]!=="f"||n[5]!=="t"&&n[5]!=="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(","),lastArchivedWal:/^[0-9A-F]{24}$/.test(n[4])?n[4]:"",keeperReady:n[5]==="t",certSha256:/^[0-9a-f]{64}$/.test(n[6])?n[6]:""}}function US(e){if(!DS.test(e))throw new Error(`not a keeper name: ${e}`);return`keeper_${e.replace(/-/g,"_")}`}function zu(e){let t=e.map(r=>`'${US(r)}'`).join(", "),n=`from pg_replication_slots where slot_name like 'keeper\\_%'${e.length===0?"":` and slot_name not in (${t})`}`;return[`select pg_drop_replication_slot(slot_name) ${n} and not active`,`select count(*) ${n} and active`]}function Wu(e){if(e!==""&&!jS.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()"]}function BS(e){return typeof e=="string"&&FS.test(e)}function HS(e){return e===pn||BS(e)}function Ma(e){if(!HS(e))throw new Error(`not an IPv4 address or ${pn}: ${JSON.stringify(e)}`);return`hostssl replication ${un} ${e===pn?pn:`${e}/32`} scram-sha-256`}function Ju(e,t=!0){if(!Ca.test(e))throw new Error("not a SCRAM-SHA-256 verifier");let n=[`do $$ begin if not exists (select 1 from pg_roles where rolname = '${un}') then create role ${un} with login replication; end if; end $$`,`alter role ${un} with login replication noinherit password '${e}'`];return t?[...n,`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${ho}' and wal_status = 'lost' and not active`,`select pg_create_physical_replication_slot('${ho}', true) where not exists (select 1 from pg_replication_slots where slot_name = '${ho}')`]:[...n,Da]}var DS,LS,Ot,Ca,Gu,yo,jS,un,ho,FS,pn,Da,wo=W(()=>{"use strict";Le();DS=/^[a-z0-9][a-z0-9-]*$/,LS=20,Ot="snoutpod_keeper",Ca=/^SCRAM-SHA-256\$[0-9]{4,6}:[A-Za-z0-9+/]+=*\$[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/,Gu=`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 '${LS} seconds')), ''), coalesce((select last_archived_wal from pg_stat_archiver), ''), exists (select 1 from pg_roles where rolname = '${Ot}' and rolreplication) and exists (select 1 from pg_hba_file_rules where database = '{replication}' and '${Ot}' = any (user_name)) and current_setting('ssl') = 'on', case when current_setting('ssl') = 'on' then encode(sha256(pg_read_binary_file(current_setting('ssl_cert_file'))), 'hex') else '' end`;yo=/^-----BEGIN CERTIFICATE-----\n[A-Za-z0-9+/=\n]{64,12000}-----END CERTIFICATE-----\n$/,jS=/^(?:"[a-z0-9-]+"|(?:ANY|FIRST) [1-9] \("[a-z0-9-]+"(?:, "[a-z0-9-]+")+\))$/;un="snoutpod_replica",ho="snoutpod_replica",FS=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;pn="samehost";Da=`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${ho}' and not active`});function Yu(e,t){return Zu(e,t,"swap")}function Xu(e,t){return Zu(e,t,"adopt")}function Zu(e,t,n){let r=null,o=!1;for(let s of e.split(`
29
+ `)){let a=s.trim();a.startsWith(`${n}-error`)?r=a.slice(`${n}-error`.length).trim()||`the ${n} failed`:a===`${n}-done`&&(o=!0)}return r||(o&&t===0?null:`the ${n} ended without finishing (exit ${t})`)}var Vu,La,ja,Ua,Fa=W(()=>{"use strict";Vu='snoutpod-pgbackrest archive-get %f "%p"',La=String.raw`set -u
30
30
  A=$1
31
31
  B=$2
32
32
  fail() { echo "swap-error $*"; exit 3; }
@@ -45,13 +45,42 @@ kept=""
45
45
  if [ -f "$C" ]; then
46
46
  kept=$(grep -v -E '^[[:space:]]*(primary_conninfo|primary_slot_name|restore_command|synchronous_standby_names)[[:space:]]*=' "$C")
47
47
  fi
48
- { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+Mb.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
48
+ { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+Vu.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
49
49
  echo "swap-done"
50
- `;sa=["select pg_promote(true, 120)","alter system reset synchronous_standby_names","alter system reset primary_conninfo","alter system reset primary_slot_name","alter system reset restore_command","select pg_reload_conf()"]});function mu(e){return`*:*:*:snoutpod_replica:${e.replace(/[\\:]/g,"\\$&")}
51
- `}var uu,pu,mx,ia=D(()=>{"use strict";aa();zr();uu="/tmp/snoutpod/replica.pgpass",pu="/tmp/snoutpod/primary.crt";mx=Buffer.from([0,0,0,8,4,210,22,47])});import{createHash as jb,createHmac as fu,pbkdf2Sync as Lb,randomBytes as hx}from"node:crypto";function gu(e,t){let n=Ub.exec(e.trim());if(!n)return!1;let[,r,o,s,a]=n,c=Number(r);if(!Number.isInteger(c)||c<1||c>1e6)return!1;let i=Lb(t.normalize("NFKC"),Buffer.from(o,"base64"),c,32,"sha256"),u=fu("sha256",i).update("Client Key").digest(),l=jb("sha256").update(u).digest(),m=fu("sha256",i).update("Server Key").digest();return l.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var Ub,la=D(()=>{"use strict";Ub=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function yu(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(`
52
- `)){let[s,a]=o.trim().split(/\s+/),c=s?n.get(s):void 0;c&&(a==="0"||a==="1"?r.set(c,!0):a==="2"&&r.set(c,!1))}return r}var hu,ca=D(()=>{"use strict";hu='for h in "$@"; do ( pg_isready -q -h "$h" -p 5432 -U snoutpod_admin -d postgres -t 3; echo "$h $?" ) & done; wait'});function Bb(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function bu(e){let[t,n]=(e.trim().split(`
53
- `)[0]??"").split("|");return t==="t"&&n!==void 0&&Bb(n)?n:null}var Fb,bx,wu,da=D(()=>{"use strict";Fb="/var/lib/postgresql/data",bx=`set -u
54
- D=${Fb}
50
+ `;ja=String.raw`set -u
51
+ A=$1
52
+ B=$2
53
+ fail() { echo "adopt-error $*"; exit 3; }
54
+ dev() { stat -c %d "$1" 2>/dev/null || stat -f %d "$1"; }
55
+ [ -d "$A" ] || fail "the home's volume on this host has no data directory"
56
+ [ -d "$B" ] || fail "the replica's volume has no data directory"
57
+ [ "$(dev "$A")" = "$(dev "$B")" ] || fail "the home's volume and the replica's are on different filesystems, so the data was not moved"
58
+ if [ -e "$B/snoutpod-adopted" ]; then
59
+ [ -f "$A/PG_VERSION" ] || fail "the replica's data was moved before, and is not in the home's volume"
60
+ else
61
+ [ -f "$B/PG_VERSION" ] || fail "the replica's volume holds no cluster"
62
+ [ -f "$B/standby.signal" ] || fail "the replica's data is not a standby's"
63
+ [ -z "$(ls -A "$A")" ] || fail "the home's volume on this host is not empty"
64
+ rmdir "$A" || fail "the home's empty volume could not be made ready"
65
+ if ! mv "$B" "$A"; then
66
+ mkdir -p "$A"
67
+ fail "the replica's data could not be moved into the home's volume"
68
+ fi
69
+ mkdir "$B" || fail "the replica's data is in the home's volume, and its own volume could not be made again"
70
+ : > "$B/snoutpod-adopted" || fail "the replica's data is in the home's volume, and its own volume could not be marked"
71
+ fi
72
+ C="$A/postgresql.auto.conf"
73
+ kept=""
74
+ if [ -f "$C" ]; then
75
+ kept=$(grep -v -E '^[[:space:]]*(primary_conninfo|primary_slot_name|restore_command|synchronous_standby_names)[[:space:]]*=' "$C")
76
+ fi
77
+ { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+Vu.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
78
+ echo "adopt-done"
79
+ `;Ua=["select pg_promote(true, 120)","alter system reset synchronous_standby_names","alter system reset primary_conninfo","alter system reset primary_slot_name","alter system reset restore_command","select pg_reload_conf()"]});function tp(e){return`*:*:*:snoutpod_replica:${e.replace(/[\\:]/g,"\\$&")}
80
+ `}var Qu,ep,Tx,Ba=W(()=>{"use strict";Fa();wo();Qu="/tmp/snoutpod/replica.pgpass",ep="/tmp/snoutpod/primary.crt";Tx=Buffer.from([0,0,0,8,4,210,22,47])});import{createHash as KS,createHmac as np,pbkdf2Sync as GS,randomBytes as Ix}from"node:crypto";function Ha(e,t){let n=qS.exec(e.trim());if(!n)return!1;let[,r,o,s,a]=n,c=Number(r);if(!Number.isInteger(c)||c<1||c>1e6)return!1;let i=GS(t.normalize("NFKC"),Buffer.from(o,"base64"),c,32,"sha256"),u=np("sha256",i).update("Client Key").digest(),l=KS("sha256").update(u).digest(),m=np("sha256",i).update("Server Key").digest();return l.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var qS,Ka=W(()=>{"use strict";qS=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function rp(e){return/^[A-Za-z0-9][A-Za-z0-9.-]{0,252}$/.test(e.host)&&Number.isInteger(e.port)&&e.port>=1&&e.port<=65535?`${e.host}:${e.port}`:null}function sp(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(`
81
+ `)){let[s,a]=o.trim().split(/\s+/),c=s?n.get(s):void 0;c&&(a==="0"||a==="1"?r.set(c,!0):a==="2"&&r.set(c,!1))}return r}var op,Ga=W(()=>{"use strict";op='for t in "$@"; do ( h=${t%:*}; p=5432; case "$t" in *:*) p=${t##*:} ;; esac; pg_isready -q -h "$h" -p "$p" -U snoutpod_admin -d postgres -t 3; echo "$t $?" ) & done; wait'});function WS(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function ip(e){let[t,n]=(e.trim().split(`
82
+ `)[0]??"").split("|");return t==="t"&&n!==void 0&&WS(n)?n:null}var zS,Dx,ap,qa=W(()=>{"use strict";zS="/var/lib/postgresql/data",Dx=`set -u
83
+ D=${zS}
55
84
  W="$D/pg_wal"
56
85
  fail() { echo "salvage-error $*"; exit 3; }
57
86
  [ -d "$W" ] || fail "the volume has no pg_wal"
@@ -90,24 +119,27 @@ for s in $segs; do
90
119
  echo "salvage-pushed $s"
91
120
  done
92
121
  echo "salvage-done"
93
- `;wu="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var kx,Su=D(()=>{"use strict";Te();kx=7*24});var $x,_u=D(()=>{"use strict";$x=7*24*60*60*1e3});var Hb,ku=D(()=>{"use strict";_u();Hb=60*60*1e3});var vu=D(()=>{"use strict"});var ua,Ox,$u=D(()=>{"use strict";ua="snoutdata_realtime",Ox=`do $snoutpod$
122
+ `;ap="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var Ux,lp=W(()=>{"use strict";Le();Ux=7*24});var Bx,cp=W(()=>{"use strict";Bx=7*24*60*60*1e3});var JS,dp=W(()=>{"use strict";cp();JS=60*60*1e3});var up=W(()=>{"use strict"});var za,Jx,pp=W(()=>{"use strict";za="snoutdata_realtime",Jx=`do $snoutpod$
94
123
  begin
95
- if exists (select 1 from pg_publication where pubname = '${ua}') then
124
+ if exists (select 1 from pg_publication where pubname = '${za}') then
96
125
  return;
97
126
  end if;
98
- create publication ${ua};
99
- execute format('alter publication ${ua} owner to %I',
127
+ create publication ${za};
128
+ execute format('alter publication ${za} owner to %I',
100
129
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
101
130
  end
102
131
  $snoutpod$;
103
- `});var Ix,Cx,Pu=D(()=>{"use strict";Ix=2*1024*1024,Cx=64*1024});var xu=D(()=>{"use strict"});function Tu(e){if(e.length===0)throw new Error("a peer network needs at least one peer");let t=["network","create","--subnet",pa,"--gateway",ma,"--disable-dns","--opt","no_default_route=1"];for(let n of[...new Set(e)].sort()){if(Jr(Rn("a peer",n,fa),pa))throw new Error(`the peer ${n} is on the peer network's bridge itself, which needs no route`);t.push("--route",`${n}/32,${ma}`)}return t.push(Pe),t}function Ou(e,t){let n;try{let a=JSON.parse(e);n=Array.isArray(a)?a[0]:a}catch{return!1}if(!n||n.internal===!0||n.dns_enabled===!0||!(n.subnets??[]).some(a=>a.subnet===pa))return!1;let r=n.options?.no_default_route;if(r!=="1"&&r!=="true")return!1;let o=(n.routes??[]).map(a=>`${a.destination??""},${a.gateway??""}`).sort(),s=[...new Set(t)].sort().map(a=>`${a}/32,${ma}`);return o.length===s.length&&o.every((a,c)=>a===s[c])}function Nu(e,t){let n;try{let o=JSON.parse(e);n=(Array.isArray(o)?o[0]:o)?.routes}catch{return[]}let r=new Set((Array.isArray(n)?n:[]).map(o=>String(o.destination??"")));return t.filter(o=>r.has(`${o}/32`))}function Rn(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Vr(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((e.peers?.length??0)>0&&r==null)throw new Error("a peer is an exception to a deny-list, so it needs one");if(r!=null)return Kb(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 Kb(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(Rn("the subnet",n,Eu),Rn("the sink",r,fa),!Jr(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Jr(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",`${Rn("a denied prefix",s,Eu)},${r}`);for(let s of e.peers??[]){if(!e.gateway)throw new Error("a peer is reached through the gateway, so it needs a pinned gateway");if(Jr(Rn("a peer",s,fa),n))throw new Error(`the peer ${s} is on the bridge itself, which needs no route`);o.push("--route",`${s}/32,${e.gateway}`)}return o.push(e.name),o}function Au(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Jr(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Au(e)&s)>>>0===(Au(n??"")&s)>>>0}function Iu(e){return e?.internal===!0}var Ru,Pe,pa,ma,Eu,fa,Yr=D(()=>{"use strict";Ru=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],Pe="snoutpeer",pa="10.96.0.0/24",ma="10.96.0.1";Eu=/^((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])$/,fa=/^((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 Gb,Xr=D(()=>{"use strict";Gb=7*24*60*60});function Jb(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 c=32;c>=25;c--){let i=2**(32-c);a.push(`${o}${(s^i)&~(i-1)&255}/${c}`)}return a}var Wb,zb,Xx,Cu=D(()=>{"use strict";Te();Pu();xu();Yr();Xr();Wb="10.93.0.0/24",zb="10.93.0.1",Xx=[...Ru,"100.64.0.0/10",...Jb(Wb,zb)]});var Du=D(()=>{"use strict"});import{createHash as Vb}from"node:crypto";function kt(e){return`${B(e).pod}-${ge}`}function Qe(e){return e.layout==="separated"}function ha(e){return Qe(e)?[]:e.services??[]}function ya(e){if(typeof e!="number"||!Number.isInteger(e)||e<1)throw new Error(`a separated router needs its own pod port (routerPodPort), and has ${JSON.stringify(e??null)}`);let t={postgres:e+Mu*1e3,admin:e+Mu*1e3+to,passthrough:e+Yb*1e3};if(t.passthrough>65535)throw new Error(`routerPodPort ${e} puts the router's ports past 65535`);return t}function ju(e,t){let n={...t};return delete n.LEPIS_HOMES,n.LEPIS_HOME=`${B(ae(e)).pod}:5432`,n.LEPIS_PASSTHROUGH_PORT=String(Xt),n}function Lu(e,t){let n=ae(e);return t?["\\getenv router_password SNOUTPOD_ROUTER_PASSWORD",`do $$ begin if not exists (select from pg_roles where rolname = '${Ae}') then create role ${Ae}; end if; end $$;`,`alter role ${Ae} with login noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls connection limit 64 password :'router_password';`,`create schema if not exists ${Ae} authorization snoutpod_admin;`,`create or replace function ${Ae}.verifier(role_name text) returns text`," language sql stable security definer set search_path = pg_catalog","as $f$"," select a.rolpassword from pg_catalog.pg_authid a"," where a.rolname = role_name and a.rolcanlogin and not a.rolsuper and not a.rolreplication"," and (a.rolvaliduntil is null or a.rolvaliduntil > now())"," and a.rolname not like 'snoutpod\\_%' and a.rolname <> 'postgres'","$f$;",`revoke all on schema ${Ae} from public;`,`revoke all on function ${Ae}.verifier(text) from public;`,`grant usage on schema ${Ae} to ${Ae};`,`grant execute on function ${Ae}.verifier(text) to ${Ae};`,`grant connect on database "${n}" to ${Ae};`,""].join(`
104
- `):[`do $$ begin if exists (select from pg_roles where rolname = '${Ae}') then alter role ${Ae} nologin; end if; end $$;`,""].join(`
105
- `)}function Tn(e){return`${B(e).pod}-lepis-tls`}function Zb(e){return Vb("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function wa(e,t){let n={...e},r=n[Zr],o=n[Qr];delete n[Zr],delete n[Qr];for(let[s,a,c]of[[Zr,r,t],[Qr,o,Zb(t)]])if(a!==void 0){if(!Xb.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=c}return n}var ge,Zr,Qr,eo,to,ga,Mu,o0,Xt,Yb,_t,Ae,no,Uu,Xb,ro=D(()=>{"use strict";Te();Du();ge="lepis",Zr="SNOUTPOD_ADMIN_PASSWORD_ENV",Qr="SNOUTPOD_ADMIN_TOKEN_ENV",eo=7433,to=1e3,ga=7432,Mu=4,o0={LEPIS_HOST:"0.0.0.0",LEPIS_PORT:String(ga),LEPIS_HOME:"127.0.0.1:5432",LEPIS_HOME_SSLMODE:"disable",LEPIS_SERVICE_USER:"snoutpod_admin",[Zr]:"LEPIS_SERVICE_PASSWORD",LEPIS_ADMIN_ADDR:`0.0.0.0:${eo}`,LEPIS_TLS_CERT:"/tls/cert.pem",LEPIS_TLS_KEY:"/tls/key.pem",[Qr]:"LEPIS_ADMIN_TOKEN",LEPIS_LOG:"info"},Xt=7434,Yb=7,_t="snoutpod.lepis";Ae="snoutpod_router";no="/tls",Uu=["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(`
106
- `),Xb=/^[A-Z][A-Z0-9_]{0,63}$/});function ba(e){return Fu.includes(e)}async function Sa(e,t){if(!ba(t))throw new Error(`no password is derived for role ${JSON.stringify(t)}: only ${Fu.join(", ")}`);if(e==="")throw new Error("no admin password to derive a role password from");let n=new TextEncoder,r=await crypto.subtle.importKey("raw",n.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=new Uint8Array(await crypto.subtle.sign("HMAC",r,n.encode(`role:${t}`))),s="";for(let a of o)s+=String.fromCharCode(a);return btoa(s).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var Fu,_a=D(()=>{"use strict";Fu=["snout_gate","snout_auth_admin","snout_storage_admin","snout_realtime_admin","snout_psyche_admin","snout_push_admin","snoutpod_router"]});var u0,p0,Bu=D(()=>{"use strict";Te();Xr();u0=6*60*60,p0=5*60});var Hu=D(()=>{"use strict"});var Ku=D(()=>{"use strict"});var h0,Gu=D(()=>{"use strict";h0=15*6e4});var _0,qu=D(()=>{"use strict";Gu();_0=2*1024**3});var Wu=D(()=>{"use strict";Ku();qu()});function Ju(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"||!eS.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Qb.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 Vu(e,t){return`${e}.nspname !~ '^pg_'
107
- and ${e}.nspname <> all (array[${zu.map(n=>`'${n}'`).join(", ")}]::text[])
132
+ `});var Yx,Xx,mp=W(()=>{"use strict";Yx=2*1024*1024,Xx=64*1024});var fp=W(()=>{"use strict"});function _o(e,t={name:xe,subnet:So,gateway:wp}){if(e.length===0)throw new Error("a peer network needs at least one peer");let n=["network","create","--subnet",t.subnet,"--gateway",t.gateway,"--disable-dns","--opt","no_default_route=1"];for(let r of[...new Set(e)].sort()){if(bo(Bt("a peer",r,Yn),t.subnet))throw new Error(`the peer ${r} is on the peer network's bridge itself, which needs no route`);n.push("--route",`${r}/32,${t.gateway}`)}return n.push(t.name),n}function bp(e){if(!Ja.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);return`${ko}${e}`}function vo(e){let t=e.startsWith(ko)?e.slice(ko.length):"";return Ja.test(t)?t:null}function Ht(e){let t=/^10\.96\.([0-9]{1,3})\.([0-9]{1,3})\/28$/.exec(e),n=t?Number(t[1]):-1,r=t?Number(t[2]):-1;return n<1||n>255||r<0||r>240||r%16!==0?null:{subnet:e,gateway:`10.96.${n}.${r+1}`}}function Va(e){let t=new Set(e);for(let n=1;n<=255;n+=1)for(let r=0;r<=240;r+=16){let o=`10.96.${n}.${r}/28`;if(!t.has(o))return o}return null}function Sp(e,t){if(!Ja.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);let n=Bt("a cluster peer",t,Yn).split(".").map(Number);return`${ko}${e}-${n.map(r=>r.toString(16).padStart(2,"0")).join("")}`}function mn(e){let t=VS.exec(e);if(!t)return null;let n=t[2],r=[0,2,4,6].map(o=>parseInt(n.slice(o,o+2),16)).join(".");return{group:t[1],peer:r}}function Ya(e){return vo(e)!==null||mn(e)!==null}function YS(e){let t=[],n=[...e].sort((r,o)=>r.subnet<o.subnet?-1:r.subnet>o.subnet?1:r.peer<o.peer?-1:r.peer>o.peer?1:0);for(let r of n){if(!Ht(r.subnet))throw new Error(`not a peer network's subnet: ${r.subnet}`);if(Bt("a peer",r.peer,Yn),r.ports===null){t.push({from:r.subnet,to:r.peer,match:"all",verdict:"accept"},{from:r.peer,to:r.subnet,match:"all",verdict:"accept"});continue}let o=[...new Set(r.ports)].sort((s,a)=>s-a);for(let s of o)if(!Number.isInteger(s)||s<1||s>65535)throw new Error(`not a port: ${s}`);o.length!==0&&t.push({from:r.subnet,to:r.peer,match:"dport",ports:o,verdict:"accept"},{from:r.peer,to:r.subnet,match:"sport",ports:o,verdict:"accept"},{from:r.peer,to:r.subnet,match:"icmp",verdict:"accept"})}return t.push({from:Wa,to:"0.0.0.0/0",match:"all",verdict:"drop"},{from:"0.0.0.0/0",to:Wa,match:"all",verdict:"drop"}),t}function kp(e){let t=n=>{let r=c=>c.length===1?String(c[0]):`{ ${c.join(", ")} }`,o=(c,i)=>i==="0.0.0.0/0"?"":`ip ${c} ${i}`,s=n.verdict==="drop"?n.from===Wa?`ip saddr != ${So}`:`ip daddr != ${So}`:"",a=n.match==="dport"?`tcp dport ${r(n.ports)}`:n.match==="sport"?`tcp sport ${r(n.ports)}`:n.match==="icmp"?"ip protocol icmp":"";return[" "+[o("saddr",n.from),o("daddr",n.to),s,a,n.verdict==="drop"?"counter drop":"accept"].filter(Boolean).join(" ")].join("")};return[`table inet ${Vn}`,`delete table inet ${Vn}`,`table inet ${Vn} {`," chain forward {"," type filter hook forward priority 0; policy accept;",...YS(e).map(t)," }","}",""].join(`
133
+ `)}function $o(e,t,n={subnet:So,gateway:wp}){let r;try{let c=JSON.parse(e);r=Array.isArray(c)?c[0]:c}catch{return!1}if(!r||r.internal===!0||r.dns_enabled===!0||!(r.subnets??[]).some(c=>c.subnet===n.subnet))return!1;let o=r.options?.no_default_route;if(o!=="1"&&o!=="true")return!1;let s=(r.routes??[]).map(c=>`${c.destination??""},${c.gateway??""}`).sort(),a=[...new Set(t)].sort().map(c=>`${c}/32,${n.gateway}`);return s.length===a.length&&s.every((c,i)=>c===a[i])}function Po(e,t){let n;try{let o=JSON.parse(e);n=(Array.isArray(o)?o[0]:o)?.routes}catch{return[]}let r=new Set((Array.isArray(n)?n:[]).map(o=>String(o.destination??"")));return t.filter(o=>r.has(`${o}/32`))}function Bt(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function xo(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((e.peers?.length??0)>0&&r==null)throw new Error("a peer is an exception to a deny-list, so it needs one");if(r!=null)return XS(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 XS(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(Bt("the subnet",n,gp),Bt("the sink",r,Yn),!bo(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!bo(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",`${Bt("a denied prefix",s,gp)},${r}`);for(let s of e.peers??[]){if(!e.gateway)throw new Error("a peer is reached through the gateway, so it needs a pinned gateway");if(bo(Bt("a peer",s,Yn),n))throw new Error(`the peer ${s} is on the bridge itself, which needs no route`);o.push("--route",`${s}/32,${e.gateway}`)}return o.push(e.name),o}function hp(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function bo(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(hp(e)&s)>>>0===(hp(n??"")&s)>>>0}function vp(e){return e?.internal===!0}var yp,xe,So,wp,ko,Ja,VS,Wa,Vn,_p,gp,Yn,Eo=W(()=>{"use strict";yp=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],xe="snoutpeer",So="10.96.0.0/24",wp="10.96.0.1";ko=`${xe}-`,Ja=/^[a-z][0-9a-z]{12}$/;VS=/^snoutpeer-([a-z][0-9a-z]{12})-([0-9a-f]{8})$/;Wa="10.96.0.0/16";Vn="snoutpod_peers";_p=`table inet ${Vn}
134
+ delete table inet ${Vn}
135
+ `;gp=/^((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])$/,Yn=/^((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 ZS,Ao=W(()=>{"use strict";ZS=7*24*60*60});function nk(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 c=32;c>=25;c--){let i=2**(32-c);a.push(`${o}${(s^i)&~(i-1)&255}/${c}`)}return a}var ek,tk,fE,$p=W(()=>{"use strict";Le();mp();fp();Eo();Ao();ek="10.93.0.0/24",tk="10.93.0.1",fE=[...yp,"100.64.0.0/10",...nk(ek,tk)]});var Pp=W(()=>{"use strict"});import{createHash as rk}from"node:crypto";function bt(e){return`${H(e).pod}-${ge}`}function lt(e){return e.layout==="separated"}function Za(e){return lt(e)?[]:e.services??[]}function Qa(e){if(typeof e!="number"||!Number.isInteger(e)||e<1)throw new Error(`a separated router needs its own pod port (routerPodPort), and has ${JSON.stringify(e??null)}`);let t={postgres:e+xp*1e3,admin:e+xp*1e3+No,passthrough:e+ok*1e3};if(t.passthrough>65535)throw new Error(`routerPodPort ${e} puts the router's ports past 65535`);return t}function Ep(e,t){let n={...t};return delete n.LEPIS_HOMES,n.LEPIS_HOME=`${H(se(e)).pod}:5432`,n.LEPIS_PASSTHROUGH_PORT=String(fn),n}function Ap(e,t){let n=se(e);return t?["\\getenv router_password SNOUTPOD_ROUTER_PASSWORD",`do $$ begin if not exists (select from pg_roles where rolname = '${Ne}') then create role ${Ne}; end if; end $$;`,`alter role ${Ne} with login noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls connection limit 64 password :'router_password';`,`create schema if not exists ${Ne} authorization snoutpod_admin;`,`create or replace function ${Ne}.verifier(role_name text) returns text`," language sql stable security definer set search_path = pg_catalog","as $f$"," select a.rolpassword from pg_catalog.pg_authid a"," where a.rolname = role_name and a.rolcanlogin and not a.rolsuper and not a.rolreplication"," and (a.rolvaliduntil is null or a.rolvaliduntil > now())"," and a.rolname not like 'snoutpod\\_%' and a.rolname <> 'postgres'","$f$;",`revoke all on schema ${Ne} from public;`,`revoke all on function ${Ne}.verifier(text) from public;`,`grant usage on schema ${Ne} to ${Ne};`,`grant execute on function ${Ne}.verifier(text) to ${Ne};`,`grant connect on database "${n}" to ${Ne};`,""].join(`
136
+ `):[`do $$ begin if exists (select from pg_roles where rolname = '${Ne}') then alter role ${Ne} nologin; end if; end $$;`,""].join(`
137
+ `)}function Xn(e){return`${H(e).pod}-lepis-tls`}function ak(e){return rk("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function ei(e,t){let n={...e},r=n[Ro],o=n[To];delete n[Ro],delete n[To];for(let[s,a,c]of[[Ro,r,t],[To,o,ak(t)]])if(a!==void 0){if(!sk.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=c}return n}var ge,Ro,To,Oo,No,Xa,xp,kE,fn,ok,Nt,Ne,Io,Rp,sk,Co=W(()=>{"use strict";Le();Pp();ge="lepis",Ro="SNOUTPOD_ADMIN_PASSWORD_ENV",To="SNOUTPOD_ADMIN_TOKEN_ENV",Oo=7433,No=1e3,Xa=7432,xp=4,kE={LEPIS_HOST:"0.0.0.0",LEPIS_PORT:String(Xa),LEPIS_HOME:"127.0.0.1:5432",LEPIS_HOME_SSLMODE:"disable",LEPIS_SERVICE_USER:"snoutpod_admin",[Ro]:"LEPIS_SERVICE_PASSWORD",LEPIS_ADMIN_ADDR:`0.0.0.0:${Oo}`,LEPIS_TLS_CERT:"/tls/cert.pem",LEPIS_TLS_KEY:"/tls/key.pem",[To]:"LEPIS_ADMIN_TOKEN",LEPIS_LOG:"info"},fn=7434,ok=7,Nt="snoutpod.lepis";Ne="snoutpod_router";Io="/tls",Rp=["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(`
138
+ `),sk=/^[A-Z][A-Z0-9_]{0,63}$/});function ti(e){return Tp.includes(e)}async function ni(e,t){if(!ti(t))throw new Error(`no password is derived for role ${JSON.stringify(t)}: only ${Tp.join(", ")}`);if(e==="")throw new Error("no admin password to derive a role password from");let n=new TextEncoder,r=await crypto.subtle.importKey("raw",n.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=new Uint8Array(await crypto.subtle.sign("HMAC",r,n.encode(`role:${t}`))),s="";for(let a of o)s+=String.fromCharCode(a);return btoa(s).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var Tp,ri=W(()=>{"use strict";Tp=["snout_gate","snout_auth_admin","snout_storage_admin","snout_realtime_admin","snout_psyche_admin","snout_push_admin","snoutpod_router"]});var AE,RE,Op=W(()=>{"use strict";Le();Ao();AE=6*60*60,RE=5*60});var Np=W(()=>{"use strict"});var Ip=W(()=>{"use strict"});var IE,Cp=W(()=>{"use strict";IE=15*6e4});var jE,Mp=W(()=>{"use strict";Cp();jE=2*1024**3});var Dp=W(()=>{"use strict";Ip();Mp()});function jp(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"||!lk.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!ik.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 Up(e,t){return`${e}.nspname !~ '^pg_'
139
+ and ${e}.nspname <> all (array[${Lp.map(n=>`'${n}'`).join(", ")}]::text[])
108
140
  and not ${t}.rolsuper
109
- and ${t}.rolname !~ '${tS.source}'`}function Xu(e){let t=[];for(let n of e.split(`
110
- `)){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 Zu(e,t){return ae(e),JSON.stringify(Ju(t).map(n=>({role:n.role,level:n.level})))}var Qb,Zt,eS,re,zu,tS,so,nS,oo,rS,Yu,ka=D(()=>{"use strict";Te();Qb=["full","read"],Zt="snout_oauth",eS=/^oauth_[0-9a-f]{32}$/,re="snout_oauth_read",zu=["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"],tS=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;so="SNOUTPOD_DB_ACCESS",nS=/^[a-z_][a-z0-9_]*$/;for(let e of zu)if(!nS.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);oo="snoutpod_oauth_read_new_schema",rS=`do $home$
141
+ and ${t}.rolname !~ '${ck.source}'`}function Bp(e){let t=[];for(let n of e.split(`
142
+ `)){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 Hp(e,t){return se(e),JSON.stringify(jp(t).map(n=>({role:n.role,level:n.level})))}var ik,gn,lk,ae,Lp,ck,Do,dk,Mo,uk,Fp,oi=W(()=>{"use strict";Le();ik=["full","read"],gn="snout_oauth",lk=/^oauth_[0-9a-f]{32}$/,ae="snout_oauth_read",Lp=["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"],ck=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;Do="SNOUTPOD_DB_ACCESS",dk=/^[a-z_][a-z0-9_]*$/;for(let e of Lp)if(!dk.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Mo="snoutpod_oauth_read_new_schema",uk=`do $home$
111
143
  declare
112
144
  -- snout_ext in the own layout, extensions in a database not yet renamed (docs/cloud/OWN-LAYOUT.md).
113
145
  home text := case
@@ -116,7 +148,7 @@ declare
116
148
  end;
117
149
  begin
118
150
  execute pg_catalog.format('create schema if not exists %I', home);
119
- execute pg_catalog.replace($fn$create or replace function @home@.${oo}() returns event_trigger
151
+ execute pg_catalog.replace($fn$create or replace function @home@.${Mo}() returns event_trigger
120
152
  language plpgsql
121
153
  security invoker
122
154
  set search_path = pg_catalog, pg_temp
@@ -125,7 +157,7 @@ declare
125
157
  created record;
126
158
  owner_role text := pg_catalog.current_database() || '_owner';
127
159
  begin
128
- if not exists (select from pg_catalog.pg_roles where rolname = '${re}') then
160
+ if not exists (select from pg_catalog.pg_roles where rolname = '${ae}') then
129
161
  return;
130
162
  end if;
131
163
  for created in
@@ -136,29 +168,29 @@ begin
136
168
  where c.command_tag = 'CREATE SCHEMA'
137
169
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
138
170
  and not c.in_extension
139
- and ${Vu("n","r")}
171
+ and ${Up("n","r")}
140
172
  loop
141
173
  begin
142
- execute pg_catalog.format('grant usage on schema %I to ${re}', created.nspname);
143
- execute pg_catalog.format('grant select on all tables in schema %I to ${re}', created.nspname);
144
- execute pg_catalog.format('grant select on all sequences in schema %I to ${re}', created.nspname);
145
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${re}', owner_role, created.nspname);
146
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${re}', owner_role, created.nspname);
174
+ execute pg_catalog.format('grant usage on schema %I to ${ae}', created.nspname);
175
+ execute pg_catalog.format('grant select on all tables in schema %I to ${ae}', created.nspname);
176
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${ae}', created.nspname);
177
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${ae}', owner_role, created.nspname);
178
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${ae}', owner_role, created.nspname);
147
179
  exception when others then
148
180
  raise warning 'snoutpod: read access reaches schema % at the next apply, not now: %', created.nspname, sqlerrm;
149
181
  end;
150
182
  end loop;
151
183
  end
152
184
  $snoutpod$$fn$, '@home@', pg_catalog.quote_ident(home));
153
- if not exists (select from pg_catalog.pg_event_trigger where evtname = '${oo}') then
154
- execute pg_catalog.format('create event trigger ${oo} on ddl_command_end
185
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${Mo}') then
186
+ execute pg_catalog.format('create event trigger ${Mo} on ddl_command_end
155
187
  when tag in (''CREATE SCHEMA'')
156
- execute function %I.${oo}()', home);
188
+ execute function %I.${Mo}()', home);
157
189
  end if;
158
190
  end
159
191
  $home$;
160
- `,Yu=`\\getenv db_access ${so}
161
- ${rS}create function pg_temp.snoutpod_member(member_name text, group_name text)
192
+ `,Fp=`\\getenv db_access ${Do}
193
+ ${uk}create function pg_temp.snoutpod_member(member_name text, group_name text)
162
194
  returns boolean
163
195
  language sql
164
196
  stable
@@ -179,7 +211,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
179
211
  set search_path = pg_catalog, pg_temp
180
212
  as $snoutpod$
181
213
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
182
- select ${Vu("n","r")}
214
+ select ${Up("n","r")}
183
215
  from pg_namespace n
184
216
  join pg_roles r on r.oid = n.nspowner
185
217
  where n.oid = schema_oid
@@ -214,9 +246,9 @@ begin
214
246
  end if;
215
247
  any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
216
248
 
217
- if not exists (select from pg_roles where rolname = '${Zt}') then
218
- create role ${Zt} nologin;
219
- change := 'created-group'; role_name := '${Zt}'; return next;
249
+ if not exists (select from pg_roles where rolname = '${gn}') then
250
+ create role ${gn} nologin;
251
+ change := 'created-group'; role_name := '${gn}'; return next;
220
252
  end if;
221
253
 
222
254
  -- Taken away: every member of the group the list does not name.
@@ -225,7 +257,7 @@ begin
225
257
  from pg_auth_members am
226
258
  join pg_roles g on g.oid = am.roleid
227
259
  join pg_roles m on m.oid = am.member
228
- where g.rolname = '${Zt}'
260
+ where g.rolname = '${gn}'
229
261
  order by 1
230
262
  loop
231
263
  continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
@@ -244,21 +276,21 @@ begin
244
276
 
245
277
  -- What a read person may read: the customer's schemas, and nothing of ours (dbAccess.ts).
246
278
  if any_read then
247
- if not exists (select from pg_roles where rolname = '${re}') then
248
- create role ${re} nologin;
249
- change := 'created-group'; role_name := '${re}'; return next;
279
+ if not exists (select from pg_roles where rolname = '${ae}') then
280
+ create role ${ae} nologin;
281
+ change := 'created-group'; role_name := '${ae}'; return next;
250
282
  end if;
251
283
  -- Stated whole, as a person's role is, and a member of nothing: whatever it held would reach
252
284
  -- every read person.
253
- alter role ${re} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
285
+ alter role ${ae} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
254
286
  for target in
255
287
  select g.rolname as of_role, pg_get_userbyid(am.grantor) as grantor
256
288
  from pg_auth_members am
257
289
  join pg_roles g on g.oid = am.roleid
258
290
  join pg_roles m on m.oid = am.member
259
- where m.rolname = '${re}'
291
+ where m.rolname = '${ae}'
260
292
  loop
261
- execute pg_catalog.format('revoke %I from ${re} granted by %I', target.of_role, target.grantor);
293
+ execute pg_catalog.format('revoke %I from ${ae} granted by %I', target.of_role, target.grantor);
262
294
  end loop;
263
295
  for target in
264
296
  select n.nspname, pg_temp.snoutpod_customer_schema(n.oid) as customer
@@ -267,16 +299,16 @@ begin
267
299
  order by 1
268
300
  loop
269
301
  if target.customer then
270
- execute pg_catalog.format('grant usage on schema %I to ${re}', target.nspname);
271
- execute pg_catalog.format('grant select on all tables in schema %I to ${re}', target.nspname);
272
- execute pg_catalog.format('grant select on all sequences in schema %I to ${re}', target.nspname);
273
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${re}', owner_role, target.nspname);
274
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${re}', owner_role, target.nspname);
302
+ execute pg_catalog.format('grant usage on schema %I to ${ae}', target.nspname);
303
+ execute pg_catalog.format('grant select on all tables in schema %I to ${ae}', target.nspname);
304
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${ae}', target.nspname);
305
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${ae}', owner_role, target.nspname);
306
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${ae}', owner_role, target.nspname);
275
307
  else
276
308
  -- Ours. Nothing was ever granted here unless the list or an owner changed since.
277
- execute pg_catalog.format('revoke all on schema %I from ${re}', target.nspname);
278
- execute pg_catalog.format('revoke all on all tables in schema %I from ${re}', target.nspname);
279
- execute pg_catalog.format('revoke all on all sequences in schema %I from ${re}', target.nspname);
309
+ execute pg_catalog.format('revoke all on schema %I from ${ae}', target.nspname);
310
+ execute pg_catalog.format('revoke all on all tables in schema %I from ${ae}', target.nspname);
311
+ execute pg_catalog.format('revoke all on all sequences in schema %I from ${ae}', target.nspname);
280
312
  end if;
281
313
  end loop;
282
314
  for target in
@@ -285,10 +317,10 @@ begin
285
317
  from pg_default_acl d
286
318
  join pg_namespace n on n.oid = d.defaclnamespace
287
319
  where not pg_temp.snoutpod_customer_schema(n.oid)
288
- and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${re}'::regrole)
320
+ and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${ae}'::regrole)
289
321
  loop
290
322
  continue when target.kind is null;
291
- execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${re}', target.for_role, target.nspname, target.kind);
323
+ execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${ae}', target.for_role, target.nspname, target.kind);
292
324
  end loop;
293
325
  end if;
294
326
 
@@ -297,7 +329,7 @@ begin
297
329
  wanted_role := entry->>'role';
298
330
  wanted_level := entry->>'level';
299
331
  if exists (select from pg_roles where rolname = wanted_role) then
300
- if not pg_temp.snoutpod_member(wanted_role, '${Zt}') then
332
+ if not pg_temp.snoutpod_member(wanted_role, '${gn}') then
301
333
  -- The name is right and the role is not ours: made by somebody else, who holds
302
334
  -- ADMIN on it. Never adopted (see dbAccess.ts).
303
335
  change := 'refused'; role_name := wanted_role; return next;
@@ -305,7 +337,7 @@ begin
305
337
  end if;
306
338
  change := 'applied';
307
339
  else
308
- execute pg_catalog.format('create role %I login in role ${Zt}', wanted_role);
340
+ execute pg_catalog.format('create role %I login in role ${gn}', wanted_role);
309
341
  change := 'created';
310
342
  end if;
311
343
  -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
@@ -318,8 +350,8 @@ begin
318
350
  execute pg_catalog.format('revoke pg_read_all_data from %I', wanted_role);
319
351
  end if;
320
352
  if wanted_level = 'full' then
321
- if pg_temp.snoutpod_member(wanted_role, '${re}') then
322
- execute pg_catalog.format('revoke ${re} from %I', wanted_role);
353
+ if pg_temp.snoutpod_member(wanted_role, '${ae}') then
354
+ execute pg_catalog.format('revoke ${ae} from %I', wanted_role);
323
355
  end if;
324
356
  execute pg_catalog.format('alter role %I nobypassrls', wanted_role);
325
357
  if not pg_temp.snoutpod_member(wanted_role, owner_role) then
@@ -334,8 +366,8 @@ begin
334
366
  execute pg_catalog.format('revoke %I from %I', owner_role, wanted_role);
335
367
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = wanted_role;
336
368
  end if;
337
- if not pg_temp.snoutpod_member(wanted_role, '${re}') then
338
- execute pg_catalog.format('grant ${re} to %I with inherit true, set false', wanted_role);
369
+ if not pg_temp.snoutpod_member(wanted_role, '${ae}') then
370
+ execute pg_catalog.format('grant ${ae} to %I with inherit true, set false', wanted_role);
339
371
  end if;
340
372
  execute pg_catalog.format('alter role %I bypassrls', wanted_role);
341
373
  execute pg_catalog.format('alter role %I in database %I reset role', wanted_role, db);
@@ -346,30 +378,30 @@ begin
346
378
 
347
379
  -- Nobody reads any more: the group goes, and with it every grant and default it held, so a
348
380
  -- project that stops using the level carries nothing of it.
349
- if not any_read and exists (select from pg_roles where rolname = '${re}') then
381
+ if not any_read and exists (select from pg_roles where rolname = '${ae}') then
350
382
  begin
351
383
  for target in
352
384
  select m.rolname as member_role, pg_get_userbyid(am.grantor) as grantor
353
385
  from pg_auth_members am
354
386
  join pg_roles g on g.oid = am.roleid
355
387
  join pg_roles m on m.oid = am.member
356
- where g.rolname = '${re}'
388
+ where g.rolname = '${ae}'
357
389
  loop
358
- execute pg_catalog.format('revoke ${re} from %I granted by %I', target.member_role, target.grantor);
390
+ execute pg_catalog.format('revoke ${ae} from %I granted by %I', target.member_role, target.grantor);
359
391
  end loop;
360
- drop owned by ${re};
361
- drop role ${re};
362
- change := 'dropped-group'; role_name := '${re}'; return next;
392
+ drop owned by ${ae};
393
+ drop role ${ae};
394
+ change := 'dropped-group'; role_name := '${ae}'; return next;
363
395
  exception when others then
364
396
  -- Holding something in another database, which this never grants. Kept, and said.
365
- raise warning 'snoutpod: could not drop ${re}: %', sqlerrm;
397
+ raise warning 'snoutpod: could not drop ${ae}: %', sqlerrm;
366
398
  end;
367
399
  end if;
368
400
  end
369
401
  $snoutpod$;
370
402
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
371
- `});var Qu,CE,DE,ME,jE,LE,ep=D(()=>{"use strict";zd();eu();Qs();ta();ru();zr();ia();la();ca();da();Su();ku();vu();$u();Cu();ro();_a();Bu();Hu();Wu();Xr();ka();Qu=120,CE=2*6e4,DE=15*60*1e3,ME=60*60*1e3,jE=5*60*1e3,LE=10*60*1e3});import{execFile as oS}from"node:child_process";function tp(){return(e,t,n={})=>new Promise((r,o)=>{let s=oS(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,c,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(c),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function N(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
372
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var On=D(()=>{"use strict"});import{readdir as sS,readFile as KE}from"node:fs/promises";async function np(e,t,n={}){let r=n.binary??"podman",o=B(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??aS)(i)!=="empty":!1}async function aS(e){try{return(await sS(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var rp=D(()=>{"use strict";Te();On()});function op(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 ip(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function lp(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=${op("the access key",e.key,lS)}`),n.push("--env",sp),e.token&&n.push("--env",ap)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${op("the endpoint",e.endpoint,iS)}`),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 cp(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{[sp]:e.secret,...e.token?{[ap]:e.token}:{}}}var sp,ap,iS,lS,dp=D(()=>{"use strict";sp="SNOUTPOD_S3_SECRET",ap="SNOUTPOD_S3_TOKEN",iS=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,lS=/^[A-Za-z0-9/+=_.-]{1,256}$/});function mp(e){if(!$a.includes(e))throw new Error(`not a role kept out of the query statistics: ${e}`);return`alter role ${e} set pg_stat_statements.track = 'none';`}var va,up,pp,$a,fp=D(()=>{"use strict";va=["snout_gate","authenticator"],up=`do $snoutpod$
403
+ `});var Kp,XA,ZA,QA,eR,tR,nR,Gp=W(()=>{"use strict";Iu();Fu();Oa();Ia();Ku();wo();Ba();Ka();Ga();qa();lp();dp();up();pp();$p();Co();ri();Op();Np();Dp();Ao();oi();Kp=120,XA=10*6e4,ZA=2*6e4,QA=15*60*1e3,eR=60*60*1e3,tR=5*60*1e3,nR=10*60*1e3});import{execFile as pk}from"node:child_process";function qp(){return(e,t,n={})=>new Promise((r,o)=>{let s=pk(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,c,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(c),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function N(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
404
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Zn=W(()=>{"use strict"});import{readdir as mk,readFile as iR}from"node:fs/promises";async function zp(e,t,n={}){let r=n.binary??"podman",o=H(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??fk)(i)!=="empty":!1}async function fk(e){try{return(await mk(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Wp=W(()=>{"use strict";Le();Zn()});function Jp(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 Xp(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Zp(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=${Jp("the access key",e.key,hk)}`),n.push("--env",Vp),e.token&&n.push("--env",Yp)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${Jp("the endpoint",e.endpoint,gk)}`),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 Qp(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{[Vp]:e.secret,...e.token?{[Yp]:e.token}:{}}}var Vp,Yp,gk,hk,em=W(()=>{"use strict";Vp="SNOUTPOD_S3_SECRET",Yp="SNOUTPOD_S3_TOKEN",gk=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,hk=/^[A-Za-z0-9/+=_.-]{1,256}$/});function rm(e){if(!ai.includes(e))throw new Error(`not a role kept out of the query statistics: ${e}`);return`alter role ${e} set pg_stat_statements.track = 'none';`}var si,tm,nm,ai,om=W(()=>{"use strict";si=["snout_gate","authenticator"],tm=`do $snoutpod$
373
405
  declare
374
406
  wanted text[];
375
407
  begin
@@ -387,7 +419,7 @@ begin
387
419
  end loop;
388
420
  end
389
421
  $snoutpod$;
390
- `,pp=`do $snoutpod$
422
+ `,nm=`do $snoutpod$
391
423
  begin
392
424
  if not exists (
393
425
  select from pg_catalog.pg_stat_sys_tables
@@ -398,8 +430,8 @@ begin
398
430
  end if;
399
431
  end
400
432
  $snoutpod$;
401
- `,$a=["snout_psyche_admin"]});function hp(e){let t=[];for(let n of e.split(`
402
- `)){let r=n.split(xa);if(r.length!==4||!Pa.includes(r[0]))continue;let[o,s,a,c]=r;t.push(c?{name:o,from:s,error:c}:{name:o,from:s,to:a})}return t}var Pa,xa,gp,yp=D(()=>{"use strict";Pa=["snouttime"];for(let e of Pa)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);xa="",gp=`
433
+ `,ai=["snout_psyche_admin"]});function am(e){let t=[];for(let n of e.split(`
434
+ `)){let r=n.split(li);if(r.length!==4||!ii.includes(r[0]))continue;let[o,s,a,c]=r;t.push(c?{name:o,from:s,error:c}:{name:o,from:s,to:a})}return t}var ii,li,sm,im=W(()=>{"use strict";ii=["snouttime"];for(let e of ii)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);li="",sm=`
403
435
  SET search_path = pg_catalog, pg_temp;
404
436
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
405
437
  DO $snoutpod$
@@ -413,7 +445,7 @@ BEGIN
413
445
  SELECT e.extname, e.extversion
414
446
  FROM pg_extension e
415
447
  JOIN pg_available_extensions a ON a.name = e.extname
416
- WHERE e.extname = ANY (ARRAY[${Pa.map(e=>`'${e}'`).join(", ")}])
448
+ WHERE e.extname = ANY (ARRAY[${ii.map(e=>`'${e}'`).join(", ")}])
417
449
  AND a.default_version IS DISTINCT FROM e.extversion
418
450
  LOOP
419
451
  BEGIN
@@ -444,121 +476,131 @@ BEGIN
444
476
  END
445
477
  $snoutpod$;
446
478
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
447
- `});function bp(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 wp,ZE,QE,Sp=D(()=>{"use strict";On();ao();wp="SNOUTPOD_OAUTH_ISSUER",ZE=256*1024,QE=5*6e4});function Qt(e){return`${B(e).pod}-guard`}function _p(e,t,n){if(!Number.isInteger(t)||t<1||t>65535)throw new Error(`not a port: ${t}`);let r=e.leaseMs??1e4;return{LEPIS_GUARD_WITNESS:e.witness,...e.witnessSslmode?{LEPIS_GUARD_WITNESS_SSLMODE:e.witnessSslmode}:{},LEPIS_GUARD_CLUSTER:e.cluster,LEPIS_GUARD_TOKEN:e.token,LEPIS_GUARD_COPY:e.copy,LEPIS_GUARD_PEER:e.peer,LEPIS_GUARD_PG:`127.0.0.1:${t}`,LEPIS_GUARD_PG_USER:"snoutpod_admin",LEPIS_GUARD_PG_PASSWORD:n,LEPIS_GUARD_STATE:`${Ea}/epoch`,LEPIS_GUARD_STATUS:`${Ea}/status.json`,LEPIS_GUARD_LEASE_MS:String(r),LEPIS_GUARD_TICK_MS:String(Math.max(1,Math.min(1e3,Math.floor(r/5))))}}function kp(e,t,n){return[e.image,e.witness,e.witnessSslmode??"",e.cluster,e.copy,e.peer,e.leaseMs??1e4,t,n].join("|")}function vp(e,t,n){if(ae(e),!n.hostDir.startsWith("/")||n.hostDir.includes(":"))throw new Error(`not a host directory for a volume: ${n.hostDir}`);return["run","--detach","--name",Qt(e),"--label",`${io}=${e}`,"--label",`${Aa}=${n.fingerprint}`,"--network","host","--userns","auto","--volume",`${n.hostDir}:${Ea}:U`,"--memory","64m","--pids-limit","64","--restart","no",...n.envNames.flatMap(r=>["--env",r]),t.image,"guard"]}function $p(e,t,n,r){let o=2*(r.leaseMs??1e4)/1e3;return n-e<=o+60?!1:t===null||n-t>o}var Ea,io,Aa,Pp=D(()=>{"use strict";Te();Ea="/guard",io="snoutpod.guard",Aa="snoutpod.guard-spec"});function Ap(e){return ae(e),{anchor:`snoutpod-${e}-copy`,router:`snoutpod-${e}-copy-lepis`,service:t=>`snoutpod-${e}-copy-${en(t)}`}}var xp,Ep,Rp=D(()=>{"use strict";Te();ao();xp="snoutpod.copy",Ep="snoutpod.copy-service"});import{createHash as cS}from"node:crypto";function et(e){return`${B(ae(e)).pod}-${lo}`}function Np(e){return Qe(e)?(e.services??[]).filter(t=>t.name!==ge):[]}function Ot(e,t){return Qe(e)&&t.name!==ge}function Ip(e){let t=e.stackPodPort;if(typeof t!="number"||!Number.isInteger(t)||t<1||t>65535)throw new Error(`${e.ref}: a services-only pod needs its own pod port (stackPodPort), and has ${JSON.stringify(t??null)}`);return t}function Ta(e,t){if(t.hostPort===null||t.hostPort===void 0)return null;let n=Ip(e);if(typeof e.port!="number"||!Number.isInteger(e.port)||e.port<1)throw new Error(`${e.ref}: ${t.name}'s slot is read off the pod port, and the spec has none`);let r=t.hostPort-e.port;if(r<1e3||r%1e3!==0)throw new Error(`${e.ref}: ${t.name}'s hostPort ${t.hostPort} is on no slot of pod port ${e.port}`);let o=n+r;if(o>65535)throw new Error(`${e.ref}: stackPodPort ${n} puts ${t.name} past 65535`);return o}function Oa(e,t){let n=Ta(e,t),r=Cp(e,t);return n===null?t:{...t,hostPort:n,...r===null?{}:{adminHostPort:r}}}function Cp(e,t){return!t.adminPort||!t.adminHostPort?null:Ta(e,{name:`${t.name} admin`,hostPort:t.adminHostPort})}function Na(e){return Np(e).flatMap(t=>{let n=Cp(e,t);return n===null||!t.adminPort?[]:[[n,t.adminPort]]}).sort((t,n)=>t[0]-n[0])}function Ia(e){let t=Np(e);return t.length>0&&Ip(e),t.flatMap(n=>{let r=Ta(e,n);return r===null?[]:[[r,n.port]]}).sort((n,r)=>n[0]-r[0])}function Dp(e,t){let n=Na(e);return cS("sha256").update(`stack:${t}:`).update(JSON.stringify(Ia(e))).update(n.length===0?"":`admin:${JSON.stringify(n)}`).digest("hex").slice(0,32)}var lt,Ra,lo,Tp,Op,Mp=D(()=>{"use strict";Te();ro();lt="snoutpod.stack",Ra="snoutpod.stack-spec",lo="stack",Tp=16,Op=16});import{createHash as mo,randomBytes as uS}from"node:crypto";import{access as pS,constants as mS,mkdir as fS}from"node:fs/promises";import{delimiter as gS,join as ja}from"node:path";function Vp(e,t,n,r){return mo("sha256").update(La(e)).update(e.smtpSecretEnv&&t?mo("sha256").update(t).digest("hex"):"").update(n===void 0?"":`router:${n}`).update(r===void 0?"":`@${r}`).digest("hex").slice(0,32)}function hS(e,t){return ya(t),mo("sha256").update(`separated:${t}:${Xt}:`).update(Vp(e)).digest("hex").slice(0,32)}function La(e){if(Array.isArray(e))return`[${e.map(La).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)}:${La(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function en(e){if(!yS.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function It(e){if(!wS.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function bS(e,t,n,r,o){if(r===void 0)return`postgres://${It(t)}:${n}@127.0.0.1:5432/${ae(e)}`;if(o!==void 0&&o!==kt(e))throw new Error(`${o} is not ${e}'s router`);return`postgres://${`${It(t)}:${n}@${o??"127.0.0.1"}`}:${Ne(r,"router port")}/${ae(e)}?sslmode=disable`}function fo(e,t){if(!t.dbUriEnv||!t.dbUriRole||t.name===ge)return;let n=(e.services??[]).find(r=>r.name===ge);if(n)return va.includes(t.dbUriRole)?n.port:Qe(e)?Xt:void 0}function Ua(e,t){return Qe(e)&&fo(e,t)!==void 0?kt(e.ref):void 0}function Up(e,t,n,r){let o=wa(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]=bS(e.ref,s,a,fo(e,t),Ua(e,t))}if(t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),t.adminPortEnv&&t.adminPort){if(!Xp.test(t.adminPortEnv))throw new Error(`${t.name}: not an environment variable name: ${JSON.stringify(t.adminPortEnv)}`);o[t.adminPortEnv]=String(Ne(t.adminPort,`${t.name}: adminPort`))}return o}function Fp(e,t){return`${B(e).pod}-${en(t)}`}function co(e){return`${e}${Yp}`}function Bp(e){return typeof e=="string"&&e.endsWith(Yp)}function Da(e){let t=e.Names?.[0];return t===void 0?void 0:t.replace(/^\//,"")}function Nt(e){return`snoutpod-host-${en(e)}`}function SS(e,t){let n=Object.keys(e.env).sort().map(o=>[o,e.env[o]]),r=t&&(t.joins||(t.publishes?.length??0)>0)?[{joins:t.joins??null,publishes:t.publishes??[]}]:[];return mo("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,n,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null,...r])).digest("hex").slice(0,32)}function _S(e){return[...e.filter(t=>!t.joins),...e.filter(t=>t.joins)]}function kS(e){return Object.keys(e.secretEnv??{}).length>0||Object.keys(e.files??{}).length>0||!!e.envFrom||!!e.databaseUrl}function vS(e,t){return t.filter(n=>n.joins===e&&n.hostPort>0).map(n=>[n.hostPort,n.port]).sort((n,r)=>n[0]-r[0])}function Ba(e){return!e.adminPort||!e.adminHostPort?null:[Ne(e.adminHostPort,`${e.name}: adminHostPort`),Ne(e.adminPort,`${e.name}: adminPort`)]}function $S(e,t){let n=[],r=nn(t);for(let o of e??[]){if(en(o.name),Ne(o.port,`${o.name}: port`),o.hostPort===null||o.hostPort===void 0)continue;Ne(o.hostPort,`${o.name}: hostPort`);for(let a of r)n.push("--publish",`${a}:${o.hostPort}:${o.port}`);let s=Ba(o);if(s&&n.push("--publish",`127.0.0.1:${s[0]}:${s[1]}`),o.name===ge){let a=o.hostPort+to;Ne(a,`${o.name}: admin hostPort`);for(let c of r)n.push("--publish",`${c}:${a}:${eo}`)}}return n}function PS(e,t,n){if(ae(e.ref),t.name!==ge)throw new Error(`${t.name} is not the router`);if(!n.network)throw new Error(`${e.ref}: a separated router needs the pod network`);Ne(t.port,`${t.name}: port`);let r=ya(e.routerPodPort),o=([s,a])=>nn(n.bind).flatMap(c=>["--publish",`${c}:${Ne(s,"router hostPort")}:${a}`]);return["run","--detach","--name",kt(e.ref),"--label",`${_t}=${e.ref}`,"--label",`${Nn}=${e.servicesGeneration??0}`,"--label",`${po}=${n.fingerprint}`,"--network",n.network,"--userns=auto","--memory",`${t.memoryMb}m`,"--pids-limit",String(em),"--restart","no",...o([r.postgres,t.port]),...o([r.admin,eo]),...o([r.passthrough,Xt]),...Zp(t),"--volume",`${Tn(e.ref)}:${no}:ro`,...n.envNames.flatMap(s=>["--env",s]),t.image]}function xS(e,t){if(ae(e.ref),!t.network)throw new Error(`${e.ref}: a services-only pod needs the pod network`);return["run","--detach","--name",et(e.ref),"--label",`${lt}=${e.ref}`,"--label",`${Ra}=${t.fingerprint}`,"--network",t.network,"--userns=auto","--memory",`${Tp}m`,"--pids-limit",String(Op),"--restart","no","--stop-signal","SIGKILL",...Ia(e).flatMap(([n,r])=>nn(t.bind).flatMap(o=>["--publish",`${o}:${Ne(n,"stack hostPort")}:${Ne(r,"service port")}`])),...Na(e).flatMap(([n,r])=>["--publish",`127.0.0.1:${Ne(n,"stack adminHostPort")}:${Ne(r,"admin port")}`]),"--entrypoint","sleep",e.image,"infinity"]}function ES(e){let t;try{t=JSON.parse(e.trim()||"null")}catch{return null}if(t===null)return[];if(typeof t!="object"||Array.isArray(t))return null;let n=[];for(let r of Object.values(t))if(Array.isArray(r))for(let o of r){let s=Number(o?.HostPort);Number.isInteger(s)&&s>0&&!n.includes(s)&&n.push(s)}return n}function nn(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 Ne(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function uo(e){let t=[];for(let n of Object.keys(e)){if(!Xp.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Zp(e,t={}){if(t.adminPublished&&e.adminHealthPath&&Ba(e)){if(!Fa.test(e.adminHealthPath))throw new Error(`${e.name}: not a health path: ${JSON.stringify(e.adminHealthPath)}`);return["--label",`${go}=${e.adminHostPort}:${e.adminHealthPath}`]}let n=e.healthPath;if(n==null||n==="")return[];if(!Fa.test(n))throw new Error(`${e.name}: not a health path: ${JSON.stringify(n)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Ne(e.hostPort,`${e.name}: hostPort`),["--label",`${go}=${e.hostPort}:${n}`])}function AS(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||!Fa.test(r)?null:{port:n,path:r}}async function TS(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>pS(o,mS.X_OK));for(let o of n.split(gS))if(o)try{let s=ja(o,e);return await r(s),s}catch{}return null}function OS(){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(`
448
- `)}function CS(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function DS(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 Ha(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function MS(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 jS(e,t,n,r){return{image:await LS(e,t,r),data:await US(e,t,n)}}async function LS(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 DS(r,n)}async function US(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",B(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?Ha(s.stdout):null}catch{return null}}function FS(e){return e.filter(t=>!!t.replicaPeer||t.guard!==void 0||t.hostRole==="standby").map(t=>({ref:t.ref,group:t.group??t.ref,container:B(t.ref).pod})).sort((t,n)=>t.ref<n.ref?-1:t.ref>n.ref?1:0)}function BS(e,t,n){if(t.size>1)return`the ${Pe} network already holds more than one project (${[...t].sort().join(", ")}), so nothing more joins it`;let r=t.size===1?[...t][0]:n;return e.group===r?null:`the ${Pe} network holds ${r}'s copies, and another project may not share its bridge`}function Gp(e,t){let n=nn(t);return(e.Ports??[]).some(r=>{let o=r.host_ip??"";return o!==""&&o!=="0.0.0.0"&&o!=="::"&&!n.includes(o)})}function WS(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:qp;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,qp)}`),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 zS(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??RS;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await TS(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function tm(e){let t=e.binary??"podman",n=e.exec,r=zS(e),o=e.probe??Qp;async function s(p){try{await n(t,["exec",...ve,"--interactive",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:IS,stdin:OS()})}catch{}}async function a(){let[p,f,y]=await Promise.all([N(n,t,["ps","--all","--filter",`label=${Oe}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${_t}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${lt}`,"--format","json"])]),S=new Map;for(let H of y.trim()?JSON.parse(y):[]){let Q=h(H);Q&&S.set(Q,(S.get(Q)??!1)||H.State==="running")}let _=[...(p.trim()?JSON.parse(p):[]).flatMap(H=>{let Q=H.Labels?.[Ue]?H.Labels?.[lt]:void 0;return Q===void 0?[H]:S.has(Q)?[S.get(Q)?H:{...H,State:"exited"}]:[]}),...(f.trim()?JSON.parse(f):[]).flatMap(H=>{let Q=H.Labels?.[_t];return Q?[{...H,Labels:{...H.Labels,[Oe]:Q,[Ue]:ge}}]:[]})],v=new Map,K=new Map,k=new Map,U=new Map,I=new Set([...S.keys(),...(f.trim()?JSON.parse(f):[]).flatMap(H=>H.Labels?.[_t]??[])]);for(let H of _){let Q=H.Labels?.[Oe],fe=H.Labels?.[Ue];if(!Q||!fe)continue;if(Bp(Da(H))){U.set(Q,[...U.get(Q)??[],fe]);continue}let G=v.get(Q)??[];G.push(fe),v.set(Q,G),Ma(H.State)==="stopped"&&K.set(Q,[...K.get(Q)??[],fe]);let ue=Number(H.Labels?.[Nn]??0),me=Number.isInteger(ue)?ue:0;k.set(Q,Math.max(k.get(Q)??me,me))}let X=_.filter(H=>H.Labels?.[Oe]&&!H.Labels?.[Ue]),se=X.map(H=>Ma(H.State)),F=await VS(X,JS,(H,Q)=>se[Q]==="running"?Promise.resolve(!0):e.hasData(H.Labels?.[Oe]??""));return X.map((H,Q)=>{let fe=H.Labels?.[Oe]??"",G=Number(H.Labels?.[Ca]);return{ref:fe,status:se[Q]??"unknown",generation:Number.isInteger(G)?G:null,hasData:F[Q]??!1,services:v.get(fe)??[],servicesStopped:K.get(fe)??[],...I.has(fe)?{separated:!0}:{},...U.has(fe)?{setAside:U.get(fe)}:{},...Gp(H,e.podBind)?{boundElsewhere:!0}:{},...k.has(fe)?{servicesGeneration:k.get(fe)}:{}}})}async function c(){let p=await N(n,t,["ps","--all","--filter",`label=${Oe}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(y=>y.Labels?.[Oe]&&!y.Labels?.[Ue]).length}async function i(p){let f=await(e.majors??((y,S)=>jS(n,t,y,S)))(p.ref,p.image);return MS(p.ref,f)}async function u(p){let f=B(p.ref),y=await i(p);if(y)throw new Error(y);let S=e.credentials,_=await ip(e.repository,S?()=>S.forProject(p.ref,p.bucket):null),v={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...cp(_)},K=[...await r(),"run","--detach","--name",f.pod,"--label",`${Oe}=${p.ref}`,"--label",`${Ca}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,"--pids-limit",String(HS),...ZS(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?nn(e.podBind).flatMap(k=>["--publish",`${k}:${p.port}:5432`]):[],...$S(ha(p),e.podBind),"--volume",`${f.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${p.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${p.bucket}`,"--env",`SNOUTPOD_S3_REGION=${p.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...XS(p.oauth),...WS(p.tuning).flatMap(k=>["--env",k]),"--env",`SNOUTPOD_RETENTION_FULL=${nu(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...lp(_),p.image,"snoutpod-postgres"];await N(n,t,K,{env:v})}async function l(p){return(await x(p)).services}async function m(p){return(await l(p)).filter(f=>!f.setAside)}function h(p){return p.Labels?.[Ue]?void 0:p.Labels?.[lt]}async function x(p){let[f,y,S]=await Promise.all([N(n,t,["ps","--all","--filter",`label=${Oe}=${ae(p)}`,"--filter",`label=${Ue}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${_t}=${p}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${lt}=${p}`,"--format","json"])]),_=f.trim()?JSON.parse(f):[],v=[];for(let k of _){let U=k.Labels?.[Ue];if(!U)continue;let I=Number(k.Labels?.[Nn]??0),X=Bp(Da(k));v.push({name:U,container:X?Da(k)??"":Fp(p,U),spec:k.Labels?.[po]??"",pod:k.Labels?.[jp]??"",running:k.State==="running",generation:Number.isInteger(I)?I:0,separate:!1,stack:k.Labels?.[lt]===p,setAside:X})}for(let k of y.trim()?JSON.parse(y):[]){if(k.Labels?.[_t]!==p)continue;let U=Number(k.Labels?.[Nn]??0);v.push({name:ge,container:kt(p),spec:k.Labels?.[po]??"",pod:"",running:k.State==="running",generation:Number.isInteger(U)?U:0,separate:!0,stack:!1,setAside:!1})}let K=null;for(let k of S.trim()?JSON.parse(S):[])h(k)===p&&(K={id:k.Id??"",spec:k.Labels?.[Ra]??"",running:k.State==="running"});return{services:v,anchor:K}}async function R(p){let f=await n(t,["container","inspect","--format","{{.Id}}",p]);return f.code===0?f.stdout.trim():""}function ee(p,f){return f?.databaseOnly?{services:p.services.filter(y=>!y.separate&&!y.stack),anchor:null}:p}async function z(p,f){let y=[];for(let I of f){let X=I.dbUriRole??null;I.dbUriEnv&&X&&!y.includes(X)&&y.push(It(X))}let S=new Map;if(y.length===0)return S;let _=await e.fixedRolePasswords?.()??new Map,v={},K=[],k=[];y.some(I=>va.includes(I))&&k.push(up,pp);for(let I of y.filter(X=>$a.includes(X)))k.push(mp(I));let U=new Map;if(p.derivedServicePasswords===!0)for(let I of y.filter(ba))U.set(I,await Sa(p.adminPassword,I));return y.forEach((I,X)=>{let se=_.get(I);if(se!==void 0){S.set(I,se);return}let F=U.get(I)??uS(24).toString("base64url"),H=`SNOUTPOD_SERVICE_PASSWORD_${X}`;S.set(I,F),v[H]=F,K.push("--env",H),k.push(`\\getenv service_password_${X} ${H}`),k.push(`\\set service_role_${X} ${I}`),k.push(`alter role :"service_role_${X}" with password :'service_password_${X}';`)}),k.push(""),await N(n,t,["exec",...ve,"--interactive",...K,B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",B(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:v,stdin:k.join(`
449
- `)}),S}async function ce(p){let f=Tn(p.ref);await N(n,t,["volume","create","--ignore",f]),await N(n,t,[...await r(),"run","--rm","--network","none","--volume",`${f}:${no}`,"--entrypoint","bash",p.image,"-c",Uu])}async function C(p){let f=await r();if((await n(t,[...f,"image","exists",p])).code===0)return;let y=await n(t,[...f,"pull","--quiet",p]);if(y.code===0)return;let S=CS(p);if(!S||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(y.stderr||y.stdout).trim()}`);await e.registryLogin(S),await N(n,t,[...f,"pull","--quiet",p])}async function de(p){let f=await n(t,["container","inspect","--format","{{json .HostConfig.PortBindings}}",B(p).pod]);return f.code===0?ES(f.stdout):null}async function L(p,f={}){let y=p.services??[],S=Qe(p)?[...y.filter($=>$.name===ge),...y.filter($=>$.name!==ge)]:y,{services:_,anchor:v}=await x(p.ref),K=_.filter($=>!$.setAside),k;S.some($=>$.smtpSecretEnv)&&(k=await e.smtpSecret?.().catch(()=>{}));let U=new Map(K.map($=>[$.name,$])),I=$=>Qe(p)&&$.name===ge,X=S.filter($=>Ot(p,$)),se="";if(X.length>0){if(!S.some($=>$.name===ge))throw new Error(`${p.ref}: a services-only pod reaches its database through the router, and spec.services has no ${ge}`);if(!e.network)throw new Error(`${p.ref}: a services-only pod needs the pod network`);if(X.some($=>$.name===lo))throw new Error(`${p.ref}: a service may not be called ${lo}, which is the services-only pod's own name`);se=Dp(p,e.network)}let F=X.length>0&&v!==null&&v.running&&v.id!==""&&v.spec===se,H=S.some($=>!I($)&&!Ot(p,$))?await R(B(p.ref).pod):"",Q=S.some($=>!I($)&&!Ot(p,$)&&Ba($))?new Set(await de(p.ref)??[]):new Set,fe=F?v?.id??"":"",G=$=>Ot(p,$)?fe:H,ue=new Map(S.map($=>[$.name,I($)?hS($,p.routerPodPort):Vp(Ot(p,$)?Oa(p,$):$,k,fo(p,$),Ua(p,$))])),me=new Set(S.filter($=>{let Be=U.get($.name);if(!Be||!Be.running||Be.spec!==ue.get($.name))return!0;let He=G($);return!I($)&&(He===""||Be.pod!==He)}).map($=>$.name));if(f.renewPasswords)for(let $ of S)$.dbUriEnv&&$.dbUriRole&&me.add($.name);let To=p.servicesGeneration??0;if(me.size===0&&S.length>0&&!K.some($=>$.generation>=To))for(let $ of S)me.add($.name);let ai=$=>$.dbUriEnv&&$.dbUriRole?$.dbUriRole:null;for(let $=!0;$;){$=!1;let Be=new Set(S.filter(He=>me.has(He.name)).map(ai));for(let He of S){let Bn=ai(He);!me.has(He.name)&&Bn!==null&&Be.has(Bn)&&(me.add(He.name),$=!0)}}let ii=new Set(S.filter($=>me.has($.name)).map($=>$.image));(X.length>0&&!F||me.has(ge))&&ii.add(p.image);for(let $ of ii)await C($);let pf=$=>f.keepSeparated===!0&&($.separate||$.stack)&&!me.has($.name);for(let $ of K)(me.has($.name)||!ue.has($.name))&&!pf($)&&await N(n,t,["rm","--force","--ignore",$.container]);if(v!==null&&!F&&!(f.keepSeparated===!0&&X.length===0)&&await N(n,t,["rm","--force","--ignore",et(p.ref)]),X.length>0&&!F){if(await N(n,t,[...await r(),...xS(p,{network:e.network,bind:e.podBind,fingerprint:se})]),fe=await R(et(p.ref)),fe==="")throw new Error(`${p.ref}: the services-only pod was made and its id could not be read`);await f.afterStart?.(et(p.ref))}let Oo=S.filter($=>me.has($.name)),No=await z(p,Oo);Oo.some($=>$.name===ge)&&await ce(p);for(let $ of Oo){if(I($)){let li=wa(ju(p.ref,$.env),p.adminPassword);await N(n,t,[...await r(),...PS(p,$,{network:e.network,bind:e.podBind,fingerprint:ue.get($.name)??"",envNames:uo(li).filter(ff=>ff!=="--env")})],{env:li}),await f.afterStart?.(kt(p.ref));continue}let Be=Ot(p,$),He=Be?et(p.ref):B(p.ref).pod,Bn=Fp(p.ref,$.name),mf=[...await r(),"run","--detach","--name",Bn,"--label",`${Oe}=${p.ref}`,"--label",`${Ca}=${p.generation}`,"--label",`${Ue}=${$.name}`,"--label",`${Nn}=${p.servicesGeneration??0}`,"--label",`${po}=${ue.get($.name)??""}`,"--label",`${jp}=${G($)}`,...Be?["--label",`${lt}=${p.ref}`]:[],"--network",`container:${He}`,"--userns",`container:${He}`,"--memory",`${$.memoryMb}m`,"--pids-limit",String(em),"--restart","no",...Zp(Be?Oa(p,$):$,{adminPublished:Be||$.adminHostPort!==null&&$.adminHostPort!==void 0&&Q.has($.adminHostPort)}),...$.name===ge?["--volume",`${Tn(p.ref)}:${no}:ro`]:[],...uo(Up(p,$,No,k)),$.image];me.has(ge)||await _e(p,$,No),await N(n,t,mf,{env:Up(p,$,No,k)})}}async function _e(p,f,y){let S=fo(p,f),_=f.dbUriRole,v=_?y.get(_):void 0;if(S===void 0||S===Xt||!_||v===void 0)return;let K=B(p.ref),k=Ua(p,f)??"127.0.0.1",{waitMs:U,retryMs:I}=e.routerLogin??{waitMs:KS,retryMs:GS},X=Date.now()+U;for(let se=1;;se++){let F=await n(t,["exec","--env","PGPASSWORD","--env","PGSSLMODE=disable","--env","PGCONNECT_TIMEOUT=3",K.pod,"psql","--host",k,"--port",String(S),"--username",It(_),"--dbname",K.database,"--no-password","--tuples-only","--no-align","--command","select 1"],{env:{PGPASSWORD:v}});if(F.code===0)return;if(Date.now()>=X){let H=`${F.stderr}
450
- ${F.stdout}`.trim().split(`
451
- `).filter(Boolean).pop()??`exit ${F.code}`;throw new Error(`${p.ref}: the router did not take ${_}'s new password within ${U}ms (${se} tries; last: ${H}), so ${f.name} was not started`)}await new Promise(H=>setTimeout(H,I))}}async function Z(p,f){if(f%6!==0)return!1;let y=await n(t,["container","inspect","--format","{{.State.Status}}",B(p).pod]);return y.code===0&&/^(exited|stopped|dead)$/.test(y.stdout.trim())}function ne(p){return["exec",...ve,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres"]}async function Un(p){let f=await n(t,[...ne(p),"-tAc",wu]);return f.code===0?bu(f.stdout):null}async function xt(p){let f=await n(t,[...ne(p),"-tAc","select pg_is_in_recovery()"]);if(f.code!==0)return null;let y=f.stdout.trim();return y==="t"?!0:y==="f"?!1:null}async function Fn(p,f){let y=K=>n(t,[...ne(p),"-tAc",K]),S=async()=>{let K=await y("select case when pg_is_in_recovery() then 'r' when (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int then 't' else 'f' end"),k=K.stdout.trim();return K.code===0&&(k==="t"||k==="f"||k==="r")?k:null},_=await S();if(_!=="f")return _==="t";let v=Date.now()+f;do{if((await y("checkpoint")).code!==0)return!1;if(await S()==="t")return!0;await new Promise(k=>setTimeout(k,1e3))}while(Date.now()<v);return!1}async function zm(p){let f=await n(t,[...ne(p),"-tAc",ou],{timeoutMs:vt});return f.code===0?su(f.stdout):null}async function Jm(p,f){let y=new Map;for(let _ of f)ae(_),y.set(_,B(_).pod);if(y.size===0)return new Map;let S=await n(t,["exec",B(p).pod,"timeout",String(10),"sh","-c",hu,"sh",...y.values()]);return S.code===0?yu(S.stdout,y):null}async function Vm(p,f){ae(f);let y=[];for(let K of[p,f]){let k=B(K).pod,U=await n(t,["container","inspect","--format","{{.State.Status}}",k]);if(U.code===0&&U.stdout.trim()==="running")throw new Error(`${k} is still running, so its data was not moved`);let I=await n(t,["volume","inspect","--format","{{.Mountpoint}}",k]),X=I.code===0?I.stdout.trim().replace(/\/+$/,""):"";if(!X)throw new Error(`the volume ${k} could not be found, so nothing was moved`);y.push(X)}let _=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",oa,"sh",y[0],y[1]]):await n("sh",["-c",oa,"sh",y[0],y[1]]),v=du(_.stdout,_.code);if(v)throw new Error(v)}async function Ym(p){return(await n(t,[...ne(p),"-v","ON_ERROR_STOP=1",...sa.flatMap(y=>["-c",y])])).code===0}async function Xm(p){let f=e.guardRoot;if(!f)return;let y=await N(n,t,["ps","--all","--filter",`label=${io}`,"--format","json"]),S=new Map((y.trim()?JSON.parse(y):[]).flatMap(v=>{let K=v.Labels?.[io];return K?[[K,{fingerprint:v.Labels?.[Aa]??"",status:Ma(v.State),startedAt:v.StartedAt??0}]]:[]})),_=new Set(p.map(({spec:v})=>v.ref));for(let v of S.keys())_.has(v)||await N(n,t,["rm","--force","--ignore",Qt(v)]);for(let{spec:v,guard:K}of p){if(v.port===null)continue;let k=kp(K,v.port,v.generation),U=S.get(v.ref);if(U?.fingerprint===k){if(U.status!=="running")await N(n,t,["start",Qt(v.ref)]);else{let se=ja(f,v.ref,"status.json"),F=await n(t,["unshare","stat","-c","%Y",se]),H=F.code===0?Number(F.stdout.trim()):null;$p(U.startedAt,Number.isFinite(H)?H:null,Math.floor(Date.now()/1e3),K)&&await N(n,t,["restart",Qt(v.ref)])}continue}await N(n,t,["rm","--force","--ignore",Qt(v.ref)]);let I=ja(f,v.ref);await fS(I,{recursive:!0,mode:448}),await C(K.image);let X=_p(K,v.port,v.adminPassword);await N(n,t,[...await r(),...vp(v.ref,K,{hostDir:I,fingerprint:k,envNames:uo(X).filter(se=>se!=="--env")})],{env:X})}}async function Zm(p,f){return(await n(t,[...ne(p),"-v","ON_ERROR_STOP=1",...iu(f).flatMap(S=>["-c",S])],{timeoutMs:vt})).code===0}async function Qm(){let p=e.network;if(!p)return null;let f=(await N(n,t,["network","inspect",p,"--format","{{range .Subnets}}{{.Subnet}} {{end}}"])).trim().split(" ")[0]??"";if(f==="")return null;let y=await N(n,t,["ps","--format","json"]),_=(y.trim()?JSON.parse(y):[]).map(k=>{let U=k.Labels?.[Oe],I=k.Labels?.[tn],X=k.Labels?.[_t];if(X)return{name:kt(X),ref:X,service:!1};let se=k.Labels?.[lt];if(se&&!k.Labels?.[Ue])return{name:et(se),ref:se,service:!1};let F=k.Labels?.[xp],H=k.Labels?.[Ep];if(F&&(!H||H===ge)){let Q=Ap(F);return{name:H?Q.router:Q.anchor,ref:F,service:!1}}return U&&!k.Labels?.[Ue]?{name:B(U).pod,ref:U,service:!1}:I?{name:Nt(I),ref:I,service:!0}:null}).filter(k=>k!==null);if(_.length===0)return{subnet:f,containers:[]};let v=await N(n,t,["inspect","--format",`{{.Name}} {{with index .NetworkSettings.Networks "${p}"}}{{.IPAddress}}{{end}}`,..._.map(k=>k.name)]),K=new Map(v.split(`
452
- `).map(k=>k.trim().split(" ")).filter(k=>k.length===2&&k[1]!=="").map(([k,U])=>[k,U]));return{subnet:f,containers:_.flatMap(k=>K.has(k.name)?[{ref:k.ref,ip:K.get(k.name),service:k.service}]:[])}}async function ef(p,f){if(!e.quotaRequests||!Number.isInteger(f)||f<1)return!1;let{writeFile:y,rename:S}=await import("node:fs/promises"),_=B(p).pod,v=`${e.quotaRequests}/${_}`;return await y(`${e.quotaRequests}/.${_}.tmp`,`${f}
453
- `),await S(`${e.quotaRequests}/.${_}.tmp`,v),!0}async function ri(p,f,y){if(!Number.isInteger(y)||y<1)return!1;let S=await n(t,[...ne(p),"-tAc",`select setting::bigint * pg_size_bytes(case when unit ~ '^[0-9]' then unit else '1' || unit end) / 1048576 from pg_settings where name = '${f}'`]);return S.code===0&&S.stdout.trim()===String(y)?!0:(await n(t,[...ne(p),"-v","ON_ERROR_STOP=1","-c",`alter system set ${f} = '${y}MB'`,"-c","select pg_reload_conf()"],{timeoutMs:vt})).code===0}function tf(p,f){return ri(p,"temp_file_limit",f)}function nf(p,f){return ri(p,"max_wal_size",f)}async function rf(p){return(await n(t,["unshare","--rootless-netns",NS,"-f","-"],{stdin:p,timeoutMs:vt})).code===0}let Eo=null,Ao=null,Ro=new Map;async function of(p,f){let y=p.join(",");if(Eo===y)return!0;let S=await n(t,["network","inspect",Pe]);if(S.code===0&&Ou(S.stdout,p))return Eo=y,f("network",null),!0;if(S.code===0){let v=(await N(n,t,["ps","--all","--filter",`network=${Pe}`,"--format","{{.Names}}"])).trim();if(v!=="")return f("network",`WARNING: the ${Pe} network does not route exactly to ${p.join(", ")}, and ${v.split(`
454
- `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`),!1;await N(n,t,["network","rm",Pe])}let _=await n(t,Tu(p));return _.code!==0?(f("network",`WARNING: the ${Pe} network could not be made: ${(_.stderr||_.stdout).trim().split(`
455
- `)[0]}`),!1):(Eo=y,f("network",null),f("made",null),f("made",`made the ${Pe} network, routing only to ${p.join(", ")}`),!0)}async function sf(p){let f=[...new Set(e.peers??[])].sort(),y=FS(p);if(f.length===0||y.length===0)return[];let S=[],_=(G,ue)=>{ue===null?Ro.delete(G):Ro.get(G)!==ue&&(Ro.set(G,ue),S.push(ue))},v=await N(n,t,["ps","--format","json"]),K=new Set((v.trim()?JSON.parse(v):[]).flatMap(G=>G.Names??[])),k=y.filter(G=>K.has(G.container));if(k.length===0)return S;let U=f.join(",");if(Ao?.key!==U){let G=e.network?await n(t,["network","inspect",e.network]):null,ue=G?.code===0?Nu(G.stdout,f):[];Ao={key:U,peers:f.filter(me=>!ue.includes(me))}}let I=Ao.peers;if(I.length===0||!await of(I,_))return S;let X=await N(n,t,["inspect","--format","{{.Name}} {{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",...k.map(G=>G.container)]),se=new Set(X.split(`
456
- `).map(G=>G.trim().split(/\s+/)).filter(G=>G.slice(1).includes(Pe)).map(G=>G[0])),F=k.filter(G=>!se.has(G.container));if(F.length===0)return S;let H=new Map(p.map(G=>[G.ref,G.group??G.ref])),Q=await N(n,t,["ps","--all","--filter",`network=${Pe}`,"--format","json"]),fe=new Set((Q.trim()?JSON.parse(Q):[]).map(G=>G.Labels?.[Oe]).filter(G=>!!G).map(G=>H.get(G)??G));for(let G of F){let ue=BS(G,fe,F[0].group);if(ue){_(G.container,`${G.container} does not join ${Pe}: ${ue}`);continue}let me=await n(t,["network","connect",Pe,G.container]);if(me.code!==0){_(G.container,`WARNING: ${G.container} could not join the ${Pe} network, so it has no route to ${I.join(", ")}: `+(me.stderr||me.stdout).trim().split(`
457
- `)[0]);continue}fe.add(G.group),_(G.container,null),S.push(`${G.container} joined the ${Pe} network: its route to ${I.join(", ")} comes from there`)}return S}async function af(p,f){let y=await n(t,[...ne(p),"-tA","-v","ON_ERROR_STOP=1",...au(f).flatMap(_=>["-c",_])],{timeoutMs:vt}),S=Number(y.stdout.trim().split(`
458
- `).pop());return y.code===0&&Number.isInteger(S)?S:null}async function lf(p){let f=await n(t,[...ne(p),"-tAc","select pg_read_file(current_setting('ssl_cert_file')) where current_setting('ssl') = 'on'"]),y=`${f.stdout.trim()}
459
- `;return f.code===0&&Wr.test(y)?y:null}async function cf(p,f){if(!ra.test(f))throw new Error("not a SCRAM-SHA-256 verifier");return oi(p,{statements:[`do $$ begin if not exists (select 1 from pg_roles where rolname = '${St}') then create role ${St} with login replication; end if; end $$`,`alter role ${St} with login replication noinherit password '${f}'`],role:St,line:`hostssl replication ${St} all scram-sha-256`,replaceLine:!1})}async function df(p,f,y){return oi(p,{statements:cu(f),role:Yt,line:lu(y),replaceLine:!0})}async function oi(p,f){let y=B(p).pod,S=[...ne(p),"-v","ON_ERROR_STOP=1"];if((await n(t,[...S,...f.statements.flatMap(I=>["-c",I])],{timeoutMs:vt})).code!==0)return!1;let v=await n(t,[...S,"-tAc","show hba_file"]),K=v.stdout.trim();return v.code!==0||!/^\/[A-Za-z0-9/_.-]+$/.test(K)||(await n(t,["exec",y,"sh","-c",f.replaceLine?'{ grep -Ev "^host(ssl)?[[:space:]]+replication[[:space:]]+$2[[:space:]]" "$1"; printf "%s\\n" "$3"; } > "$1.next" && cat "$1.next" > "$1" && rm -f "$1.next"':'grep -Eq "^host(ssl)?[[:space:]]+replication[[:space:]]+$2[[:space:]]" "$1" || printf "%s\\n" "$3" >> "$1"',"sh",K,f.role,f.line])).code!==0||!await si(p,K.replace(/\/[^/]+$/,""))?!1:(await n(t,[...S,"-tAc","select pg_reload_conf()"])).code===0}async function si(p,f){let y=B(p).pod,S=[...ne(p),"-v","ON_ERROR_STOP=1"],_=await n(t,[...S,"-tAc","show ssl"]);return _.code===0&&_.stdout.trim()!=="on"&&((await n(t,["exec","--user","root",y,"sh","-c",'[ -s "$1/server.key" ] || { openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj "/CN=$2" -keyout "$1/server.key" -out "$1/server.crt" >/dev/null 2>&1 && chown postgres:postgres "$1/server.key" "$1/server.crt" && chmod 0600 "$1/server.key"; }',"sh",f,y])).code!==0||(await n(t,[...S,"-c","alter system set ssl_cert_file = 'server.crt'","-c","alter system set ssl_key_file = 'server.key'","-c","alter system set ssl = on"])).code!==0)?!1:_.code===0}async function uf(p,f){if(!Wr.test(f.certificate))throw new Error("the primary's certificate is not one PEM certificate");let y=B(p).pod;for(let[S,_]of[[uu,mu(f.password)],[pu,f.certificate]]){let v=await n(t,["exec","--interactive","--user","postgres",y,"sh","-c",'umask 077 && mkdir -p "$(dirname "$1")" && cat > "$1.next" && mv -f "$1.next" "$1"',"sh",S],{stdin:_,timeoutMs:vt});if(v.code!==0)throw new Error(`${S} could not be written in ${y}: ${(v.stderr||v.stdout).trim().slice(0,200)}`)}}return{list:a,ensureImage:C,inRecovery:xt,replayLsn:Un,settleTimeline:Fn,podHealth:zm,setSyncNames:Zm,...e.guardRoot?{syncGuards:Xm}:{},dropKeeperSlots:af,bridgeContainers:Qm,applyPodIsolation:rf,joinPeerNetwork:sf,requestDiskQuota:ef,ensureTempFileLimit:tf,ensureMaxWalSize:nf,ensureKeeperRole:cf,ensureReplicaRole:df,writeFollowFiles:uf,podCertificate:lf,swapHomeData:Vm,promoteStandby:Ym,nodesAnswer:Jm,preflight:i,create:u,createServices:L,async start(p){let f=(await l(p).catch(()=>[])).filter(y=>y.running&&!y.separate&&!y.stack).map(y=>y.container);f.length>0&&await N(n,t,["stop","--time","5",...f]),await N(n,t,["start",B(p).pod])},async startServices(p){let{services:f,anchor:y}=await x(p);if(y!==null&&!y.running){let _=f.filter(v=>v.stack&&v.running).map(v=>v.container);_.length>0&&await N(n,t,["stop","--time","5",..._]),await N(n,t,["start",et(p)])}let S=f.filter(_=>!_.stack||y!==null).map(_=>_.container);S.length>0&&await N(n,t,["start",...S])},async publishedPorts(p){return de(p)},async setAsideSidecars(p){let f=(await l(p)).filter(y=>!y.separate&&!y.stack&&!y.setAside);for(let y of f)await N(n,t,["rm","--force","--ignore",co(y.container)]),await N(n,t,["rename",y.container,co(y.container)]);return f.map(y=>y.name)},async setAsideStackServices(p){let f=(await l(p)).filter(y=>y.stack&&!y.setAside);for(let y of f)await N(n,t,["rm","--force","--ignore",co(y.container)]),await N(n,t,["rename",y.container,co(y.container)]);return f.map(y=>y.name)},async dropSetAside(p){let f=(await l(p)).filter(y=>y.setAside).map(y=>y.container);f.length>0&&await N(n,t,["rm","--force","--ignore",...f])},async routerAnswers(p,f){let y=B(p),S=Date.now()+f;for(;;){if((await n(t,["exec",y.pod,"pg_isready","--host",kt(p),"--port",String(ga),"--dbname",y.database,"--timeout","2"])).code===0)return!0;if(Date.now()>=S)return!1;await new Promise(v=>setTimeout(v,500))}},async applyHostServices(p){let f=[],y=await N(n,t,["ps","--all","--filter",`label=${tn}`,"--format","json"]),S=y.trim()?JSON.parse(y):[],_=new Map;for(let k of S){let U=k.Labels?.[tn];U&&_.set(U,{spec:k.Labels?.[Hp]??"",state:k.State??"",elsewhere:Gp(k,e.podBind),joins:k.Labels?.[Kp]||null})}let v=new Set;for(let k of _S(p)){let U=_.get(k.name);_.delete(k.name);try{await K(k,U)}catch(I){f.push({name:k.name,action:"failed",reason:I instanceof Error?I.message:String(I)})}}async function K(k,U){let I=k.joins?en(k.joins):null;if(I!==null&&!p.some(ue=>ue.name===I&&!ue.joins))throw new Error(`it joins ${I}, which this host does not run`);let X=e.composeHostService?await e.composeHostService(k):k;if(kS(X))throw new Error("it names host secrets, and this host cannot resolve them");let se=e.hostServiceLocal?.(X,p),F=se?{...X,...se.env&&Object.keys(se.env).length>0?{env:{...X.env,...se.env}}:{},...se.volumes?{volumes:se.volumes}:{},...se.network?{network:se.network}:{}}:X,H=Nt(F.name),Q=I===null?vS(F.name,p):[],fe=SS(F,{joins:I,publishes:Q});if(U&&U.spec===fe&&!(U.state!=="running"&&U.elsewhere)){U.state!=="running"?(await N(n,t,["start",H]),f.push({name:F.name,action:"started",reason:U.state}),v.add(F.name)):I!==null&&v.has(I)&&(await N(n,t,["restart",H]),f.push({name:F.name,action:"started",reason:`${I}, whose network it shares, was started again`}));return}let G=U?U.spec===fe?"published on another instance's address":"the spec changed":"it was not here";if(await C(F.image),U){for(let[ue,me]of _)me.joins===F.name&&(await N(n,t,["rm","--force","--ignore",Nt(ue)]),_.delete(ue));await N(n,t,["rm","--force","--ignore",H])}await N(n,t,[...await r(),"run","--detach","--name",H,"--label",`${tn}=${F.name}`,"--label",`${Hp}=${fe}`,...I!==null?["--label",`${Kp}=${I}`]:[],...F.healthPath&&F.hostPort>0?["--label",`${go}=${F.hostPort}:${F.healthPath}`]:[],"--memory",`${F.memoryMb}m`,"--pids-limit",String(qS),"--restart","no",...I!==null?["--network",`container:${Nt(I)}`]:F.network?["--network",F.network]:e.network?["--network",e.network]:[],...(F.volumes??[]).flatMap(ue=>["--volume",ue]),...I===null?[...F.hostPort>0?[[F.hostPort,F.port]]:[],...Q].flatMap(([ue,me])=>nn(e.podBind).flatMap(To=>["--publish",`${To}:${ue}:${me}`])):[],...F.adminPort&&F.adminHostPort?["--publish",`127.0.0.1:${F.adminHostPort}:${F.adminPort}`]:[],...uo(F.env),F.image,...F.command??[]],{env:{...F.env}}),f.push({name:F.name,action:U?"recreated":"created",reason:G}),I===null&&v.add(F.name)}for(let k of[..._.keys()].sort((U,I)=>+!_.get(U)?.joins-+!_.get(I)?.joins))await N(n,t,["rm","--force","--ignore",Nt(k)]),f.push({name:k,action:"removed",reason:"it is no longer on the list"});return f},async hostServiceHealth(p){let f=await N(n,t,["ps","--all","--filter",`label=${tn}`,"--format","json"]),y=(f.trim()?JSON.parse(f):[]).map(_=>_.Labels?.[tn]??"").filter(_=>_!=="");if(y.length===0)return[];let S=Date.now()+p;for(;;){let _=[];for(let v of y)_.push(await Wp(n,t,v,Nt(v),o));if(_.every(v=>v.ok)||Date.now()>=S)return _;await new Promise(v=>setTimeout(v,500))}},async serviceHealth(p,f){let y=await m(p);if(y.length===0)return[];let S=Date.now()+f;for(;;){let _=await Promise.all(y.map(v=>Wp(n,t,v.name,v.container,o)));if(_.every(v=>v.ok)||Date.now()>=S)return _;await new Promise(v=>setTimeout(v,500))}},async drain(p,f,y){await new Promise(_=>setTimeout(_,f)),await n(t,[...ne(p),"-c","checkpoint"],{timeoutMs:vt});let S=Date.now()+y;for(;;){let _=await n(t,[...ne(p),"-tAc","select count(*) filter (where state <> 'idle') from (select state, case when state = 'idle' then pg_terminate_backend(pid) end from pg_stat_activity where backend_type = 'client backend' and pid <> pg_backend_pid()) s"]),v=_.code===0&&Number(_.stdout.trim())||0;if(v===0||Date.now()>=S)return{stillRunning:v};await new Promise(K=>setTimeout(K,250))}},async stop(p,f,y){let{services:S,anchor:_}=ee(await x(p),y);for(let v of S)await N(n,t,["stop","--time",String(Lp),v.container]);_!==null&&await N(n,t,["stop","--time",String(Lp),et(p)]),await N(n,t,["stop","--time",String(f),B(p).pod])},async remove(p,f){let{services:y,anchor:S}=ee(await x(p),f);for(let _ of y)await N(n,t,["rm","--force","--ignore",_.container]);S!==null&&await N(n,t,["rm","--force","--ignore",et(p)]),await N(n,t,["rm","--force","--ignore","--volumes",B(p).pod])},async removeData(p){await N(n,t,["volume","rm","--force",B(p).pod]);let f=Tn(p);(await n(t,["volume","exists",f])).code===0&&await N(n,t,["volume","rm","--force",f])},async applyOwnerPassword(p){let f=await n(t,["exec",...ve,B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${ae(p.ref)}_owner'`]);if(f.code===0&&gu(f.stdout,p.ownerPassword))return;let y=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
460
- `);await N(n,t,["exec",...ve,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:p.ownerPassword},stdin:y})},async applyRolePassword(p,f,y){let S=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${It(f)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
461
- `);await N(n,t,["exec",...ve,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",B(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:y},stdin:S})},async applyRouterLogin(p,f){if(await N(n,t,["exec",...ve,"--interactive","--env","SNOUTPOD_ROUTER_PASSWORD",B(p).pod,"psql","--username","snoutpod_admin","--dbname",ae(p),"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROUTER_PASSWORD:f??""},stdin:Lu(p,f!==null)}),f!==null){let y=[...ne(p),"-v","ON_ERROR_STOP=1"],S=await n(t,[...y,"-tAc","show data_directory"]),_=S.stdout.trim();if(S.code!==0||!/^\/[A-Za-z0-9/_.-]+$/.test(_)||!await si(p,_))throw new Error(`${p}: TLS for the copies' routers could not be turned on`);await N(n,t,[...y,"-tAc","select pg_reload_conf()"])}},async updateExtensions(p){let f=B(p),y=await n(t,["exec",...ve,f.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${f.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${f.database}' desc, datname limit 1`]),S=y.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(y.stdout.trim())?y.stdout.trim():f.database,_=await N(n,t,["exec",...ve,"--interactive",f.pod,"psql","--username","snoutpod_admin","--dbname",S,"--quiet","-tA","-F",xa,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:gp});return hp(_)},async applyDbAccess(p,f){let y=B(p),S=await N(n,t,["exec",...ve,"--interactive","--env",so,y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[so]:Zu(p,f)},stdin:Yu,timeoutMs:6e4});return Xu(S)},async runProjectSql(p,f){let y=B(p);await N(n,t,["exec",...ve,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:f})},async ensureHostServiceDatabase(p,f,y){let S=Nt(p),_=It(f),v=It(y),K=U=>["exec",S,"psql","--username",_,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",U];(await N(n,t,K(`select 1 from pg_database where datname = '${v}'`))).trim()!=="1"&&await N(n,t,K(`create database "${v}"`))},async renameProject(p,f){f!==p.ref&&await N(n,t,["exec",...ve,"--interactive",B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:YS(f,p.ref)})},async waitReady(p,f){let y=B(p).pod,S=Date.now()+f,_=0;for(;;){let v=await n(t,["exec",...ve,y,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(v.code===0&&v.stdout.trim()==="t")return s(p),!0;if(Date.now()>=S||await Z(p,++_))return!1;await new Promise(K=>setTimeout(K,500))}},async waitStandby(p,f){let y=Date.now()+f,S=0;for(;;){if(await xt(p)===!0)return!0;if(Date.now()>=y||await Z(p,++S))return!1;await new Promise(_=>setTimeout(_,500))}},async capacity(){let[p,f]=await Promise.all([c(),e.capacity()]);return{pods:p,...f}},async databaseBytes(p){let f=await n(t,["exec",...ve,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return f.code===0?ea(f.stdout):null},async storageBytes(p){let f=await n(t,["exec",...ve,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-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 f.code===0?ea(f.stdout):null},async setReadOnly(p,f){let y=B(p);await N(n,t,["exec",...ve,y.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${y.database} set default_transaction_read_only = ${f?"on":"off"}`])},async archiveState(p){let f=await n(t,["exec",...ve,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-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(f.code!==0)return null;let[y="",S=""]=f.stdout.trim().split(`
462
- `)[0]?.split("|")??[],_=Number(S);return{lastArchivedAt:y.trim()||null,failures:Number.isInteger(_)&&_>=0?_:0}}}}async function Wp(e,t,n,r,o=Qp){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,c;try{let m=JSON.parse(s.stdout);a=m[0]?.State,c=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=AS(c?.[go]);if(!u)return{name:n,ok:!0,detail:"running"};let l=await o(u.port,u.path);return l===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${l}`}}async function VS(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 Ma(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function YS(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" with allow_connections false;',"set snoutpod.rename_from = :'from_db';","do $$","declare"," db text := current_setting('snoutpod.rename_from');"," remaining text;","begin",` for attempt in 1..${Jp} loop`," perform pg_stat_clear_snapshot();"," select string_agg(coalesce(nullif(application_name, ''), backend_type) || ' (pid ' || pid || ')', ', ')"," into remaining from pg_stat_activity where datname = db and pid <> pg_backend_pid();"," if remaining is null then"," return;"," end if;"," perform pg_terminate_backend(pid) from pg_stat_activity where datname = db and pid <> pg_backend_pid();",` perform pg_sleep(${zp/Jp});`," end loop;",` raise exception 'database % still has sessions after ${zp}s, not renamed: %', db, remaining;`,"end","$$;",'alter database :"from_db" rename to :"to_db";','alter database :"to_db" with allow_connections true;','alter role :"from_role" rename to :"to_role";',"\\getenv admin_password POSTGRES_PASSWORD","\\if :{?admin_password}","alter role snoutpod_admin with password :'admin_password';","\\endif","alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;","checkpoint;",""].join(`
463
- `)}function XS(e){if(!e?.issuer)return[];try{return["--env",`${wp}=${bp(e.issuer)}`]}catch{return[]}}function ZS(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Oe,Ca,Ue,Nn,po,jp,Lp,yS,wS,Yp,tn,Hp,Kp,Xp,Fa,go,Qp,RS,NS,ve,vt,IS,qp,HS,em,KS,GS,qS,JS,zp,Jp,ao=D(()=>{"use strict";Te();Qs();Yr();zr();ia();ca();la();On();dp();ta();fp();yp();ka();Sp();da();Pp();aa();_a();Rp();ro();Mp();Oe="snoutpod.ref",Ca="snoutpod.generation",Ue="snoutpod.service",Nn="snoutpod.services-generation",po="snoutpod.service-spec",jp="snoutpod.service-pod";Lp=10,yS=/^[a-z][a-z0-9-]{0,30}$/;wS=/^[a-z][a-z0-9_]{0,62}$/;Yp="-set-aside";tn="snoutpod.host-service",Hp="snoutpod.host-service-spec",Kp="snoutpod.host-service-joins";Xp=/^[A-Za-z_][A-Za-z0-9_]*$/;Fa=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,go="snoutpod.health";Qp=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()})},RS="crun";NS="/usr/sbin/nft",ve=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp -c synchronous_commit=local"],vt=3e4,IS=3e4;qp=200,HS=1024,em=512,KS=45e3,GS=1e3,qS=4096;JS=8;zp=30,Jp=300});var hm={};ci(hm,{DEFAULT_LOCAL_MEMORY_MB:()=>am,DEFAULT_LOCAL_PORT:()=>r_,ENGINE:()=>oe,ENGINE_NAME:()=>Ka,IMAGE:()=>Wa,NETWORK:()=>$t,NEW_PROJECT_MAJOR:()=>qa,NO_BLACKHOLE_ROUTES:()=>dm,PUBLIC_IMAGE:()=>i_,PUBLISHED_MAJORS:()=>rn,capacity:()=>cm,chooseLocalImage:()=>Dn,dropIfUnused:()=>fm,ensureImage:()=>l_,ensureNetwork:()=>m_,exec:()=>ye,hasData:()=>bo,localImageFor:()=>lm,localPsql:()=>h_,localRuntime:()=>p_,localSpec:()=>mm,localStatus:()=>za,localUri:()=>pm,networkPolicyInPlace:()=>um,newLocalPassword:()=>s_,newLocalRef:()=>o_,podLog:()=>gm,podName:()=>on,podmanReady:()=>d_,removeLocal:()=>b_,staleImage:()=>Ga,startLocal:()=>g_,stopLocal:()=>w_,volumePgVersion:()=>ho});import{randomBytes as om}from"node:crypto";import{existsSync as nm}from"node:fs";import{platform as wo}from"node:os";import{delimiter as QS,join as e_}from"node:path";function t_(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=wo()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(QS))if(t&&e.some(n=>nm(e_(t,n))))return"podman";return nm("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Dn(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Cn(qa);let n=Number(Ha(e));if(!rn.includes(n)){let r=e.trim()||"(empty)",o=rn.map(s=>Cn(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${rn.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Cn(n)}function Cn(e){return{name:`${n_}:${e}`,published:`${sm}:${e}`,major:e}}function o_(){return Qd(e=>om(e))}function s_(){return om(18).toString("base64url")}async function l_(e,t=ye,n=Dn(null)){let r=n.published;if(!r)return null;if((await t(oe,["image","exists",n.name])).code===0)return await c_(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(oe,["pull",r],{timeoutMs:im}).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(oe,["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 c_(e,t,n,r){let o=await t(oe,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
464
- `):[];if(!s||s!==a)return;let c=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(oe,["pull",r],{timeoutMs:im}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(c));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
465
- `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(oe,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),l=u?.code===0?u.stdout.trim():"";if(!l||l===s)return;let m=await t(oe,["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 d_(e=a_,t=Wa){if(Ka!=="podman")return`SNOUTPOD_ENGINE=${Ka}: 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 ye(oe,["--version"])}catch{return`${oe} 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`${oe} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await ye(oe,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await ye(oe,["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 u_(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 ye(oe,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
466
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function bo(e){let t=wo()==="darwin"||wo()==="win32";return np(ye,e,{binary:oe,look:t?u_:void 0})}async function ho(e,t=ye,n=wo()){let r=B(e).pod;if((await t(oe,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of rn){let c=Cn(a).name;if((await t(oe,["image","exists",c])).code===0){s=c;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 (${rn.map(a=>Cn(a).published).join(" or ")}).`);o=await t(oe,["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(oe,["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(oe,["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 lm(e,t=ho){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Dn(null));ae(e);let n=t===ho?yo.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Dn(o)}catch(s){throw new Error(`${on(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===ho&&(yo.set(e,r),r.catch(()=>yo.delete(e))),r}async function cm(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function p_(e){return tm({exec:ye,binary:oe,network:$t,hasData:bo,capacity:cm,repository:e})}function on(e){return B(e).pod}async function m_(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await f_()||await ye(oe,Vr({name:$t}));return}let t=Vr({name:$t,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await um())return;await e?.(),await ye(oe,["network","rm","--force",$t]).catch(()=>{});let o=await ye(oe,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${dm}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${$t} with egress blocked${n?`: ${n}`:""}`)}async function f_(){return(await ye(oe,["network","inspect",$t,"--format","json"])).code===0}async function um(){let e=await ye(oe,["network","inspect",$t,"--format","json"]);if(e.code!==0)return!1;try{return Iu(JSON.parse(e.stdout)?.[0])}catch{return!1}}function pm(e){let t=B(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function mm(e,t={}){return ae(e.ref),{ref:e.ref,generation:1,image:t.image??Wa,memoryMb:t.memoryMb??am,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 In(e,t){e.onProgress?.(t)}async function g_(e,t,n={}){let r=n.image??(await lm(t.ref,n.pgVersion)).name,o=mm(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??bo,c=(await e.list()).find(u=>u.ref===t.ref),i=null;if(c?c.status==="stopped"&&(i=await(n.staleImage??Ga)(t.ref,r))?(In(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??fm)(i)):c.status==="stopped"?(In(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(In(n,"The database is already running."),c.status==="running"&&await(n.staleImage??Ga)(t.ref,r)&&In(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(In(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 y_(t.ref,s,n.podLog??gm));return await e.applyOwnerPassword(o),await e.createServices(o),za(e,t,n)}function rm(e){return e.trim().replace(/^sha256:/,"")}async function Ga(e,t){let[n,r]=await Promise.all([ye(oe,["container","inspect","--format","{{.Image}}",on(e)]).catch(()=>null),ye(oe,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=rm(n.stdout),s=rm(r.stdout);return o&&s&&o!==s?o:null}async function fm(e){let t=await ye(oe,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await ye(oe,["rmi",e]).catch(()=>{})}async function h_(e,t){let n=B(e.ref);return ye(oe,["exec","--interactive","-e","PGPASSWORD",on(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 gm(e){let t=await ye(oe,["logs","--tail","20",on(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function y_(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${on(e)} never became ready in ${t}ms
479
+ `});function cm(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 lm,hR,yR,dm=W(()=>{"use strict";Zn();Lo();lm="SNOUTPOD_OAUTH_ISSUER",hR=256*1024,yR=5*6e4});function hn(e){return`${H(e).pod}-guard`}function um(e,t,n){if(!Number.isInteger(t)||t<1||t>65535)throw new Error(`not a port: ${t}`);let r=e.leaseMs??1e4;return{LEPIS_GUARD_WITNESS:e.witness,...e.witnessSslmode?{LEPIS_GUARD_WITNESS_SSLMODE:e.witnessSslmode}:{},...e.witnessCa?{LEPIS_GUARD_WITNESS_CA:`${jo}/${ci}`}:{},LEPIS_GUARD_CLUSTER:e.cluster,LEPIS_GUARD_TOKEN:e.token,LEPIS_GUARD_COPY:e.copy,LEPIS_GUARD_PEER:e.peer,LEPIS_GUARD_PG:`127.0.0.1:${t}`,LEPIS_GUARD_PG_USER:"snoutpod_admin",LEPIS_GUARD_PG_PASSWORD:n,LEPIS_GUARD_STATE:`${jo}/epoch`,LEPIS_GUARD_STATUS:`${jo}/status.json`,LEPIS_GUARD_LEASE_MS:String(r),LEPIS_GUARD_TICK_MS:String(Math.max(1,Math.min(1e3,Math.floor(r/5))))}}function pm(e,t,n,r){let o=e.witnessCa?r(e.witnessCa).slice(0,16):"";return[e.image,e.witness,e.witnessSslmode??"",e.cluster,e.copy,e.peer,e.leaseMs??1e4,t,n,...o?[o]:[]].join("|")}function mm(e,t,n){if(se(e),!n.hostDir.startsWith("/")||n.hostDir.includes(":"))throw new Error(`not a host directory for a volume: ${n.hostDir}`);return["run","--detach","--name",hn(e),"--label",`${Uo}=${e}`,"--label",`${di}=${n.fingerprint}`,"--network","host","--userns","auto","--volume",`${n.hostDir}:${jo}:U`,"--memory","64m","--pids-limit","64","--restart","no",...n.envNames.flatMap(r=>["--env",r]),t.image,"guard"]}function fm(e,t,n,r){let o=2*(r.leaseMs??1e4)/1e3;return n-e<=o+60?!1:t===null||n-t>o}var ci,jo,Uo,di,gm=W(()=>{"use strict";Le();ci="witness-ca.pem";jo="/guard",Uo="snoutpod.guard",di="snoutpod.guard-spec"});function wm(e){return se(e),{anchor:`snoutpod-${e}-copy`,router:`snoutpod-${e}-copy-lepis`,service:t=>`snoutpod-${e}-copy-${yn(t)}`}}var hm,ym,bm=W(()=>{"use strict";Le();Lo();hm="snoutpod.copy",ym="snoutpod.copy-service"});import{createHash as yk}from"node:crypto";function ct(e){return`${H(se(e)).pod}-${Fo}`}function _m(e){return lt(e)?(e.services??[]).filter(t=>t.name!==ge):[]}function Kt(e,t){return lt(e)&&t.name!==ge}function vm(e){let t=e.stackPodPort;if(typeof t!="number"||!Number.isInteger(t)||t<1||t>65535)throw new Error(`${e.ref}: a services-only pod needs its own pod port (stackPodPort), and has ${JSON.stringify(t??null)}`);return t}function pi(e,t){if(t.hostPort===null||t.hostPort===void 0)return null;let n=vm(e);if(typeof e.port!="number"||!Number.isInteger(e.port)||e.port<1)throw new Error(`${e.ref}: ${t.name}'s slot is read off the pod port, and the spec has none`);let r=t.hostPort-e.port;if(r<1e3||r%1e3!==0)throw new Error(`${e.ref}: ${t.name}'s hostPort ${t.hostPort} is on no slot of pod port ${e.port}`);let o=n+r;if(o>65535)throw new Error(`${e.ref}: stackPodPort ${n} puts ${t.name} past 65535`);return o}function mi(e,t){let n=pi(e,t),r=$m(e,t);return n===null?t:{...t,hostPort:n,...r===null?{}:{adminHostPort:r}}}function $m(e,t){return!t.adminPort||!t.adminHostPort?null:pi(e,{name:`${t.name} admin`,hostPort:t.adminHostPort})}function fi(e){return _m(e).flatMap(t=>{let n=$m(e,t);return n===null||!t.adminPort?[]:[[n,t.adminPort]]}).sort((t,n)=>t[0]-n[0])}function gi(e){let t=_m(e);return t.length>0&&vm(e),t.flatMap(n=>{let r=pi(e,n);return r===null?[]:[[r,n.port]]}).sort((n,r)=>n[0]-r[0])}function Pm(e,t){let n=fi(e);return yk("sha256").update(`stack:${t}:`).update(JSON.stringify(gi(e))).update(n.length===0?"":`admin:${JSON.stringify(n)}`).digest("hex").slice(0,32)}var St,ui,Fo,Sm,km,xm=W(()=>{"use strict";Le();Co();St="snoutpod.stack",ui="snoutpod.stack-spec",Fo="stack",Sm=16,km=16});import{createHash as er,randomBytes as bk}from"node:crypto";import{access as Sk,constants as kk,mkdir as Em,readFile as _k,readdir as vk,rename as $k,rm as Pk,writeFile as xk}from"node:fs/promises";import{delimiter as Ek,join as qt}from"node:path";function Hm(e,t,n,r){return er("sha256").update(bi(e)).update(e.smtpSecretEnv&&t?er("sha256").update(t).digest("hex"):"").update(n===void 0?"":`router:${n}`).update(r===void 0?"":`@${r}`).digest("hex").slice(0,32)}function Ak(e,t){return Qa(t),er("sha256").update(`separated:${t}:${fn}:`).update(Hm(e)).digest("hex").slice(0,32)}function bi(e){if(Array.isArray(e))return`[${e.map(bi).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)}:${bi(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function yn(e){if(!Rk.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function It(e){if(!Tk.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Ok(e,t,n,r,o){if(r===void 0)return`postgres://${It(t)}:${n}@127.0.0.1:5432/${se(e)}`;if(o!==void 0&&o!==bt(e))throw new Error(`${o} is not ${e}'s router`);return`postgres://${`${It(t)}:${n}@${o??"127.0.0.1"}`}:${Ue(r,"router port")}/${se(e)}?sslmode=disable`}function Nk(e,t,n){let r=`${t}=`,o=e.split(`
480
+ `).find(s=>s.startsWith(r));if(o===void 0)return null;try{let s=new URL(o.slice(r.length).trim());return s.protocol!=="postgres:"||decodeURIComponent(s.username)!==n||s.password===""?null:decodeURIComponent(s.password)}catch{return null}}function Ko(e,t){if(!t.dbUriEnv||!t.dbUriRole||t.name===ge)return;let n=(e.services??[]).find(r=>r.name===ge);if(n)return si.includes(t.dbUriRole)?n.port:lt(e)?fn:void 0}function Si(e,t){return lt(e)&&Ko(e,t)!==void 0?bt(e.ref):void 0}function Tm(e,t,n,r){let o=ei(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]=Ok(e.ref,s,a,Ko(e,t),Si(e,t))}if(t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),t.adminPortEnv&&t.adminPort){if(!Gm.test(t.adminPortEnv))throw new Error(`${t.name}: not an environment variable name: ${JSON.stringify(t.adminPortEnv)}`);o[t.adminPortEnv]=String(Ue(t.adminPort,`${t.name}: adminPort`))}return o}function Om(e,t){return`${H(e).pod}-${yn(t)}`}function Ik(e){try{let t=JSON.parse(e);return typeof t.spec=="string"&&/^[0-9a-f]{32}$/.test(t.spec)&&Number.isInteger(t.generation)?{spec:t.spec,generation:t.generation}:null}catch{return null}}function Bo(e){return`${e}${Km}`}function Im(e){return typeof e=="string"&&e.endsWith(Km)}function yi(e){let t=e.Names?.[0];return t===void 0?void 0:t.replace(/^\//,"")}function Gt(e){return`snoutpod-host-${yn(e)}`}function Ck(e,t){let n=Object.keys(e.env).sort().map(o=>[o,e.env[o]]),r=t&&(t.joins||(t.publishes?.length??0)>0)?[{joins:t.joins??null,publishes:t.publishes??[]}]:[];return er("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,n,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null,...r])).digest("hex").slice(0,32)}function Mk(e){return[...e.filter(t=>!t.joins),...e.filter(t=>t.joins)]}function Dk(e){return Object.keys(e.secretEnv??{}).length>0||Object.keys(e.files??{}).length>0||!!e.envFrom||!!e.databaseUrl}function Lk(e,t){return t.filter(n=>n.joins===e&&n.hostPort>0).map(n=>[n.hostPort,n.port]).sort((n,r)=>n[0]-r[0])}function _i(e){return!e.adminPort||!e.adminHostPort?null:[Ue(e.adminHostPort,`${e.name}: adminHostPort`),Ue(e.adminPort,`${e.name}: adminPort`)]}function jk(e,t){let n=[],r=kn(t);for(let o of e??[]){if(yn(o.name),Ue(o.port,`${o.name}: port`),o.hostPort===null||o.hostPort===void 0)continue;Ue(o.hostPort,`${o.name}: hostPort`);for(let a of r)n.push("--publish",`${a}:${o.hostPort}:${o.port}`);let s=_i(o);if(s&&n.push("--publish",`127.0.0.1:${s[0]}:${s[1]}`),o.name===ge){let a=o.hostPort+No;Ue(a,`${o.name}: admin hostPort`);for(let c of r)n.push("--publish",`${c}:${a}:${Oo}`)}}return n}function Uk(e,t,n){if(se(e.ref),t.name!==ge)throw new Error(`${t.name} is not the router`);if(!n.network)throw new Error(`${e.ref}: a separated router needs the pod network`);Ue(t.port,`${t.name}: port`);let r=Qa(e.routerPodPort),o=([s,a])=>kn(n.bind).flatMap(c=>["--publish",`${c}:${Ue(s,"router hostPort")}:${a}`]);return["run","--detach","--name",bt(e.ref),"--label",`${Nt}=${e.ref}`,"--label",`${Sn}=${e.servicesGeneration??0}`,"--label",`${Qn}=${n.fingerprint}`,"--network",n.network,"--userns=auto","--memory",`${t.memoryMb}m`,"--pids-limit",String(Jm),"--restart","no",...o([r.postgres,t.port]),...o([r.admin,Oo]),...o([r.passthrough,fn]),...qm(t),"--volume",`${Xn(e.ref)}:${Io}:ro`,...n.envNames.flatMap(s=>["--env",s]),t.image]}function Fk(e,t){if(se(e.ref),!t.network)throw new Error(`${e.ref}: a services-only pod needs the pod network`);return["run","--detach","--name",ct(e.ref),"--label",`${St}=${e.ref}`,"--label",`${ui}=${t.fingerprint}`,"--network",t.network,"--userns=auto","--memory",`${Sm}m`,"--pids-limit",String(km),"--restart","no","--stop-signal","SIGKILL",...gi(e).flatMap(([n,r])=>kn(t.bind).flatMap(o=>["--publish",`${o}:${Ue(n,"stack hostPort")}:${Ue(r,"service port")}`])),...fi(e).flatMap(([n,r])=>["--publish",`127.0.0.1:${Ue(n,"stack adminHostPort")}:${Ue(r,"admin port")}`]),"--entrypoint","sleep",e.image,"infinity"]}function Bk(e){let t;try{t=JSON.parse(e.trim()||"null")}catch{return null}if(t===null)return[];if(typeof t!="object"||Array.isArray(t))return null;let n=[];for(let r of Object.values(t))if(Array.isArray(r))for(let o of r){let s=Number(o?.HostPort);Number.isInteger(s)&&s>0&&!n.includes(s)&&n.push(s)}return n}function kn(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 Ue(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Ho(e){let t=[];for(let n of Object.keys(e)){if(!Gm.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function qm(e,t={}){if(t.adminPublished&&e.adminHealthPath&&_i(e)){if(!ki.test(e.adminHealthPath))throw new Error(`${e.name}: not a health path: ${JSON.stringify(e.adminHealthPath)}`);return["--label",`${Go}=${e.adminHostPort}:${e.adminHealthPath}`]}let n=e.healthPath;if(n==null||n==="")return[];if(!ki.test(n))throw new Error(`${e.name}: not a health path: ${JSON.stringify(n)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Ue(e.hostPort,`${e.name}: hostPort`),["--label",`${Go}=${e.hostPort}:${n}`])}function Hk(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||!ki.test(r)?null:{port:n,path:r}}async function Gk(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Sk(o,kk.X_OK));for(let o of n.split(Ek))if(o)try{let s=qt(o,e);return await r(s),s}catch{}return null}function qk(){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(`
481
+ `)}function Wk(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Jk(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 vi(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Vk(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 Yk(e,t,n,r){return{image:await Xk(e,t,r),data:await Zk(e,t,n)}}async function Xk(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 Jk(r,n)}async function Zk(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",H(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?vi(s.stdout):null}catch{return null}}function Wm(e){return e.filter(t=>!!t.replicaPeer||t.guard!==void 0||t.hostRole==="standby").map(t=>({ref:t.ref,group:t.group??t.ref,container:H(t.ref).pod})).sort((t,n)=>t.ref<n.ref?-1:t.ref>n.ref?1:0)}function Qk(e,t,n){if(t.size>1)return`the ${xe} network already holds more than one project (${[...t].sort().join(", ")}), so nothing more joins it`;let r=t.size===1?[...t][0]:n;return e.group===r?null:`the ${xe} network holds ${r}'s copies, and another project may not share its bridge`}function e_(e,t){let n=new Map,r=new Set(t.length>0?Wm(e.filter(o=>!(o.guard!==void 0&&o.peerAddress))).map(o=>o.ref):[]);for(let o of e){if(o.state==="deleted"||!o.groupPeers||o.groupPeers.length===0)continue;let s=o.group??o.ref;for(let a of[...new Set(o.groupPeers)].sort()){if(a===o.peerAddress||r.has(o.ref)&&t.includes(a))continue;let c=Sp(s,a),i=n.get(c)??{group:s,peer:a,containers:[]};i.containers.push(H(o.ref).pod),o.ref===s&&i.containers.push(bt(o.ref)),n.set(c,i)}}return n}function t_(e,t){let n=[];for(let[r,o]of[...t].sort(([s],[a])=>s<a?-1:s>a?1:0)){if(!Ht(o))continue;let s=vo(r);if(s!==null){let i=e.find(u=>u.guard!==void 0&&u.state!=="deleted"&&!!u.peerAddress&&(u.group??u.ref)===s);i&&n.push({subnet:o,peer:i.peerAddress,ports:i.peerPorts?[...i.peerPorts]:null});continue}let a=mn(r);if(a===null)continue;let c=e.filter(i=>i.state!=="deleted"&&(i.group??i.ref)===a.group&&(i.groupPeers??[]).includes(a.peer));c.length>0&&n.push({subnet:o,peer:a.peer,ports:[...new Set(c.flatMap(i=>i.groupPeerPorts?.[a.peer]??[]))]})}return n}function Lm(e,t){let n=kn(t);return(e.Ports??[]).some(r=>{let o=r.host_ip??"";return o!==""&&o!=="0.0.0.0"&&o!=="::"&&!n.includes(o)})}function a_(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:jm;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,jm)}`),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 i_(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Kk;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Gk(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Vm(e){let t=e.binary??"podman",n=e.exec,r=i_(e),o=e.probe??zm;async function s(p){try{await n(t,["exec",..._e,"--interactive",H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:zk,stdin:qk()})}catch{}}async function a(){let[p,f,g]=await Promise.all([N(n,t,["ps","--all","--filter",`label=${je}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${Nt}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${St}`,"--format","json"])]),w=await T(p.trim()?JSON.parse(p):[],!0),S=new Map;for(let F of g.trim()?JSON.parse(g):[]){let B=y(F);B&&S.set(B,(S.get(B)??!1)||F.State==="running")}let k=[...w.flatMap(F=>{let B=F.Labels?.[Ge]?F.Labels?.[St]:void 0;return B===void 0?[F]:S.has(B)?[S.get(B)?F:{...F,State:"exited"}]:[]}),...(f.trim()?JSON.parse(f):[]).flatMap(F=>{let B=F.Labels?.[Nt];return B?[{...F,Labels:{...F.Labels,[je]:B,[Ge]:ge}}]:[]})],x=new Map,_=new Map,I=new Map,j=new Map,z=new Set([...S.keys(),...(f.trim()?JSON.parse(f):[]).flatMap(F=>F.Labels?.[Nt]??[])]);for(let F of k){let B=F.Labels?.[je],D=F.Labels?.[Ge];if(!B||!D)continue;if(Im(yi(F))){j.set(B,[...j.get(B)??[],D]);continue}let Y=x.get(B)??[];Y.push(D),x.set(B,Y),wi(F.State)==="stopped"&&_.set(B,[..._.get(B)??[],D]);let me=Number(F.Labels?.[Sn]??0),Te=Number.isInteger(me)?me:0;I.set(B,Math.max(I.get(B)??Te,Te))}let L=k.filter(F=>F.Labels?.[je]&&!F.Labels?.[Ge]),$=L.map(F=>u_(F,Date.now())?"stopped":wi(F.State)),U=await c_(L,l_,(F,B)=>$[B]==="running"?Promise.resolve(!0):e.hasData(F.Labels?.[je]??""));return L.map((F,B)=>{let D=F.Labels?.[je]??"",Y=Number(F.Labels?.[hi]);return{ref:D,status:$[B]??"unknown",generation:Number.isInteger(Y)?Y:null,hasData:U[B]??!1,services:x.get(D)??[],servicesStopped:_.get(D)??[],...z.has(D)?{separated:!0}:{},...j.has(D)?{setAside:j.get(D)}:{},...Lm(F,e.podBind)?{boundElsewhere:!0}:{},...I.has(D)?{servicesGeneration:I.get(D)}:{}}})}async function c(){let p=await N(n,t,["ps","--all","--filter",`label=${je}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(g=>g.Labels?.[je]&&!g.Labels?.[Ge]).length}async function i(p){let f=await(e.majors??((g,w)=>Yk(n,t,g,w)))(p.ref,p.image);return Vk(p.ref,f)}async function u(p){let f=H(p.ref),g=await i(p);if(g)throw new Error(g);let w=e.credentials,S=await Xp(e.repository,w?()=>w.forProject(p.ref,p.bucket):null),k={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...Qp(S)},x=[...await r(),"run","--detach","--name",f.pod,"--label",`${je}=${p.ref}`,"--label",`${hi}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,"--pids-limit",String(n_),...f_(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?kn(e.podBind).flatMap(_=>["--publish",`${_}:${p.port}:5432`]):[],...jk(Za(p),e.podBind),"--volume",`${f.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${p.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${p.bucket}`,"--env",`SNOUTPOD_S3_REGION=${p.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...m_(p.oauth),...a_(p.tuning).flatMap(_=>["--env",_]),"--env",`SNOUTPOD_RETENTION_FULL=${Hu(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...Zp(S),p.image,"snoutpod-postgres"];await N(n,t,x,{env:k})}async function l(p){return(await le(p)).services}async function m(p){return(await l(p)).filter(f=>!f.setAside)}function y(p){return p.Labels?.[Ge]?void 0:p.Labels?.[St]}async function T(p,f){let g=e.inPlaceRoot;if(g===void 0)return p;let w=await vk(g).catch(()=>[]);if(w.length===0)return p;let S=new Set(p.flatMap(x=>x.Id?[x.Id]:[])),k=new Map;for(let x of w){let _=x.endsWith(".json")?x.slice(0,-5):"";if(!Nm.test(_))continue;if(!S.has(_)){f&&await Pk(qt(g,x),{force:!0});continue}let I=Ik(await _k(qt(g,x),"utf8").catch(()=>""));I!==null&&k.set(_,I)}return p.map(x=>{let _=x.Id&&x.Labels?.[Ge]?k.get(x.Id):void 0;return _===void 0?x:{...x,Labels:{...x.Labels,[Qn]:_.spec,[Sn]:String(_.generation)}}})}async function A(p){let f=await n(t,["container","inspect","--format","{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}",p]),[g,w]=f.code===0?f.stdout.trim().split(/\s+/).map(Number):[];return g!==void 0&&Number.isInteger(g)&&g>0&&w!==void 0&&Number.isInteger(w)?{bytes:g,swap:w}:null}async function oe(p,f,g){let w=g.swap===-1?["--memory-swap","-1"]:g.swap>g.bytes?["--memory-swap",`${f+Math.floor((g.swap-g.bytes)/wn)}m`]:[];if((await n(t,["update","--memory",`${f}m`,...w,p])).code!==0)return!1;let k=await A(p);return k!==null&&k.bytes===f*wn}async function Z(p,f){let g=e.inPlaceRoot;if(g===void 0||!Nm.test(p))return;await Em(g,{recursive:!0});let w=qt(g,`${p}.json`);await xk(`${w}.tmp`,JSON.stringify(f)),await $k(`${w}.tmp`,w)}async function le(p){let[f,g,w]=await Promise.all([N(n,t,["ps","--all","--filter",`label=${je}=${se(p)}`,"--filter",`label=${Ge}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${Nt}=${p}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${St}=${p}`,"--format","json"])]),S=await T(f.trim()?JSON.parse(f):[],!1),k=[];for(let _ of S){let I=_.Labels?.[Ge];if(!I)continue;let j=Number(_.Labels?.[Sn]??0),z=Im(yi(_));k.push({name:I,container:z?yi(_)??"":Om(p,I),id:_.Id??"",spec:_.Labels?.[Qn]??"",pod:_.Labels?.[Am]??"",running:_.State==="running",generation:Number.isInteger(j)?j:0,separate:!1,stack:_.Labels?.[St]===p,setAside:z})}for(let _ of g.trim()?JSON.parse(g):[]){if(_.Labels?.[Nt]!==p)continue;let I=Number(_.Labels?.[Sn]??0);k.push({name:ge,container:bt(p),id:_.Id??"",spec:_.Labels?.[Qn]??"",pod:"",running:_.State==="running",generation:Number.isInteger(I)?I:0,separate:!0,stack:!1,setAside:!1})}let x=null;for(let _ of w.trim()?JSON.parse(w):[])y(_)===p&&(x={id:_.Id??"",spec:_.Labels?.[ui]??"",running:_.State==="running"});return{services:k,anchor:x}}async function G(p){let f=await n(t,["container","inspect","--format","{{.Id}}",p]);return f.code===0?f.stdout.trim():""}function ue(p,f){return f?.databaseOnly?{services:p.services.filter(g=>!g.separate&&!g.stack),anchor:null}:p}async function V(p,f,g=new Map){let w=[];for(let z of f){let L=z.dbUriRole??null;z.dbUriEnv&&L&&!w.includes(L)&&w.push(It(L))}let S=new Map;if(w.length===0)return S;let k=await e.fixedRolePasswords?.()??new Map,x={},_=[],I=[];w.some(z=>si.includes(z))&&I.push(tm,nm);for(let z of w.filter(L=>ai.includes(L)))I.push(rm(z));let j=new Map;if(p.derivedServicePasswords===!0)for(let z of w.filter(ti))j.set(z,await ni(p.adminPassword,z));return w.forEach((z,L)=>{let $=k.get(z)??(j.has(z)?void 0:g.get(z));if($!==void 0){S.set(z,$);return}let U=j.get(z)??bk(24).toString("base64url"),F=`SNOUTPOD_SERVICE_PASSWORD_${L}`;S.set(z,U),x[F]=U,_.push("--env",F),I.push(`\\getenv service_password_${L} ${F}`),I.push(`\\set service_role_${L} ${z}`),I.push(`alter role :"service_role_${L}" with password :'service_password_${L}';`)}),I.push(""),await N(n,t,["exec",..._e,"--interactive",..._,H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",H(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:x,stdin:I.join(`
482
+ `)}),S}async function ve(p,f,g){let w=new Map;for(let x of g){let _=x.dbUriRole,I=f.find(L=>L.name===x.name);if(!x.dbUriEnv||!_||w.has(_)||I===void 0)continue;let j=await n(t,["container","inspect","--format","{{range .Config.Env}}{{println .}}{{end}}",I.container]),z=j.code===0?Nk(j.stdout,x.dbUriEnv,It(_)):null;z!==null&&w.set(_,z)}if(w.size===0)return w;let S=await n(t,["exec",..._e,H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolname || ' ' || coalesce(rolpassword, '') from pg_authid where rolname in (${[...w.keys()].map(x=>`'${x}'`).join(", ")})`]),k=new Map;if(S.code===0)for(let x of S.stdout.split(`
483
+ `)){let[_,I]=x.trim().split(" ");_&&I&&k.set(_,I)}for(let[x,_]of w)Ha(k.get(x)??"",_)||w.delete(x);return w}async function ne(p){let f=Xn(p.ref);await N(n,t,["volume","create","--ignore",f]),await N(n,t,[...await r(),"run","--rm","--network","none","--volume",`${f}:${Io}`,"--entrypoint","bash",p.image,"-c",Rp])}async function fe(p){let f=await r();if((await n(t,[...f,"image","exists",p])).code===0)return;let g=await n(t,[...f,"pull","--quiet",p]);if(g.code===0)return;let w=Wk(p);if(!w||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(g.stderr||g.stdout).trim()}`);await e.registryLogin(w),await N(n,t,[...f,"pull","--quiet",p])}async function Pn(p){let f=await n(t,["container","inspect","--format","{{json .HostConfig.PortBindings}}",H(p).pod]);return f.code===0?Bk(f.stdout):null}async function Wt(p,f={}){let g=p.services??[],w=lt(p)?[...g.filter(P=>P.name===ge),...g.filter(P=>P.name!==ge)]:g,{services:S,anchor:k}=await le(p.ref),x=S.filter(P=>!P.setAside),_;w.some(P=>P.smtpSecretEnv)&&(_=await e.smtpSecret?.().catch(()=>{}));let I=new Map(x.map(P=>[P.name,P])),j=P=>lt(p)&&P.name===ge,z=w.filter(P=>Kt(p,P)),L="";if(z.length>0){if(!w.some(P=>P.name===ge))throw new Error(`${p.ref}: a services-only pod reaches its database through the router, and spec.services has no ${ge}`);if(!e.network)throw new Error(`${p.ref}: a services-only pod needs the pod network`);if(z.some(P=>P.name===Fo))throw new Error(`${p.ref}: a service may not be called ${Fo}, which is the services-only pod's own name`);L=Pm(p,e.network)}let $=z.length>0&&k!==null&&k.running&&k.id!==""&&k.spec===L,U=w.some(P=>!j(P)&&!Kt(p,P))?await G(H(p.ref).pod):"",F=w.some(P=>!j(P)&&!Kt(p,P)&&_i(P))?new Set(await Pn(p.ref)??[]):new Set,B=$?k?.id??"":"",D=P=>Kt(p,P)?B:U,Y=P=>j(P)?Ak(P,p.routerPodPort):Hm(Kt(p,P)?mi(p,P):P,_,Ko(p,P),Si(p,P)),me=new Map(w.map(P=>[P.name,Y(P)])),Te=p.servicesGeneration??0,ze=new Set;if(e.inPlaceRoot!==void 0)for(let P of w){let he=I.get(P.name),Ie=D(P);if(j(P)||!he||!he.running||he.id===""||Ie===""||he.pod!==Ie||he.spec===me.get(P.name))continue;let Ye=await A(he.container);Ye===null||Ye.bytes%wn!==0||Ye.bytes/wn>=P.memoryMb||Y({...P,memoryMb:Ye.bytes/wn})===he.spec&&await oe(he.container,P.memoryMb,Ye)&&(await Z(he.id,{spec:me.get(P.name)??"",generation:Te}),ze.add(P.name),e.log?.(`${p.ref}: ${P.name} took ${P.memoryMb} MB in place (was ${Ye.bytes/wn} MB), not stopped`))}let Ee=new Set(w.filter(P=>{let he=I.get(P.name);if(ze.has(P.name))return!1;if(!he||!he.running||he.spec!==me.get(P.name))return!0;let Ie=D(P);return!j(P)&&(Ie===""||he.pod!==Ie)}).map(P=>P.name));if(f.renewPasswords)for(let P of w)P.dbUriEnv&&P.dbUriRole&&Ee.add(P.name);if(Ee.size===0&&ze.size===0&&w.length>0&&!x.some(P=>P.generation>=Te))for(let P of w)Ee.add(P.name);let Gi=P=>P.dbUriEnv&&P.dbUriRole?P.dbUriRole:null;for(let P=!0;P;){P=!1;let he=new Set(w.filter(Ie=>Ee.has(Ie.name)).map(Gi));for(let Ie of w){let Ye=Gi(Ie);!Ee.has(Ie.name)&&Ye!==null&&he.has(Ye)&&(Ee.add(Ie.name),P=!0)}}let qi=new Set(w.filter(P=>Ee.has(P.name)).map(P=>P.image));(z.length>0&&!$||Ee.has(ge))&&qi.add(p.image);for(let P of qi)await fe(P);let gg=P=>f.keepSeparated===!0&&(P.separate||P.stack)&&!Ee.has(P.name);for(let P of x)(Ee.has(P.name)||!me.has(P.name))&&!gg(P)&&await N(n,t,["rm","--force","--ignore",P.container]);if(k!==null&&!$&&!(f.keepSeparated===!0&&z.length===0)&&await N(n,t,["rm","--force","--ignore",ct(p.ref)]),z.length>0&&!$){if(await N(n,t,[...await r(),...Fk(p,{network:e.network,bind:e.podBind,fingerprint:L})]),B=await G(ct(p.ref)),B==="")throw new Error(`${p.ref}: the services-only pod was made and its id could not be read`);await f.afterStart?.(ct(p.ref))}let dr=w.filter(P=>Ee.has(P.name)),zi=S.filter(P=>P.setAside),as=await V(p,dr,zi.length>0&&!f.renewPasswords?await ve(p,zi,dr):new Map);dr.some(P=>P.name===ge)&&await ne(p);for(let P of dr){if(j(P)){let Wi=ei(Ep(p.ref,P.env),p.adminPassword);await N(n,t,[...await r(),...Uk(p,P,{network:e.network,bind:e.podBind,fingerprint:me.get(P.name)??"",envNames:Ho(Wi).filter(yg=>yg!=="--env")})],{env:Wi}),await f.afterStart?.(bt(p.ref));continue}let he=Kt(p,P),Ie=he?ct(p.ref):H(p.ref).pod,Ye=Om(p.ref,P.name),hg=[...await r(),"run","--detach","--name",Ye,"--label",`${je}=${p.ref}`,"--label",`${hi}=${p.generation}`,"--label",`${Ge}=${P.name}`,"--label",`${Sn}=${p.servicesGeneration??0}`,"--label",`${Qn}=${me.get(P.name)??""}`,"--label",`${Am}=${D(P)}`,...he?["--label",`${St}=${p.ref}`]:[],"--network",`container:${Ie}`,"--userns",`container:${Ie}`,"--memory",`${P.memoryMb}m`,"--pids-limit",String(Jm),"--restart","no",...qm(he?mi(p,P):P,{adminPublished:he||P.adminHostPort!==null&&P.adminHostPort!==void 0&&F.has(P.adminHostPort)}),...P.name===ge?["--volume",`${Xn(p.ref)}:${Io}:ro`]:[],...Ho(Tm(p,P,as,_)),P.image];Ee.has(ge)||await ir(p,P,as),await N(n,t,hg,{env:Tm(p,P,as,_)})}}async function ir(p,f,g){let w=Ko(p,f),S=f.dbUriRole,k=S?g.get(S):void 0;if(w===void 0||w===fn||!S||k===void 0)return;let x=H(p.ref),_=Si(p,f)??"127.0.0.1",{waitMs:I,retryMs:j}=e.routerLogin??{waitMs:r_,retryMs:o_},z=Date.now()+I;for(let L=1;;L++){let $=await n(t,["exec","--env","PGPASSWORD","--env","PGSSLMODE=disable","--env","PGCONNECT_TIMEOUT=3",x.pod,"psql","--host",_,"--port",String(w),"--username",It(S),"--dbname",x.database,"--no-password","--tuples-only","--no-align","--command","select 1"],{env:{PGPASSWORD:k}});if($.code===0){e.log?.(L===1?`${p.ref}: the router took ${S}'s password at once, ${f.name} starts`:`${p.ref}: the router took ${S}'s password on try ${L}, after ${L-1} refused, ${f.name} starts`);return}if(Date.now()>=z){let U=`${$.stderr}
484
+ ${$.stdout}`.trim().split(`
485
+ `).filter(Boolean).pop()??`exit ${$.code}`;throw new Error(`${p.ref}: the router did not take ${S}'s new password within ${I}ms (${L} tries; last: ${U}), so ${f.name} was not started`)}await new Promise(U=>setTimeout(U,j))}}async function ji(p,f){if(f%6!==0)return!1;let g=await n(t,["container","inspect","--format","{{.State.Status}}",H(p).pod]);return g.code===0&&/^(exited|stopped|dead)$/.test(g.stdout.trim())}function $e(p){return["exec",..._e,H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres"]}async function Kf(p){let f=await n(t,[...$e(p),"-tAc",ap]);return f.code===0?ip(f.stdout):null}async function Gf(p){let f=await n(t,[...$e(p),"-tAc","select case when pg_is_in_recovery() then '' else pg_current_wal_lsn()::text end"]),g=f.code===0?f.stdout.trim():"";return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/.test(g)?g:null}async function Ui(p){let f=await n(t,[...$e(p),"-tAc","select pg_is_in_recovery()"]);if(f.code!==0)return null;let g=f.stdout.trim();return g==="t"?!0:g==="f"?!1:null}async function qf(p,f){let g=x=>n(t,[...$e(p),"-tAc",x]),w=async()=>{let x=await g("select case when pg_is_in_recovery() then 'r' when (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int then 't' else 'f' end"),_=x.stdout.trim();return x.code===0&&(_==="t"||_==="f"||_==="r")?_:null},S=await w();if(S!=="f")return S==="t";let k=Date.now()+f;do{if((await g("checkpoint")).code!==0)return!1;if(await w()==="t")return!0;await new Promise(_=>setTimeout(_,1e3))}while(Date.now()<k);return!1}async function zf(p){let f=await n(t,[...$e(p),"-tAc",Gu],{timeoutMs:dt});return f.code===0?qu(f.stdout):null}async function Wf(p,f){let g=new Map;for(let S of f){if(typeof S!="string"){se(S.ref);let k=rp(S);k&&g.set(S.ref,k);continue}se(S),g.set(S,H(S).pod)}if(g.size===0)return new Map;let w=await n(t,["exec",H(p).pod,"timeout",String(10),"sh","-c",op,"sh",...g.values()]);return w.code===0?sp(w.stdout,g):null}async function Jf(p,f){se(f);let g=[];for(let x of[p,f]){let _=H(x).pod,I=await n(t,["container","inspect","--format","{{.State.Status}}",_]);if(I.code===0&&I.stdout.trim()==="running")throw new Error(`${_} is still running, so its data was not moved`);let j=await n(t,["volume","inspect","--format","{{.Mountpoint}}",_]),z=j.code===0?j.stdout.trim().replace(/\/+$/,""):"";if(!z)throw new Error(`the volume ${_} could not be found, so nothing was moved`);g.push(z)}let S=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",La,"sh",g[0],g[1]]):await n("sh",["-c",La,"sh",g[0],g[1]]),k=Yu(S.stdout,S.code);if(k)throw new Error(k)}async function Vf(p,f){se(p),se(f);for(let _ of[p,f]){let I=H(_).pod,j=await n(t,["container","inspect","--format","{{.State.Status}}",I]);if(j.code===0&&j.stdout.trim()==="running")throw new Error(`${I} is running, so no data was moved`)}let g=H(p).pod;(await n(t,["volume","exists",g])).code!==0&&await N(n,t,["volume","create",g]);let w=[];for(let _ of[g,H(f).pod]){let I=await n(t,["volume","inspect","--format","{{.Mountpoint}}",_]),j=I.code===0?I.stdout.trim().replace(/\/+$/,""):"";if(!j)throw new Error(`the volume ${_} could not be found, so nothing was moved`);w.push(j)}let k=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",ja,"sh",w[0],w[1]]):await n("sh",["-c",ja,"sh",w[0],w[1]]),x=Xu(k.stdout,k.code);if(x)throw new Error(x)}async function Yf(p){return(await n(t,[...$e(p),"-v","ON_ERROR_STOP=1",...Ua.flatMap(g=>["-c",g])])).code===0}async function Xf(p){let f=e.guardRoot;if(!f)return;let g=await N(n,t,["ps","--all","--filter",`label=${Uo}`,"--format","json"]),w=new Map((g.trim()?JSON.parse(g):[]).flatMap(k=>{let x=k.Labels?.[Uo];return x?[[x,{fingerprint:k.Labels?.[di]??"",status:wi(k.State),startedAt:k.StartedAt??0}]]:[]})),S=new Set(p.map(({spec:k})=>k.ref));for(let k of w.keys())S.has(k)||await N(n,t,["rm","--force","--ignore",hn(k)]);for(let{spec:k,guard:x}of p){if(k.port===null)continue;let _=pm(x,k.port,k.generation,L=>er("sha256").update(L).digest("hex")),I=w.get(k.ref);if(I?.fingerprint===_){if(I.status!=="running")await N(n,t,["start",hn(k.ref)]);else{let L=qt(f,k.ref,"status.json"),$=await n(t,["unshare","stat","-c","%Y",L]),U=$.code===0?Number($.stdout.trim()):null;fm(I.startedAt,Number.isFinite(U)?U:null,Math.floor(Date.now()/1e3),x)&&await N(n,t,["restart",hn(k.ref)])}continue}await N(n,t,["rm","--force","--ignore",hn(k.ref)]);let j=qt(f,k.ref);await Em(j,{recursive:!0,mode:448}),x.witnessCa&&await N(n,t,["unshare","sh","-c",'umask 022 && printf %s "$1" > "$2.tmp" && mv "$2.tmp" "$2"',"witness-ca",x.witnessCa,qt(j,ci)]),await fe(x.image);let z=um(x,k.port,k.adminPassword);await N(n,t,[...await r(),...mm(k.ref,x,{hostDir:j,fingerprint:_,envNames:Ho(z).filter(L=>L!=="--env")})],{env:z})}}async function Zf(p){return(await n(t,[...$e(p),"-v","ON_ERROR_STOP=1","-c",Da],{timeoutMs:dt})).code===0}async function Qf(p,f){return(await n(t,[...$e(p),"-v","ON_ERROR_STOP=1",...Wu(f).flatMap(w=>["-c",w])],{timeoutMs:dt})).code===0}async function eg(){let p=e.network;if(!p)return null;let f=(await N(n,t,["network","inspect",p,"--format","{{range .Subnets}}{{.Subnet}} {{end}}"])).trim().split(" ")[0]??"";if(f==="")return null;let g=await N(n,t,["ps","--format","json"]),S=(g.trim()?JSON.parse(g):[]).map(_=>{let I=_.Labels?.[je],j=_.Labels?.[bn],z=_.Labels?.[Nt];if(z)return{name:bt(z),ref:z,service:!1};let L=_.Labels?.[St];if(L&&!_.Labels?.[Ge])return{name:ct(L),ref:L,service:!1};let $=_.Labels?.[hm],U=_.Labels?.[ym];if($&&(!U||U===ge)){let F=wm($);return{name:U?F.router:F.anchor,ref:$,service:!1}}return I&&!_.Labels?.[Ge]?{name:H(I).pod,ref:I,service:!1}:j?{name:Gt(j),ref:j,service:!0}:null}).filter(_=>_!==null);if(S.length===0)return{subnet:f,containers:[]};let k=await N(n,t,["inspect","--format",`{{.Name}} {{with index .NetworkSettings.Networks "${p}"}}{{.IPAddress}}{{end}}`,...S.map(_=>_.name)]),x=new Map(k.split(`
486
+ `).map(_=>_.trim().split(" ")).filter(_=>_.length===2&&_[1]!=="").map(([_,I])=>[_,I]));return{subnet:f,containers:S.flatMap(_=>x.has(_.name)?[{ref:_.ref,ip:x.get(_.name),service:_.service}]:[])}}async function tg(p,f){if(!e.quotaRequests||!Number.isInteger(f)||f<1)return!1;let{writeFile:g,rename:w}=await import("node:fs/promises"),S=H(p).pod,k=`${e.quotaRequests}/${S}`;return await g(`${e.quotaRequests}/.${S}.tmp`,`${f}
487
+ `),await w(`${e.quotaRequests}/.${S}.tmp`,k),!0}async function Fi(p,f,g){if(!Number.isInteger(g)||g<1)return!1;let w=await n(t,[...$e(p),"-tAc",`select setting::bigint * pg_size_bytes(case when unit ~ '^[0-9]' then unit else '1' || unit end) / 1048576 from pg_settings where name = '${f}'`]);return w.code===0&&w.stdout.trim()===String(g)?!0:(await n(t,[...$e(p),"-v","ON_ERROR_STOP=1","-c",`alter system set ${f} = '${g}MB'`,"-c","select pg_reload_conf()"],{timeoutMs:dt})).code===0}function ng(p,f){return Fi(p,"temp_file_limit",f)}function rg(p,f){return Fi(p,"max_wal_size",f)}async function og(p){return(await n(t,["unshare","--rootless-netns",Dm,"-f","-"],{stdin:p,timeoutMs:dt})).code===0}let rs=null,os=null,qe=new Map,lr=0;async function sg(p,f){let g=p.join(",");if(rs===g)return!0;let w=await n(t,["network","inspect",xe]);if(w.code===0&&$o(w.stdout,p))return rs=g,f("network",null),!0;if(w.code===0){let k=(await N(n,t,["ps","--all","--filter",`network=${xe}`,"--format","{{.Names}}"])).trim();if(k!=="")return f("network",`WARNING: the ${xe} network does not route exactly to ${p.join(", ")}, and ${k.split(`
488
+ `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`),!1;await N(n,t,["network","rm",xe])}let S=await n(t,_o(p));return S.code!==0?(f("network",`WARNING: the ${xe} network could not be made: ${(S.stderr||S.stdout).trim().split(`
489
+ `)[0]}`),!1):(rs=g,f("network",null),f("made",null),f("made",`made the ${xe} network, routing only to ${p.join(", ")}`),!0)}async function ag(p,f={}){let g=await Bi(p),w=[...await ig(p),...await lg(p),...await cg(p,f.groupPeers===!0)],S=await Bi(p);return[...g,...w,...S]}let Jt=null;async function Bi(p){if(lr===0&&cr===0&&Jt===null&&!p.some(x=>ss(x)||(x.groupPeers??[]).length>0))return[];let f=await n(t,["network","ls","--format","json"]);if(f.code!==0)return[];let g=new Map;for(let x of f.stdout.trim()?JSON.parse(f.stdout):[])x.name&&Ya(x.name)&&g.set(x.name,x.subnets?.[0]?.subnet??"");if(g.size===0&&Jt===null)return[];let w=g.size===0?_p:kp(t_(p,g)),S=Date.now();if(Jt&&Jt.script===w&&S-Jt.at<15e3)return[];let k=await n(t,["unshare","--rootless-netns",Dm,"-f","-"],{stdin:w,timeoutMs:dt});if(k.code!==0){let x=`WARNING: the peer networks' filter could not be loaded: ${(k.stderr||k.stdout).trim().split(`
490
+ `)[0]}`;return qe.get("filter")!==x?(qe.set("filter",x),[x]):[]}return qe.delete("filter"),Jt=g.size===0?null:{script:w,at:S},[]}function ss(p){return p.guard!==void 0&&p.state!=="deleted"&&!!p.peerAddress}async function ig(p){let f=[...new Set(e.peers??[])].sort(),g=Wm(p.filter(D=>!ss(D)));if(f.length===0||g.length===0)return[];let w=[],S=(D,Y)=>{Y===null?qe.delete(D):qe.get(D)!==Y&&(qe.set(D,Y),w.push(Y))},k=await N(n,t,["ps","--format","json"]),x=new Set((k.trim()?JSON.parse(k):[]).flatMap(D=>D.Names??[])),_=g.filter(D=>x.has(D.container));if(_.length===0)return w;let I=f.join(",");if(os?.key!==I){let D=e.network?await n(t,["network","inspect",e.network]):null,Y=D?.code===0?Po(D.stdout,f):[];os={key:I,peers:f.filter(me=>!Y.includes(me))}}let j=os.peers;if(j.length===0||!await sg(j,S))return w;let z=await N(n,t,["inspect","--format","{{.Name}} {{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",..._.map(D=>D.container)]),L=new Set(z.split(`
491
+ `).map(D=>D.trim().split(/\s+/)).filter(D=>D.slice(1).includes(xe)).map(D=>D[0])),$=_.filter(D=>!L.has(D.container));if($.length===0)return w;let U=new Map(p.map(D=>[D.ref,D.group??D.ref])),F=await N(n,t,["ps","--all","--filter",`network=${xe}`,"--format","json"]),B=new Set((F.trim()?JSON.parse(F):[]).map(D=>D.Labels?.[je]).filter(D=>!!D).map(D=>U.get(D)??D));for(let D of $){let Y=Qk(D,B,$[0].group);if(Y){S(D.container,`${D.container} does not join ${xe}: ${Y}`);continue}let me=await n(t,["network","connect",xe,D.container]);if(me.code!==0){S(D.container,`WARNING: ${D.container} could not join the ${xe} network, so it has no route to ${j.join(", ")}: `+(me.stderr||me.stdout).trim().split(`
492
+ `)[0]);continue}B.add(D.group),S(D.container,null),w.push(`${D.container} joined the ${xe} network: its route to ${j.join(", ")} comes from there`)}return w}async function lg(p){let f=p.filter(ss),g=[],w=(L,$)=>{$===null?qe.delete(L):qe.get(L)!==$&&(qe.set(L,$),g.push($))};if(f.length===0&&lr===0)return g;let S=await n(t,["network","ls","--format","json"]);if(S.code!==0)return g;let k=new Map,x=[];for(let L of S.stdout.trim()?JSON.parse(S.stdout):[])L.name&&vo(L.name)!==null?k.set(L.name,L.subnets?.[0]?.subnet??""):L.name&&mn(L.name)!==null&&x.push(L.subnets?.[0]?.subnet??"");lr=k.size;let _=await N(n,t,["ps","--format","json"]),I=new Set((_.trim()?JSON.parse(_):[]).flatMap(L=>L.Names??[])),j=f.length>0&&e.network?await n(t,["network","inspect",e.network]):null,z=new Set;for(let L of f){let $=L.group??L.ref,U=bp($),F=H(L.ref).pod;z.add(U);let B=L.peerAddress;if(j?.code===0&&Po(j.stdout,[B]).length>0||!I.has(F))continue;let D=k.has(U)?Ht(k.get(U)):null,Y=!1;if(D){let ze=await n(t,["network","inspect",U]);Y=ze.code===0&&$o(ze.stdout,[B],D)}if(!Y&&k.has(U)){let ze=(await N(n,t,["ps","--all","--filter",`network=${U}`,"--format","{{.Names}}"])).trim();if(ze!==""){w(U,`WARNING: the ${U} network does not route exactly to ${B}, and ${ze.split(`
493
+ `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`);continue}await N(n,t,["network","rm",U]),k.delete(U)}if(!Y){let ze=D&&D.subnet||Va([...k.values(),...x]);if(D=ze?Ht(ze):null,!D){w(U,`WARNING: no /28 is free for the ${U} network, so ${F} has no route to ${B}`);continue}let Ee=await n(t,_o([B],{name:U,subnet:D.subnet,gateway:D.gateway}));if(Ee.code!==0){w(U,`WARNING: the ${U} network could not be made: ${(Ee.stderr||Ee.stdout).trim().split(`
494
+ `)[0]}`);continue}k.set(U,D.subnet),g.push(`made the ${U} network (${D.subnet}), routing only to ${B}`)}if((await N(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",F])).trim().split(/\s+/).includes(U)){w(U,null);continue}let Te=await n(t,["network","connect",U,F]);if(Te.code!==0){w(U,`WARNING: ${F} could not join the ${U} network, so it has no route to ${B}: ${(Te.stderr||Te.stdout).trim().split(`
495
+ `)[0]}`);continue}w(U,null),g.push(`${F} joined the ${U} network: its route to ${B} comes from there`)}for(let L of k.keys()){if(z.has(L))continue;(await N(n,t,["ps","--all","--filter",`network=${L}`,"--format","{{.Names}}"])).trim()===""&&(await n(t,["network","rm",L])).code===0&&(k.delete(L),g.push(`removed the ${L} network: its pair is no longer on this host`))}return lr=k.size,g}let cr=0;async function cg(p,f){let g=[],w=($,U)=>{U===null?qe.delete($):qe.get($)!==U&&(qe.set($,U),g.push(U))},S=e_(p,e.peers??[]);if(S.size===0&&cr===0)return g;let k=await n(t,["network","ls","--format","json"]);if(k.code!==0)return g;let x=new Map,_=[];for(let $ of k.stdout.trim()?JSON.parse(k.stdout):[]){let U=$.subnets?.[0]?.subnet??"";$.name&&Ya($.name)&&_.push(U),$.name&&mn($.name)!==null&&x.set($.name,U)}cr=x.size;let I=await N(n,t,["ps","--format","json"]),j=new Set((I.trim()?JSON.parse(I):[]).flatMap($=>$.Names??[])),z=S.size>0&&e.network?await n(t,["network","inspect",e.network]):null;for(let[$,U]of[...S].sort(([F],[B])=>F<B?-1:F>B?1:0)){if(z?.code===0&&Po(z.stdout,[U.peer]).length>0)continue;let F=U.containers.filter(Y=>j.has(Y));if(F.length===0)continue;let B=x.has($)?Ht(x.get($)):null,D=!1;if(B){let Y=await n(t,["network","inspect",$]);D=Y.code===0&&$o(Y.stdout,[U.peer],B)}if(!D&&x.has($)){let Y=(await N(n,t,["ps","--all","--filter",`network=${$}`,"--format","{{.Names}}"])).trim();if(Y!==""){w($,`WARNING: the ${$} network does not route exactly to ${U.peer}, and ${Y.split(`
496
+ `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`);continue}await N(n,t,["network","rm",$]),_.splice(_.indexOf(x.get($)),1),x.delete($)}if(!D){let Y=B&&B.subnet||Va(_);if(B=Y?Ht(Y):null,!B){w($,`WARNING: no /28 is free for the ${$} network, so ${F.join(", ")} has no route to ${U.peer}`);continue}let me=await n(t,_o([U.peer],{name:$,subnet:B.subnet,gateway:B.gateway}));if(me.code!==0){w($,`WARNING: the ${$} network could not be made: ${(me.stderr||me.stdout).trim().split(`
497
+ `)[0]}`);continue}x.set($,B.subnet),_.push(B.subnet),g.push(`made the ${$} network (${B.subnet}), routing only to ${U.peer}`)}for(let Y of F){if((await N(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",Y])).trim().split(/\s+/).includes($)){w(`${$} ${Y}`,null);continue}let Te=await n(t,["network","connect",$,Y]);if(Te.code!==0){w(`${$} ${Y}`,`WARNING: ${Y} could not join the ${$} network, so it has no route to ${U.peer}: ${(Te.stderr||Te.stdout).trim().split(`
498
+ `)[0]}`);continue}w(`${$} ${Y}`,null),g.push(`${Y} joined the ${$} network: its route to ${U.peer} comes from there`)}}let L=new Set(p.filter($=>$.state!=="deleted").map($=>$.group??$.ref));for(let $ of[...x.keys()]){if(S.has($))continue;let U=mn($);if(L.has(U.group)&&!f)continue;let F=(await N(n,t,["ps","--all","--filter",`network=${$}`,"--format","{{.Names}}"])).trim();for(let B of F===""?[]:F.split(`
499
+ `))(await n(t,["network","disconnect",$,B])).code===0&&g.push(`${B} left the ${$} network: ${U.peer} holds none of its cluster's pods`);(await n(t,["network","rm",$])).code===0&&(x.delete($),g.push(`removed the ${$} network: ${U.peer} holds none of its cluster's pods`))}return cr=x.size,g}async function dg(p,f){let g=await n(t,[...$e(p),"-tA","-v","ON_ERROR_STOP=1",...zu(f).flatMap(S=>["-c",S])],{timeoutMs:dt}),w=Number(g.stdout.trim().split(`
500
+ `).pop());return g.code===0&&Number.isInteger(w)?w:null}async function ug(p){let f=await n(t,[...$e(p),"-tAc","select pg_read_file(current_setting('ssl_cert_file')) where current_setting('ssl') = 'on'"]),g=`${f.stdout.trim()}
501
+ `;return f.code===0&&yo.test(g)?g:null}async function pg(p,f){if(!Ca.test(f))throw new Error("not a SCRAM-SHA-256 verifier");return Hi(p,{statements:[`do $$ begin if not exists (select 1 from pg_roles where rolname = '${Ot}') then create role ${Ot} with login replication; end if; end $$`,`alter role ${Ot} with login replication noinherit password '${f}'`],role:Ot,line:`hostssl replication ${Ot} all scram-sha-256`,replaceLine:!1})}async function mg(p,f,g,w=!0){let S=e.rootless??(typeof process.getuid!="function"||process.getuid()!==0);return Ma(g),Hi(p,{statements:Ju(f,w),role:un,line:Ma(S?pn:g),replaceLine:!0})}async function Hi(p,f){let g=H(p).pod,w=[...$e(p),"-v","ON_ERROR_STOP=1"];if((await n(t,[...w,...f.statements.flatMap(j=>["-c",j])],{timeoutMs:dt})).code!==0)return!1;let k=await n(t,[...w,"-tAc","show hba_file"]),x=k.stdout.trim();return k.code!==0||!/^\/[A-Za-z0-9/_.-]+$/.test(x)||(await n(t,["exec",g,"sh","-c",f.replaceLine?'{ grep -Ev "^host(ssl)?[[:space:]]+replication[[:space:]]+$2[[:space:]]" "$1"; printf "%s\\n" "$3"; } > "$1.next" && cat "$1.next" > "$1" && rm -f "$1.next"':'grep -Eq "^host(ssl)?[[:space:]]+replication[[:space:]]+$2[[:space:]]" "$1" || printf "%s\\n" "$3" >> "$1"',"sh",x,f.role,f.line])).code!==0||!await Ki(p,x.replace(/\/[^/]+$/,""))?!1:(await n(t,[...w,"-tAc","select pg_reload_conf()"])).code===0}async function Ki(p,f){let g=H(p).pod,w=[...$e(p),"-v","ON_ERROR_STOP=1"],S=await n(t,[...w,"-tAc","show ssl"]);return S.code===0&&S.stdout.trim()!=="on"&&((await n(t,["exec","--user","root",g,"sh","-c",'[ -s "$1/server.key" ] || { openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj "/CN=$2" -keyout "$1/server.key" -out "$1/server.crt" >/dev/null 2>&1 && chown postgres:postgres "$1/server.key" "$1/server.crt" && chmod 0600 "$1/server.key"; }',"sh",f,g])).code!==0||(await n(t,[...w,"-c","alter system set ssl_cert_file = 'server.crt'","-c","alter system set ssl_key_file = 'server.key'","-c","alter system set ssl = on"])).code!==0)?!1:S.code===0}async function fg(p,f){if(!yo.test(f.certificate))throw new Error("the primary's certificate is not one PEM certificate");let g=H(p).pod;for(let[w,S]of[[Qu,tp(f.password)],[ep,f.certificate]]){let k=await n(t,["exec","--interactive","--user","postgres",g,"sh","-c",'umask 077 && mkdir -p "$(dirname "$1")" && cat > "$1.next" && mv -f "$1.next" "$1"',"sh",w],{stdin:S,timeoutMs:dt});if(k.code!==0)throw new Error(`${w} could not be written in ${g}: ${(k.stderr||k.stdout).trim().slice(0,200)}`)}}return{list:a,ensureImage:fe,inRecovery:Ui,replayLsn:Kf,currentLsn:Gf,settleTimeline:qf,podHealth:zf,setSyncNames:Qf,...e.guardRoot?{syncGuards:Xf}:{},dropKeeperSlots:dg,bridgeContainers:eg,applyPodIsolation:og,joinPeerNetwork:ag,requestDiskQuota:tg,ensureTempFileLimit:ng,ensureMaxWalSize:rg,ensureKeeperRole:pg,ensureReplicaRole:mg,dropIdleReplicaSlot:Zf,writeFollowFiles:fg,podCertificate:ug,swapHomeData:Jf,adoptHomeData:Vf,promoteStandby:Yf,nodesAnswer:Wf,preflight:i,create:u,createServices:Wt,async start(p){let f=(await l(p).catch(()=>[])).filter(g=>g.running&&!g.separate&&!g.stack).map(g=>g.container);f.length>0&&await N(n,t,["stop","--time","5",...f]),await N(n,t,["start",H(p).pod])},async startServices(p){let{services:f,anchor:g}=await le(p);if(g!==null&&!g.running){let S=f.filter(k=>k.stack&&k.running).map(k=>k.container);S.length>0&&await N(n,t,["stop","--time","5",...S]),await N(n,t,["start",ct(p)])}let w=f.filter(S=>!S.stack||g!==null).map(S=>S.container);w.length>0&&await N(n,t,["start",...w])},async publishedPorts(p){return Pn(p)},async setAsideSidecars(p){let f=(await l(p)).filter(g=>!g.separate&&!g.stack&&!g.setAside);for(let g of f)await N(n,t,["rm","--force","--ignore",Bo(g.container)]),await N(n,t,["rename",g.container,Bo(g.container)]);return f.map(g=>g.name)},async setAsideStackServices(p){let f=(await l(p)).filter(g=>g.stack&&!g.setAside);for(let g of f)await N(n,t,["rm","--force","--ignore",Bo(g.container)]),await N(n,t,["rename",g.container,Bo(g.container)]);return f.map(g=>g.name)},async dropSetAside(p){let f=(await l(p)).filter(g=>g.setAside).map(g=>g.container);f.length>0&&await N(n,t,["rm","--force","--ignore",...f])},async routerAnswers(p,f){let g=H(p),w=Date.now()+f;for(;;){if((await n(t,["exec",g.pod,"pg_isready","--host",bt(p),"--port",String(Xa),"--dbname",g.database,"--timeout","2"])).code===0)return!0;if(Date.now()>=w)return!1;await new Promise(k=>setTimeout(k,500))}},async applyHostServices(p){let f=[],g=await N(n,t,["ps","--all","--filter",`label=${bn}`,"--format","json"]),w=g.trim()?JSON.parse(g):[],S=new Map;for(let _ of w){let I=_.Labels?.[bn];I&&S.set(I,{spec:_.Labels?.[Cm]??"",state:_.State??"",elsewhere:Lm(_,e.podBind),joins:_.Labels?.[Mm]||null})}let k=new Set;for(let _ of Mk(p)){let I=S.get(_.name);S.delete(_.name);try{await x(_,I)}catch(j){f.push({name:_.name,action:"failed",reason:j instanceof Error?j.message:String(j)})}}async function x(_,I){let j=_.joins?yn(_.joins):null;if(j!==null&&!p.some(Y=>Y.name===j&&!Y.joins))throw new Error(`it joins ${j}, which this host does not run`);let z=e.composeHostService?await e.composeHostService(_):_;if(Dk(z))throw new Error("it names host secrets, and this host cannot resolve them");let L=e.hostServiceLocal?.(z,p),$=L?{...z,...L.env&&Object.keys(L.env).length>0?{env:{...z.env,...L.env}}:{},...L.volumes?{volumes:L.volumes}:{},...L.network?{network:L.network}:{}}:z,U=Gt($.name),F=j===null?Lk($.name,p):[],B=Ck($,{joins:j,publishes:F});if(I&&I.spec===B&&!(I.state!=="running"&&I.elsewhere)){I.state!=="running"?(await N(n,t,["start",U]),f.push({name:$.name,action:"started",reason:I.state}),k.add($.name)):j!==null&&k.has(j)&&(await N(n,t,["restart",U]),f.push({name:$.name,action:"started",reason:`${j}, whose network it shares, was started again`}));return}let D=I?I.spec===B?"published on another instance's address":"the spec changed":"it was not here";if(await fe($.image),I){for(let[Y,me]of S)me.joins===$.name&&(await N(n,t,["rm","--force","--ignore",Gt(Y)]),S.delete(Y));await N(n,t,["rm","--force","--ignore",U])}await N(n,t,[...await r(),"run","--detach","--name",U,"--label",`${bn}=${$.name}`,"--label",`${Cm}=${B}`,...j!==null?["--label",`${Mm}=${j}`]:[],...$.healthPath&&$.hostPort>0?["--label",`${Go}=${$.hostPort}:${$.healthPath}`]:[],"--memory",`${$.memoryMb}m`,"--pids-limit",String(s_),"--restart","no",...j!==null?["--network",`container:${Gt(j)}`]:$.network?["--network",$.network]:e.network?["--network",e.network]:[],...($.volumes??[]).flatMap(Y=>["--volume",Y]),...j===null?[...$.hostPort>0?[[$.hostPort,$.port]]:[],...F].flatMap(([Y,me])=>kn(e.podBind).flatMap(Te=>["--publish",`${Te}:${Y}:${me}`])):[],...$.adminPort&&$.adminHostPort?["--publish",`127.0.0.1:${$.adminHostPort}:${$.adminPort}`]:[],...Ho($.env),$.image,...$.command??[]],{env:{...$.env}}),f.push({name:$.name,action:I?"recreated":"created",reason:D}),j===null&&k.add($.name)}for(let _ of[...S.keys()].sort((I,j)=>+!S.get(I)?.joins-+!S.get(j)?.joins))await N(n,t,["rm","--force","--ignore",Gt(_)]),f.push({name:_,action:"removed",reason:"it is no longer on the list"});return f},async hostServiceHealth(p){let f=await N(n,t,["ps","--all","--filter",`label=${bn}`,"--format","json"]),g=(f.trim()?JSON.parse(f):[]).map(S=>S.Labels?.[bn]??"").filter(S=>S!=="");if(g.length===0)return[];let w=Date.now()+p;for(;;){let S=[];for(let k of g)S.push(await Um(n,t,k,Gt(k),o));if(S.every(k=>k.ok)||Date.now()>=w)return S;await new Promise(k=>setTimeout(k,500))}},async serviceHealth(p,f){let g=await m(p);if(g.length===0)return[];let w=Date.now()+f;for(;;){let S=await Promise.all(g.map(k=>Um(n,t,k.name,k.container,o)));if(S.every(k=>k.ok)||Date.now()>=w)return S;await new Promise(k=>setTimeout(k,500))}},async drain(p,f,g){await new Promise(S=>setTimeout(S,f)),await n(t,[...$e(p),"-c","checkpoint"],{timeoutMs:dt});let w=Date.now()+g;for(;;){let S=await n(t,[...$e(p),"-tAc","select count(*) filter (where state <> 'idle') from (select state, case when state = 'idle' then pg_terminate_backend(pid) end from pg_stat_activity where backend_type = 'client backend' and pid <> pg_backend_pid()) s"]),k=S.code===0&&Number(S.stdout.trim())||0;if(k===0||Date.now()>=w)return{stillRunning:k};await new Promise(x=>setTimeout(x,250))}},async stop(p,f,g){let{services:w,anchor:S}=ue(await le(p),g);for(let k of w)await N(n,t,["stop","--time",String(Rm),k.container]);S!==null&&await N(n,t,["stop","--time",String(Rm),ct(p)]),await N(n,t,["stop","--time",String(f),H(p).pod])},async remove(p,f){let{services:g,anchor:w}=ue(await le(p),f);for(let S of g)await N(n,t,["rm","--force","--ignore",S.container]);w!==null&&await N(n,t,["rm","--force","--ignore",ct(p)]),await N(n,t,["rm","--force","--ignore","--volumes",H(p).pod])},async removeData(p){await N(n,t,["volume","rm","--force",H(p).pod]);let f=Xn(p);(await n(t,["volume","exists",f])).code===0&&await N(n,t,["volume","rm","--force",f])},async applyOwnerPassword(p){let f=await n(t,["exec",..._e,H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${se(p.ref)}_owner'`]);if(f.code===0&&Ha(f.stdout,p.ownerPassword))return;let g=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
502
+ `);await N(n,t,["exec",..._e,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:p.ownerPassword},stdin:g})},async applyRolePassword(p,f,g){let w=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${It(f)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
503
+ `);await N(n,t,["exec",..._e,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",H(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:g},stdin:w})},async applyRouterLogin(p,f){if(await N(n,t,["exec",..._e,"--interactive","--env","SNOUTPOD_ROUTER_PASSWORD",H(p).pod,"psql","--username","snoutpod_admin","--dbname",se(p),"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROUTER_PASSWORD:f??""},stdin:Ap(p,f!==null)}),f!==null){let g=[...$e(p),"-v","ON_ERROR_STOP=1"],w=await n(t,[...g,"-tAc","show data_directory"]),S=w.stdout.trim();if(w.code!==0||!/^\/[A-Za-z0-9/_.-]+$/.test(S)||!await Ki(p,S))throw new Error(`${p}: TLS for the copies' routers could not be turned on`);await N(n,t,[...g,"-tAc","select pg_reload_conf()"])}},async updateExtensions(p){let f=H(p),g=await n(t,["exec",..._e,f.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${f.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${f.database}' desc, datname limit 1`]),w=g.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(g.stdout.trim())?g.stdout.trim():f.database,S=await N(n,t,["exec",..._e,"--interactive",f.pod,"psql","--username","snoutpod_admin","--dbname",w,"--quiet","-tA","-F",li,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:sm});return am(S)},async applyDbAccess(p,f){let g=H(p),w=await N(n,t,["exec",..._e,"--interactive","--env",Do,g.pod,"psql","--username","snoutpod_admin","--dbname",g.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Do]:Hp(p,f)},stdin:Fp,timeoutMs:6e4});return Bp(w)},async runProjectSql(p,f){let g=H(p);await N(n,t,["exec",..._e,"--interactive",g.pod,"psql","--username","snoutpod_admin","--dbname",g.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:f})},async ensureHostServiceDatabase(p,f,g){let w=Gt(p),S=It(f),k=It(g),x=I=>["exec",w,"psql","--username",S,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",I];(await N(n,t,x(`select 1 from pg_database where datname = '${k}'`))).trim()!=="1"&&await N(n,t,x(`create database "${k}"`))},async renameProject(p,f){f!==p.ref&&await N(n,t,["exec",..._e,"--interactive",H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:p_(f,p.ref)})},async waitReady(p,f){let g=H(p).pod,w=Date.now()+f,S=0;for(;;){let k=await n(t,["exec",..._e,g,"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(p),!0;if(Date.now()>=w||await ji(p,++S))return!1;await new Promise(x=>setTimeout(x,500))}},async waitStandby(p,f){let g=Date.now()+f,w=0;for(;;){if(await Ui(p)===!0)return!0;if(Date.now()>=g||await ji(p,++w))return!1;await new Promise(S=>setTimeout(S,500))}},async capacity(){let[p,f]=await Promise.all([c(),e.capacity()]);return{pods:p,...f}},async databaseBytes(p){let f=await n(t,["exec",..._e,H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return f.code===0?Na(f.stdout):null},async storageBytes(p){let f=await n(t,["exec",..._e,H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-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 f.code===0?Na(f.stdout):null},async setReadOnly(p,f){let g=H(p);await N(n,t,["exec",..._e,g.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${g.database} set default_transaction_read_only = ${f?"on":"off"}`])},async archiveState(p){let f=await n(t,["exec",..._e,H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-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(f.code!==0)return null;let[g="",w=""]=f.stdout.trim().split(`
504
+ `)[0]?.split("|")??[],S=Number(w);return{lastArchivedAt:g.trim()||null,failures:Number.isInteger(S)&&S>=0?S:0}}}}async function Um(e,t,n,r,o=zm){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,c;try{let m=JSON.parse(s.stdout);a=m[0]?.State,c=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=Hk(c?.[Go]);if(!u)return{name:n,ok:!0,detail:"running"};let l=await o(u.port,u.path);return l===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${l}`}}async function c_(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 u_(e,t){return(e.State??"").toLowerCase()==="created"&&typeof e.Created=="number"&&e.Created>0&&t-e.Created*1e3>d_}function wi(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function p_(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" with allow_connections false;',"set snoutpod.rename_from = :'from_db';","do $$","declare"," db text := current_setting('snoutpod.rename_from');"," remaining text;","begin",` for attempt in 1..${Bm} loop`," perform pg_stat_clear_snapshot();"," select string_agg(coalesce(nullif(application_name, ''), backend_type) || ' (pid ' || pid || ')', ', ')"," into remaining from pg_stat_activity where datname = db and pid <> pg_backend_pid();"," if remaining is null then"," return;"," end if;"," perform pg_terminate_backend(pid) from pg_stat_activity where datname = db and pid <> pg_backend_pid();",` perform pg_sleep(${Fm/Bm});`," end loop;",` raise exception 'database % still has sessions after ${Fm}s, not renamed: %', db, remaining;`,"end","$$;",'alter database :"from_db" rename to :"to_db";','alter database :"to_db" with allow_connections true;','alter role :"from_role" rename to :"to_role";',"\\getenv admin_password POSTGRES_PASSWORD","\\if :{?admin_password}","alter role snoutpod_admin with password :'admin_password';","\\endif","alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;","checkpoint;",""].join(`
505
+ `)}function m_(e){if(!e?.issuer)return[];try{return["--env",`${lm}=${cm(e.issuer)}`]}catch{return[]}}function f_(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var je,hi,Ge,Sn,Qn,Am,Rm,Rk,Tk,Km,wn,Nm,bn,Cm,Mm,Gm,ki,Go,zm,Kk,Dm,_e,dt,zk,jm,n_,Jm,r_,o_,s_,l_,d_,Fm,Bm,Lo=W(()=>{"use strict";Le();Oa();Eo();wo();Ba();Ga();Ka();Zn();em();Ia();om();im();oi();dm();qa();gm();Fa();ri();bm();Co();xm();je="snoutpod.ref",hi="snoutpod.generation",Ge="snoutpod.service",Sn="snoutpod.services-generation",Qn="snoutpod.service-spec",Am="snoutpod.service-pod";Rm=10,Rk=/^[a-z][a-z0-9-]{0,30}$/;Tk=/^[a-z][a-z0-9_]{0,62}$/;Km="-set-aside",wn=1024*1024,Nm=/^[0-9a-f]{12,64}$/;bn="snoutpod.host-service",Cm="snoutpod.host-service-spec",Mm="snoutpod.host-service-joins";Gm=/^[A-Za-z_][A-Za-z0-9_]*$/;ki=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Go="snoutpod.health";zm=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()})},Kk="crun";Dm="/usr/sbin/nft",_e=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp -c synchronous_commit=local"],dt=3e4,zk=3e4;jm=200,n_=1024,Jm=512,r_=45e3,o_=1e3,s_=4096;l_=8;d_=12e4;Fm=30,Bm=300});var uf={};Ji(uf,{DEFAULT_LOCAL_MEMORY_MB:()=>ef,DEFAULT_LOCAL_PORT:()=>b_,ENGINE:()=>ie,ENGINE_NAME:()=>$i,IMAGE:()=>Ei,NETWORK:()=>Ct,NEW_PROJECT_MAJOR:()=>xi,NO_BLACKHOLE_ROUTES:()=>of,PUBLIC_IMAGE:()=>v_,PUBLISHED_MAJORS:()=>_n,capacity:()=>rf,chooseLocalImage:()=>rr,dropIfUnused:()=>cf,ensureImage:()=>$_,ensureNetwork:()=>R_,exec:()=>we,hasData:()=>Jo,localImageFor:()=>nf,localPsql:()=>N_,localRuntime:()=>A_,localSpec:()=>lf,localStatus:()=>Ai,localUri:()=>af,networkPolicyInPlace:()=>sf,newLocalPassword:()=>k_,newLocalRef:()=>S_,podLog:()=>df,podName:()=>vn,podmanReady:()=>x_,removeLocal:()=>M_,staleImage:()=>Pi,startLocal:()=>O_,stopLocal:()=>C_,volumePgVersion:()=>qo});import{randomBytes as Zm}from"node:crypto";import{existsSync as Ym}from"node:fs";import{platform as Wo}from"node:os";import{delimiter as g_,join as h_}from"node:path";function y_(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Wo()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(g_))if(t&&e.some(n=>Ym(h_(t,n))))return"podman";return Ym("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function rr(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return nr(xi);let n=Number(vi(e));if(!_n.includes(n)){let r=e.trim()||"(empty)",o=_n.map(s=>nr(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${_n.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return nr(n)}function nr(e){return{name:`${w_}:${e}`,published:`${Qm}:${e}`,major:e}}function S_(){return Uu(e=>Zm(e))}function k_(){return Zm(18).toString("base64url")}async function $_(e,t=we,n=rr(null)){let r=n.published;if(!r)return null;if((await t(ie,["image","exists",n.name])).code===0)return await P_(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(ie,["pull",r],{timeoutMs:tf}).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(ie,["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 P_(e,t,n,r){let o=await t(ie,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
506
+ `):[];if(!s||s!==a)return;let c=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(ie,["pull",r],{timeoutMs:tf}).catch(y=>({code:1,stdout:"",stderr:y instanceof Error?y.message:String(y)})).finally(()=>clearTimeout(c));if(i.code!==0){let y=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
507
+ `).at(-1);e?.(`Could not check for a newer database image (${y}); using the one on this machine.`);return}let u=await t(ie,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),l=u?.code===0?u.stdout.trim():"";if(!l||l===s)return;let m=await t(ie,["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 x_(e=__,t=Ei){if($i!=="podman")return`SNOUTPOD_ENGINE=${$i}: 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 we(ie,["--version"])}catch{return`${ie} 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`${ie} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await we(ie,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await we(ie,["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 E_(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 we(ie,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
508
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Jo(e){let t=Wo()==="darwin"||Wo()==="win32";return zp(we,e,{binary:ie,look:t?E_:void 0})}async function qo(e,t=we,n=Wo()){let r=H(e).pod;if((await t(ie,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of _n){let c=nr(a).name;if((await t(ie,["image","exists",c])).code===0){s=c;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 (${_n.map(a=>nr(a).published).join(" or ")}).`);o=await t(ie,["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(ie,["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(ie,["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 nf(e,t=qo){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(rr(null));se(e);let n=t===qo?zo.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return rr(o)}catch(s){throw new Error(`${vn(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===qo&&(zo.set(e,r),r.catch(()=>zo.delete(e))),r}async function rf(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function A_(e){return Vm({exec:we,binary:ie,network:Ct,hasData:Jo,capacity:rf,repository:e})}function vn(e){return H(e).pod}async function R_(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await T_()||await we(ie,xo({name:Ct}));return}let t=xo({name:Ct,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await sf())return;await e?.(),await we(ie,["network","rm","--force",Ct]).catch(()=>{});let o=await we(ie,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${of}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ct} with egress blocked${n?`: ${n}`:""}`)}async function T_(){return(await we(ie,["network","inspect",Ct,"--format","json"])).code===0}async function sf(){let e=await we(ie,["network","inspect",Ct,"--format","json"]);if(e.code!==0)return!1;try{return vp(JSON.parse(e.stdout)?.[0])}catch{return!1}}function af(e){let t=H(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function lf(e,t={}){return se(e.ref),{ref:e.ref,generation:1,image:t.image??Ei,memoryMb:t.memoryMb??ef,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 tr(e,t){e.onProgress?.(t)}async function O_(e,t,n={}){let r=n.image??(await nf(t.ref,n.pgVersion)).name,o=lf(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Jo,c=(await e.list()).find(u=>u.ref===t.ref),i=null;if(c?c.status==="stopped"&&(i=await(n.staleImage??Pi)(t.ref,r))?(tr(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??cf)(i)):c.status==="stopped"?(tr(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(tr(n,"The database is already running."),c.status==="running"&&await(n.staleImage??Pi)(t.ref,r)&&tr(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(tr(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 I_(t.ref,s,n.podLog??df));return await e.applyOwnerPassword(o),await e.createServices(o),Ai(e,t,n)}function Xm(e){return e.trim().replace(/^sha256:/,"")}async function Pi(e,t){let[n,r]=await Promise.all([we(ie,["container","inspect","--format","{{.Image}}",vn(e)]).catch(()=>null),we(ie,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Xm(n.stdout),s=Xm(r.stdout);return o&&s&&o!==s?o:null}async function cf(e){let t=await we(ie,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await we(ie,["rmi",e]).catch(()=>{})}async function N_(e,t){let n=H(e.ref);return we(ie,["exec","--interactive","-e","PGPASSWORD",vn(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 df(e){let t=await we(ie,["logs","--tail","20",vn(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function I_(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${vn(e)} never became ready in ${t}ms
467
509
  --- pod log ---
468
- ${r}`}async function w_(e,t,n={}){ae(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Qu),za(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function b_(e,t,n){ae(t),await e.remove(t),n&&(await e.removeData(t),yo.delete(t))}async function za(e,t,n={}){ae(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??bo;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?pm(t):null}}var Ka,oe,qa,rn,n_,sm,Wa,$t,r_,am,ye,a_,i_,im,yo,dm,ym=D(()=>{"use strict";ep();On();rp();Yr();ao();Te();Ka=process.env.SNOUTPOD_ENGINE??"podman";oe=t_(),qa=18,rn=[17,18],n_="snoutpod/postgres",sm="ghcr.io/snoutdata/snoutpod-postgres";Wa=Dn(null).name,$t=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",r_=54322,am=512,ye=tp();a_="cd packages/snoutpod && npm run image:build",i_=`${sm}:${qa}`;im=20*60*1e3;yo=new Map;dm="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});xe();var Me=["--admin","--token","--project"],Io=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],Dt=[...Me,"--plan","--no-wait","--timeout",...Io],Re=[...Dt,"--yes"],Co=[{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 scale",args:"[ref]",summary:"More capacity for your app's API (auth, the data API and the router): 1 to 3 instances. Shows the plan and asks first; without --instances, what your plan allows",flags:["--ref","--instances","--yes"]},{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:"ai",summary:"The project's own AI gateway: on or off, its models, budgets and the last 30 days' spend",flags:["--ref"]},{name:"ai enable",summary:"Switch the AI gateway on. Its container joins the pod, which restarts the database once",flags:["--ref"]},{name:"ai disable",summary:"Switch it off. Its providers, models, keys and log stay in the project's database",flags:["--ref"]},{name:"ai models",summary:"The models it serves: provider, upstream id, price and whether each is served",flags:["--ref"]},{name:"ai models add",args:"[name]",summary:"Add a model, with its provider and key when the provider is new. The key comes on stdin and the provider proves it first; without --model, print the steps to find the key and the model's id",flags:["--ref","--source","--model","--provider","--base-url","--region","--price-in","--price-out","--access-key-id","--auth-header","--stdin"]},{name:"ai models remove",args:"<name>",summary:"Stop serving a model",flags:["--ref"]},{name:"ai providers",summary:"Where its models run, and whether each one's key is stored (never the key)",flags:["--ref"]},{name:"ai providers remove",args:"<name>",summary:"Remove a provider no model uses",flags:["--ref"]},{name:"ai routes",summary:"Names that stand for several models, and what each tries in which order",flags:["--ref"]},{name:"ai routes set",args:"<name>",summary:"Make or replace a route: its models, tried in turn when one fails for a reason that is not the caller's",flags:["--ref","--models","--sort"]},{name:"ai routes remove",args:"<name>",summary:"Remove a route; its models stay",flags:["--ref"]},{name:"ai keys",summary:"Keys a server calls the gateway with: created, last used, revoked",flags:["--ref"]},{name:"ai keys create",summary:"Make a key for a server, shown once",flags:["--ref","--name","--models","--expires"]},{name:"ai keys revoke",args:"<id>",summary:"Refuse a key from now on",flags:["--ref"]},{name:"ai budgets",summary:"What may be spent, and by whom",flags:["--ref"]},{name:"ai budgets set",args:"project|users",summary:"The project's budget, or every signed-in user's. A browser call needs both, hard",flags:["--ref","--amount","--period","--soft"]},{name:"ai budgets remove",args:"<id>",summary:"Remove a budget",flags:["--ref"]},{name:"ai usage",summary:"Requests, tokens and cost, grouped",flags:["--ref","--by","--days"]},{name:"ai test",summary:"One chat call through the project's door, with a key made for it and revoked after",flags:["--ref","--model","--prompt"]},{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:[...Me]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...Me]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...Me]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router and the services start in pods of their own while the database keeps running. 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, the router stops and the services move back into the database 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:[...Dt]},{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:[...Dt,"--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:[...Re,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...Re]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...Dt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...Dt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...Re,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...Re]},{name:"cluster autopilot",summary:"The autopilot, off unless you set it: say its mode and what it decided last, or set it to off, advise (record what it would run) or auto (run it); auto asks, or takes --yes",flags:["--project","--interval-ms","--yes"]},{name:"cluster table auto",summary:"Choose a distribution key for every table from the schema (the column most tables reference, else the primary key), say why for each, and distribute what it can in one job",flags:[...Re,"--keyspace","--schemas","--min-bytes"]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...Re]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...Re,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...Re]},{name:"cluster range move",summary:"Give a range to another node",flags:[...Re,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...Re,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...Re,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...Re,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...Dt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...Re,"--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:[...Re]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...Me,...Io]},{name:"cluster jobs",summary:"The latest jobs",flags:[...Me]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...Me]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...Me,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...Me,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...Me,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...Me,...Io,"--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:[...Me,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],ui={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","projects scale --instances":"How many instances of the app API: 1 to what your plan allows (2 on Pro, 3 on Business). Your database is not copied","projects scale --yes":"Apply the plan without asking. Needed when nothing can ask (a script, CI)","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","--source":"Where the model runs: bedrock, anthropic, openai, openrouter, ollama or compatible","--model":"The model's id, as the provider names it","ai test --model":"The model to call (default: the first chat model served)","ai models add --provider":"The provider to add it to, or the name of a new one (default: the one already connected for --source)","ai models add --region":"Bedrock: the AWS region the model runs in","ai models add --stdin":"Read the provider's key from a pipe (AWS: the secret access key), so it is in neither the shell history nor ps","--base-url":"The provider's OpenAI-compatible address, usually ending in /v1","--price-in":"Dollars per million input tokens. Both rates, or neither to take the shipped catalogue's","--price-out":"Dollars per million output tokens","--access-key-id":"Bedrock with AWS credentials: the access key id (the secret on stdin)","--auth-header":"A header the server checks for its key, instead of a Bearer token","--models":"Comma-separated models the key may call (default: every one)","ai keys create --name":"What the key is for, shown in ai keys","ai routes set --models":"The models to try, comma-separated, in order","ai routes set --sort":"order (as written, the default), price (the cheapest first), or latency or throughput (the fastest first, as measured)","ai keys create --expires":"Days until the key stops working. Without it, it works until revoked","--amount":"Dollars","--period":"day (the default) or month","--soft":"Warn in the log when it is passed, instead of refusing","--by":"Group by day, model, provider, operation, key, user or group (default model)","ai usage --days":"How many days, up to 366 (default 30)","--prompt":"What to ask (default: a five-word hello)","--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","--interval-ms":"How often the autopilot asks the advisor, in milliseconds (default 60000, at least 5000)","--schemas":"Comma-separated schemas whose tables table auto reads (default public)","--min-bytes":"The size below which table auto leaves a table keyed only by its own primary key unspread (default 16 MiB)","--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 Do(e,t){return ui[`${e} ${t}`]??ui[t]??""}function pi(e,t){let n=Co.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}pe();we();le();import{spawn as _g}from"node:child_process";function kg(e){return`snout-${e}`}function Kt(e,t,n){return new Promise((r,o)=>{let s=_g("docker",["compose","-p",kg(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",c="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(c+=String(i),n)for(let u of String(i).split(/\r?\n/).filter(Boolean))d(b(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new T("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 T("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:c}))})}async function vg(e){let t=await Kt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new T("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 Qi(e){let t=await vg(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return w({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${O(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${W([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
469
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function el(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await Kt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new T("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return w({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 tl(e){let t=await Kt(e,["stop"],!0);if(t.code!==0)throw new T("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return w({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function nl(e){w({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${W([["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}`]])}
470
- `)})}ln();import{cpSync as sl,existsSync as yn,readdirSync as Og,readFileSync as al,rmSync as il,statSync as Ng,writeFileSync as ll}from"node:fs";import{join as Le,resolve as gr}from"node:path";var $g=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Pg(e){return $g.test(e)}function At(e){if(!Pg(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 xg=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Eg(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!==".."&&xg.test(n))}var Qo=2*1024*1024,rl=200;function ol(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>rl)throw new Error(`a function may hold ${rl} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Eg(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,c)=>a.path<c.path?-1:a.path>c.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=Ag(o);if(s.length>Qo)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Qo/1024} KB. A function is source code; put large files in Storage.`);return o}function Ag(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 Rg=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Tg=["SNOUTDATA_","SNOUT_"];function hn(e){if(!Rg.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Tg.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 Tv=64*1024;we();xe();le();function cl(e){let t=Le(e.folder,"functions");return yn(t)?Og(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Ng(Le(t,n)).isDirectory()).sort():[]}async function hr(e){d(b(" docker compose run --rm functions-deploy"));let t=await Kt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new T("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function dl(e,t,n){let r=Le(e.folder,".env"),o=al(r,"utf8"),s=(Wn(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 c=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return ll(r,i.test(o)?o.replace(i,c):`${o.replace(/\n?$/,`
510
+ ${r}`}async function C_(e,t,n={}){se(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Kp),Ai(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function M_(e,t,n){se(t),await e.remove(t),n&&(await e.removeData(t),zo.delete(t))}async function Ai(e,t,n={}){se(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Jo;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?af(t):null}}var $i,ie,xi,_n,w_,Qm,Ei,Ct,b_,ef,we,__,v_,tf,zo,of,pf=W(()=>{"use strict";Gp();Zn();Wp();Eo();Lo();Le();$i=process.env.SNOUTPOD_ENGINE??"podman";ie=y_(),xi=18,_n=[17,18],w_="snoutpod/postgres",Qm="ghcr.io/snoutdata/snoutpod-postgres";Ei=rr(null).name,Ct=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",b_=54322,ef=512,we=qp();__="cd packages/snoutpod && npm run image:build",v_=`${Qm}:${xi}`;tf=20*60*1e3;zo=new Map;of="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});Ae();var Be=["--admin","--token","--project"],is=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],Vt=[...Be,"--plan","--no-wait","--timeout",...is],Ce=[...Vt,"--yes"],ls=[{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","--profile","--timeout"]},{name:"logout",summary:"Forget the stored session, of this profile or of every one",flags:["--profile","--all"]},{name:"whoami",summary:"Who this credential belongs to, and whether it is the account login --email asked for; --all lists every profile",flags:["--profile","--all"]},{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","--servers","--size","--max-size","--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 scale",args:"[ref]",summary:"More capacity for your app's API (auth, the data API and the router): 1 to 3 instances. Shows the plan and asks first; without --instances, what your plan allows",flags:["--ref","--instances","--yes"]},{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:"ai",summary:"The project's own AI gateway: on or off, its models, budgets and the last 30 days' spend",flags:["--ref"]},{name:"ai enable",summary:"Switch the AI gateway on. Its container joins the pod, which restarts the database once",flags:["--ref"]},{name:"ai disable",summary:"Switch it off. Its providers, models, keys and log stay in the project's database",flags:["--ref"]},{name:"ai models",summary:"The models it serves: provider, upstream id, price and whether each is served",flags:["--ref"]},{name:"ai models add",args:"[name]",summary:"Add a model, with its provider and key when the provider is new. The key comes on stdin and the provider proves it first; without --model, print the steps to find the key and the model's id",flags:["--ref","--source","--model","--provider","--base-url","--region","--price-in","--price-out","--access-key-id","--auth-header","--stdin"]},{name:"ai models remove",args:"<name>",summary:"Stop serving a model",flags:["--ref"]},{name:"ai providers",summary:"Where its models run, and whether each one's key is stored (never the key)",flags:["--ref"]},{name:"ai providers remove",args:"<name>",summary:"Remove a provider no model uses",flags:["--ref"]},{name:"ai routes",summary:"Names that stand for several models, and what each tries in which order",flags:["--ref"]},{name:"ai routes set",args:"<name>",summary:"Make or replace a route: its models, tried in turn when one fails for a reason that is not the caller's",flags:["--ref","--models","--sort"]},{name:"ai routes remove",args:"<name>",summary:"Remove a route; its models stay",flags:["--ref"]},{name:"ai keys",summary:"Keys a server calls the gateway with: created, last used, revoked",flags:["--ref"]},{name:"ai keys create",summary:"Make a key for a server, shown once",flags:["--ref","--name","--models","--expires"]},{name:"ai keys revoke",args:"<id>",summary:"Refuse a key from now on",flags:["--ref"]},{name:"ai budgets",summary:"What may be spent, and by whom",flags:["--ref"]},{name:"ai budgets set",args:"project|users",summary:"The project's budget, or every signed-in user's. A browser call needs both, hard",flags:["--ref","--amount","--period","--soft"]},{name:"ai budgets remove",args:"<id>",summary:"Remove a budget",flags:["--ref"]},{name:"ai usage",summary:"Requests, tokens and cost, grouped",flags:["--ref","--by","--days"]},{name:"ai test",summary:"One chat call through the project's door, with a key made for it and revoked after",flags:["--ref","--model","--prompt"]},{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, and at the profile it was linked with",flags:["--ref","--profile"]},{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 and the services start in pods of their own while the database keeps running. 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, the router stops and the services move back into the database 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:[...Vt]},{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:[...Vt,"--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","--distribution-key","--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:[...Ce,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...Ce]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...Vt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...Vt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...Ce,"--distribution-key","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...Ce]},{name:"cluster autopilot",summary:"The autopilot, off unless you set it: say its mode and what it decided last, or set it to off, advise (record what it would run) or auto (run it); auto asks, or takes --yes",flags:["--project","--interval-ms","--yes"]},{name:"cluster table auto",summary:"Choose a distribution key for every table from the schema (the column most tables reference, else the primary key), say why for each, and distribute what it can in one job",flags:[...Ce,"--keyspace","--schemas","--min-bytes"]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...Ce]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...Ce,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...Ce]},{name:"cluster range move",summary:"Give a range to another node",flags:[...Ce,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...Ce,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...Ce,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...Ce,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...Vt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...Ce,"--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:[...Ce]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...Be,...is]},{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,...is,"--set"]},{name:"cluster check",summary:"The design check: whether every table's distribution key, foreign keys, unique keys and indexes let the cluster keep related rows on one node, each finding with its fix (SQL, or the command that does it). Runs nothing; exits 1 when something must be fixed",flags:[...Be]},{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"]}],Yi={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","--profile":"Which stored sign-in to use, one per account. Defaults to SNOUTDATA_PROFILE, then the folder's link, then the default","login --profile":"Keep this sign-in as a profile of its own, beside the others; later commands pick it with --profile","link --profile":"Record the profile in the link, so every command in this folder signs in as that account","logout --all":"Forget the session of every profile on this machine","whoami --all":"List every profile signed in on this machine and whose it is, from the stored files, without calling out","projects scale --instances":"How many instances of the app API: 1 to what your plan allows (2 on Pro, 3 on Business). Your database is not copied","projects scale --yes":"Apply the plan without asking. Needed when nothing can ask (a script, CI)","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","projects create --servers":"shared (the default) or dedicated: your account's own servers, when we have set that up with you","projects create --max-size":"A dedicated project's largest size: it grows up to it under load and back down when quiet. Defaults to --size","projects create --size":"A dedicated project's server size: small, medium or large. The first project of a size starts its servers (about 5 minutes)","--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","--source":"Where the model runs: bedrock, anthropic, openai, openrouter, ollama or compatible","--model":"The model's id, as the provider names it","ai test --model":"The model to call (default: the first chat model served)","ai models add --provider":"The provider to add it to, or the name of a new one (default: the one already connected for --source)","ai models add --region":"Bedrock: the AWS region the model runs in","ai models add --stdin":"Read the provider's key from a pipe (AWS: the secret access key), so it is in neither the shell history nor ps","--base-url":"The provider's OpenAI-compatible address, usually ending in /v1","--price-in":"Dollars per million input tokens. Both rates, or neither to take the shipped catalogue's","--price-out":"Dollars per million output tokens","--access-key-id":"Bedrock with AWS credentials: the access key id (the secret on stdin)","--auth-header":"A header the server checks for its key, instead of a Bearer token","--models":"Comma-separated models the key may call (default: every one)","ai keys create --name":"What the key is for, shown in ai keys","ai routes set --models":"The models to try, comma-separated, in order","ai routes set --sort":"order (as written, the default), price (the cheapest first), or latency or throughput (the fastest first, as measured)","ai keys create --expires":"Days until the key stops working. Without it, it works until revoked","--amount":"Dollars","--period":"day (the default) or month","--soft":"Warn in the log when it is passed, instead of refusing","--by":"Group by day, model, provider, operation, key, user or group (default model)","ai usage --days":"How many days, up to 366 (default 30)","--prompt":"What to ask (default: a five-word hello)","--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","--distribution-key":"The distribution key: the column whose value decides which node holds each row (not the primary key, though the primary key must include it). --column is its old name","--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","--interval-ms":"How often the autopilot asks the advisor, in milliseconds (default 60000, at least 5000)","--schemas":"Comma-separated schemas whose tables table auto reads (default public)","--min-bytes":"The size below which table auto leaves a table keyed only by its own primary key unspread (default 16 MiB)","--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 cs(e,t){return Yi[`${e} ${t}`]??Yi[t]??""}function Xi(e,t){let n=ls.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}pe();be();de();import{spawn as Ah}from"node:child_process";function Rh(e){return`snout-${e}`}function rn(e,t,n){return new Promise((r,o)=>{let s=Ah("docker",["compose","-p",Rh(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",c="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(c+=String(i),n)for(let u of String(i).split(/\r?\n/).filter(Boolean))d(v(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new C("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 C("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:c}))})}async function Th(e){let t=await rn(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new C("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 Ul(e){let t=await Th(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return b({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${M(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${X([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
511
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Fl(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await rn(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new C("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return b({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 Bl(e){let t=await rn(e,["stop"],!0);if(t.code!==0)throw new C("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return b({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Hl(e){b({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${X([["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}`]])}
512
+ `)})}An();import{cpSync as ql,existsSync as Ln,readdirSync as jh,readFileSync as zl,rmSync as Wl,statSync as Uh,writeFileSync as Jl}from"node:fs";import{join as Je,resolve as Ur}from"node:path";var Oh=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Nh(e){return Oh.test(e)}function jt(e){if(!Nh(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 Ih=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Ch(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!==".."&&Ih.test(n))}var Os=2*1024*1024,Kl=200;function Gl(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>Kl)throw new Error(`a function may hold ${Kl} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Ch(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,c)=>a.path<c.path?-1:a.path>c.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=Mh(o);if(s.length>Os)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Os/1024} KB. A function is source code; put large files in Storage.`);return o}function Mh(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 Dh=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Lh=["SNOUTDATA_","SNOUT_"];function Dn(e){if(!Dh.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Lh.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 z$=64*1024;be();Ae();de();function Vl(e){let t=Je(e.folder,"functions");return Ln(t)?jh(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Uh(Je(t,n)).isDirectory()).sort():[]}async function Fr(e){d(v(" docker compose run --rm functions-deploy"));let t=await rn(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new C("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Yl(e,t,n){let r=Je(e.folder,".env"),o=zl(r,"utf8"),s=(hr(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 c=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return Jl(r,i.test(o)?o.replace(i,c):`${o.replace(/\n?$/,`
471
513
  `)}${c}
472
- `),!0}async function es(e,t,n){At(t);let r=gr(n.dir??Le(process.cwd(),"functions",t));if(!yn(Le(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=Le(e.folder,"functions",t);if(gr(o)!==r){il(o,{recursive:!0,force:!0}),sl(r,o,{recursive:!0});let a=Le(r,"..","_shared");yn(a)&&gr(a)!==gr(Le(e.folder,"functions","_shared"))&&sl(a,Le(e.folder,"functions","_shared"),{recursive:!0})}let s=dl(e,t,!n.verifyJwt);if(await hr(e),s){let a=await Kt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new T("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:ts(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function ul(e,t,n){let r=await es(e,t,n);w(r,()=>{d(`${O(t)} deployed locally: ${r.url}`),n.verifyJwt||d(b(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function ts(e,t){return`${e.apiUrl}/functions/v1/${t}`}function yr(e){return{ref:e.ref,local:e.folder,functions:cl(e).map(t=>({name:t,url:ts(e,t)}))}}function pl(e){let t=cl(e);w(yr(e),()=>{if(t.length===0){d(`No functions in ${Le(e.folder,"functions")}.`);return}process.stdout.write(`${W([["NAME","URL"],...t.map(n=>[n,ts(e,n)])])}
473
- `)})}async function ns(e,t){At(t);let n=Le(e.folder,"functions",t);if(!yn(n))throw new T("not-found",`this local project has no function called ${t}`);return il(n,{recursive:!0,force:!0}),dl(e,t,!1),await hr(e),{ref:e.ref,name:t,removed:!0}}async function ml(e,t){w(await ns(e,t),()=>d(`${t} is gone.`))}function wr(e){return Le(e.folder,"functions",".env")}function rs(e){let t=wr(e);return yn(t)?Wn(al(t,"utf8")):new Map}function fl(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;ll(wr(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
514
+ `),!0}async function Ns(e,t,n){jt(t);let r=Ur(n.dir??Je(process.cwd(),"functions",t));if(!Ln(Je(r,"index.ts")))throw new h(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=Je(e.folder,"functions",t);if(Ur(o)!==r){Wl(o,{recursive:!0,force:!0}),ql(r,o,{recursive:!0});let a=Je(r,"..","_shared");Ln(a)&&Ur(a)!==Ur(Je(e.folder,"functions","_shared"))&&ql(a,Je(e.folder,"functions","_shared"),{recursive:!0})}let s=Yl(e,t,!n.verifyJwt);if(await Fr(e),s){let a=await rn(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new C("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:Is(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function Xl(e,t,n){let r=await Ns(e,t,n);b(r,()=>{d(`${M(t)} deployed locally: ${r.url}`),n.verifyJwt||d(v(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Is(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Br(e){return{ref:e.ref,local:e.folder,functions:Vl(e).map(t=>({name:t,url:Is(e,t)}))}}function Zl(e){let t=Vl(e);b(Br(e),()=>{if(t.length===0){d(`No functions in ${Je(e.folder,"functions")}.`);return}process.stdout.write(`${X([["NAME","URL"],...t.map(n=>[n,Is(e,n)])])}
515
+ `)})}async function Cs(e,t){jt(t);let n=Je(e.folder,"functions",t);if(!Ln(n))throw new C("not-found",`this local project has no function called ${t}`);return Wl(n,{recursive:!0,force:!0}),Yl(e,t,!1),await Fr(e),{ref:e.ref,name:t,removed:!0}}async function Ql(e,t){b(await Cs(e,t),()=>d(`${t} is gone.`))}function Hr(e){return Je(e.folder,"functions",".env")}function Ms(e){let t=Hr(e);return Ln(t)?hr(zl(t,"utf8")):new Map}function ec(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Jl(Hr(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
474
516
  `)+(t.size?`
475
- `:""))}async function gl(e,t){for(let r of t){let o=hn(r.name);if(o)throw new g(o)}let n=rs(e);for(let r of t)n.set(r.name,r.value);fl(e,n),await hr(e),w({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${O(r.name)} set in ${wr(e)}`)})}function br(e){return{ref:e.ref,local:e.folder,secrets:[...rs(e).keys()].sort().map(t=>({name:t}))}}function hl(e){let t=br(e),n=t.secrets.map(r=>r.name);w(t,()=>{d(n.length?n.join(`
476
- `):`No function secrets in ${wr(e)}.`)})}async function yl(e,t){let n=rs(e);if(!n.delete(t))throw new T("not-found",`${t} is not set for this local project`);fl(e,n),await hr(e),w({ref:e.ref,unset:t},()=>d(`${t} unset.`))}we();pt();ct();qo();Zn();le();ur();pe();pe();we();ct();le();async function wl(){let{projects:e,allowance:t}=await je();w({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${W(Ig(e))}
477
- `)})}function Ig(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?O("read-only"):n.state==="ready"?n.state:O(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],st(n.lastConnectionAt)])]}async function os(e){let t=await P("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 gn(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(b(` ${a}`)))}})}e.link&&jt(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;w({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}
478
- `),d(""),d(`Ready. ${O(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 bl(e,t,n={}){let r=`cloud-project-${e}`,o=await P(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){w({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",c=await fn(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(b(` ${u}`)))}}),i=c?.state??"deleted";if(i==="error")throw new T("failed",`${t} went to error instead of ${s}${c?.stateDetail?`: ${c.stateDetail}`:""}.`,{ref:t,state:i});w({...o,...c?{project:c}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Sl(e){let t=await P("cloud-project-reset-password",{ref:e});w(t,()=>{process.stdout.write(`${t.password}
479
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function _l(){let{teams:e}=await mr();w({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${W([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
480
- `)})}async function kl(e){let{teams:t}=await mr(),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}pe();xe();we();pt();le();import{createInterface as Dg}from"node:readline/promises";var Ve={which:"--instances takes 1, 2 or 3",now:(e,t)=>`${e} runs its app API on ${t===1?"1 instance":`${t} instances`}.`,most:e=>e<=1?"More than 1 instance is on the Pro and Business plans.":`Your plan allows up to ${e} instances.`,notOpen:"Scaling is not open to this account yet.",nothing:e=>`${e} already runs that many instances. Nothing was changed.`,ask:(e,t)=>`Scale ${e} to ${t===1?"1 instance":`${t} instances`}?`,noAsk:"scaling changes where the app API runs, and nothing was asked: pass --yes to apply it",declined:"Not scaled. Nothing was changed.",done:(e,t)=>`${e}: its app API now runs on ${t===1?"1 instance":`${t} instances`}.`},Mg={plan:"quota","not-allowed":"forbidden",sharded:"conflict","not-ready":"conflict","owner-cap":"quota","too-soon":"not-ready",busy:"not-ready",full:"no-capacity"};function vl(e){let t=Number(e);if(!/^[123]$/.test(e.trim())||!Number.isInteger(t))throw new g(Ve.which);return t}async function jg(e){let t=Dg({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()}}async function $l(e,t){if(t.instances===void 0){let s=await P("cloud-project-spread",{ref:e,preview:!0});w(s,()=>{d(Ve.now(s.ref,s.current)),d(s.what),d(Ve.most(s.most)),s.most>s.current&&!s.open&&d(Ve.notOpen)});return}let n=t.instances;if(!t.yes&&!t.ask&&!be())throw new g(Ve.noAsk);let r=await P("cloud-project-spread",{ref:e,preview:!0,instances:n});if(r.outcome==="refused")throw new T(Mg[r.code??""]??"conflict",r.error??Ve.notOpen,{ref:e,instances:n,code:r.code});if(r.outcome==="none"){w({ref:r.ref,instances:n,changed:!1},()=>d(Ve.nothing(r.ref)));return}if(process.stderr.write(`${r.what}
517
+ `:""))}async function tc(e,t){for(let r of t){let o=Dn(r.name);if(o)throw new h(o)}let n=Ms(e);for(let r of t)n.set(r.name,r.value);ec(e,n),await Fr(e),b({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${M(r.name)} set in ${Hr(e)}`)})}function Kr(e){return{ref:e.ref,local:e.folder,secrets:[...Ms(e).keys()].sort().map(t=>({name:t}))}}function nc(e){let t=Kr(e),n=t.secrets.map(r=>r.name);b(t,()=>{d(n.length?n.join(`
518
+ `):`No function secrets in ${Hr(e)}.`)})}async function rc(e,t){let n=Ms(e);if(!n.delete(t))throw new C("not-found",`${t} is not set for this local project`);ec(e,n),await Fr(e),b({ref:e.ref,unset:t},()=>d(`${t} unset.`))}be();Pt();_t();vs();_r();de();Mr();pe();pe();Ae();be();_t();de();async function oc(){let{projects:e,allowance:t}=await We();b({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${X(Fh(e))}
519
+ `)})}function Fh(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?M("read-only"):n.state==="ready"?n.state:M(n.state)}${n.serverSilent?` ${M("(server not answering)")}`:""}`,n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],gt(n.lastConnectionAt)])]}async function Ds(e){let t=e.servers==="dedicated";if(e.servers!==void 0&&e.servers!=="dedicated"&&e.servers!=="shared")throw new h("--servers is shared or dedicated");if(t!==(e.size!==void 0))throw new h(t?"--servers dedicated needs --size small, medium or large":"--size goes with --servers dedicated");let n=await R("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId,...t?{placement:"dedicated",size:e.size,...e.maxSize?{maxSize:e.maxSize}:{}}:{}}),r=n.project.ref;d(`Created ${r}.`);let o=n.project;if(e.wait){d("Waiting for it to start\u2026");let a="";o=await Mn(r,{timeoutMs:e.timeoutMs??(t?15*6e4:void 0),onTick:c=>{c!==a&&(a=c,d(v(` ${c}`)))}})}e.link&&Xt(e.link,{ref:r,name:o.name});let s=`postgres://${o.user}:${encodeURIComponent(n.password)}@${o.host}:5432/${o.database}?sslmode=require`;b({project:o,ref:r,host:o.host,database:o.database,user:o.user,...e.showUrl?{password:n.password,uri:s}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?s:r}
520
+ `),d(""),d(`Ready. ${M(r)} in ${o.region}, at ${o.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 sc(e,t,n={}){let r=`cloud-project-${e}`,o=await R(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){b({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",c=await Cn(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(v(` ${u}`)))}}),i=c?.state??"deleted";if(i==="error")throw new C("failed",`${t} went to error instead of ${s}${c?.stateDetail?`: ${c.stateDetail}`:""}.`,{ref:t,state:i});b({...o,...c?{project:c}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ac(e){let t=await R("cloud-project-reset-password",{ref:e});b(t,()=>{process.stdout.write(`${t.password}
521
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function ic(){let{teams:e}=await Lr();b({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${X([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
522
+ `)})}async function lc(e){let{teams:t}=await Lr(),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}pe();Ae();be();Pt();de();import{createInterface as Hh}from"node:readline/promises";var ot={which:"--instances takes 1, 2 or 3",now:(e,t)=>`${e} runs its app API on ${t===1?"1 instance":`${t} instances`}.`,most:e=>e<=1?"More than 1 instance is on the Pro and Business plans.":`Your plan allows up to ${e} instances.`,notOpen:"Scaling is not open to this account yet.",nothing:e=>`${e} already runs that many instances. Nothing was changed.`,ask:(e,t)=>`Scale ${e} to ${t===1?"1 instance":`${t} instances`}?`,noAsk:"scaling changes where the app API runs, and nothing was asked: pass --yes to apply it",declined:"Not scaled. Nothing was changed.",done:(e,t)=>`${e}: its app API now runs on ${t===1?"1 instance":`${t} instances`}.`},Kh={plan:"quota","not-allowed":"forbidden",sharded:"conflict","not-ready":"conflict","owner-cap":"quota","too-soon":"not-ready",busy:"not-ready",full:"no-capacity"};function cc(e){let t=Number(e);if(!/^[123]$/.test(e.trim())||!Number.isInteger(t))throw new h(ot.which);return t}async function Gh(e){let t=Hh({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()}}async function dc(e,t){if(t.instances===void 0){let s=await R("cloud-project-spread",{ref:e,preview:!0});b(s,()=>{d(ot.now(s.ref,s.current)),d(s.what),d(ot.most(s.most)),s.most>s.current&&!s.open&&d(ot.notOpen)});return}let n=t.instances;if(!t.yes&&!t.ask&&!Se())throw new h(ot.noAsk);let r=await R("cloud-project-spread",{ref:e,preview:!0,instances:n});if(r.outcome==="refused")throw new C(Kh[r.code??""]??"conflict",r.error??ot.notOpen,{ref:e,instances:n,code:r.code});if(r.outcome==="none"){b({ref:r.ref,instances:n,changed:!1},()=>d(ot.nothing(r.ref)));return}if(process.stderr.write(`${r.what}
481
523
  ${r.message?`${r.message}
482
- `:""}`),!t.yes&&!await(t.ask??jg)(Ve.ask(r.ref,n)))throw new T("failed",Ve.declined);let o=await P("cloud-project-spread",{ref:e,instances:n});w({ref:o.ref,instances:o.copies,changed:o.changed,...o.preparing?{preparing:!0,message:o.message}:{}},()=>{d(Ve.done(o.ref,o.copies)),o.message&&d(o.message)})}pe();le();async function Pl(e){let t=await P("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});w(t,()=>{process.stdout.write(`${t.token}
483
- `),d(""),d(`${O(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(b(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function xl(){let{tokens:e}=await P("cloud-token-list");w({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${W([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?O(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",st(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
484
- `)})}async function El(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await P("cloud-token-revoke",t);w(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}pe();xe();le();async function Al(e){let t=await Ce(e);w({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(`${Tl(t.appKey,t.serverKey)}
485
- `),Fg(t.keysIssuedAt)})}async function Rl(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=pr(await P("cloud-project-rotate-jwt",{ref:e}));w(n,()=>{process.stdout.write(`${Tl(n.appKey,n.serverKey)}
486
- `),d(""),d(n.breaks),d(b(" snoutdata keys --json to read them again later"))})}function Tl(e,t){return W([["KEY","VALUE"],["app key",e],["server key",t]])}function Fg(e){d(""),d(`The ${O("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${O("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(b(` issued ${e.slice(0,10)}`))}pe();xe();le();import{readdir as Hg,readFile as Kg,stat as Gg}from"node:fs/promises";import{join as as,relative as qg,sep as Wg}from"node:path";var Ol=["functions"];async function ss(e){try{return(await Gg(e)).isDirectory()}catch{return!1}}async function zg(e,t){if(t){if(!await ss(t))throw new g(`${t} is not a directory`);return t}for(let n of Ol){let r=as(n,e);if(await ss(r))return r}throw new g(`no folder found for ${e} at ${Ol[0]}/${e}; use --dir to say where it is.`)}async function Nl(e){let t=[];async function n(r){for(let o of await Hg(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=as(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Kg(s),c=a.toString("utf8");if(Buffer.compare(Buffer.from(c,"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:qg(e,s).split(Wg).join("/"),text:c})}}return await n(e),t}async function Jg(e,t,n){let r=await Nl(t);if(r.length===0)throw new g(`${t} has no files in it`);let o=n??(r.some(c=>c.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=as(t,"..","_shared");if(!await ss(s))return{entrypoint:o,files:r};let a=(await Nl(s)).map(c=>({path:`_shared/${c.path}`,text:c.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(c=>({path:`${e}/${c.path}`,text:c.text})),...a]}}async function is(e,t,n){At(t);let r=await zg(t,n.dir),{entrypoint:o,files:s}=await Jg(t,r,n.entrypoint),a=ol({entrypoint:o,files:s});return{answer:await P("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 Cl(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await is(e,t,n),c=r.functions.find(i=>i.name===t);w(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${O(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),c&&d(` ${c.url}`),n.verifyJwt||(d(""),d(O(" Anybody who knows this URL can run it. No API key is required.")),d(b(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(b(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function ls(e){return P("cloud-project-functions",{ref:e})}function cs(e,t){return At(t),P("cloud-project-functions",{ref:e,action:"delete",name:t})}function ds(e,t,n){if(At(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new g("functions size needs --memory MB, --concurrency N, or --reset");return P("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Vg(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 Dl(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 Ml(e,t,n){let r=await ds(e,t,n),o=r.functions.find(s=>s.name===t);w(r,()=>{let s=o?Vg(o,r.limit):null;d(`${O(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(b(" The next request runs at it; workers already running finish what they hold."));let a=Dl(r.limit);a&&d(b(` ${a}`))})}async function jl(e){let t=await ls(e);w(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(b(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${W([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":O("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])])}
487
- `),t.limit.maxFunctions!==null&&d(b(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Dl(t.limit);n&&d(b(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Il(e){let t=e.dropped_before>0?b(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?b(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?O("error "):"";return`${b(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Ll(e,t,n){let r=a=>P("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(w({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(b(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Il(a)}
488
- `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(c=>setTimeout(c,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let c of a)process.stdout.write(`${Il(c)}
489
- `),s=c.at}}async function Ul(e,t){let n=await cs(e,t);w(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}pe();import{spawn as bh}from"node:child_process";import{createHash as Sh}from"node:crypto";import{createReadStream as Vl}from"node:fs";import{mkdtemp as _h,rm as kh,stat as Sr}from"node:fs/promises";import{request as vh}from"node:http";import{request as $h}from"node:https";import{tmpdir as Ph}from"node:os";import{basename as xh,dirname as Eh,join as gs}from"node:path";var Xg="x-snoutdata-upload-bytes",Zg="x-snoutdata-upload-sha256",Qg="/done",eh=/^[0-9a-f]{64}$/,th=/^sha256:[0-9a-f]{64}$/;function nh(e){return eh.test(e)}var De=class extends Error{constructor(n,r){super(`the upload failed (${n})`);this.status=n;this.body=r}};function us(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function Fl(e){let t=String(us(e).digest??"");if(!th.test(t))throw new De(502,JSON.stringify({error:"The upload finished without saying what it stored, so there is nothing to deploy. Build again."}));return t}var rh=15*6e4,oh=2e3;async function Bl(e){let{ticketUrl:t,file:n,transport:r}=e;if(!nh(e.sha256))throw new Error("an upload is declared with its SHA-256, in hex");let o=await r.call("POST",t,{[Xg]:String(e.bytes),[Zg]:e.sha256});if(o.status===405){e.step?.("uploading");let l=await r.put(t,n,{});if(l.status!==200)throw new De(l.status,l.body);return Fl(l.body)}if(o.status!==200)throw new De(o.status,o.body);let s=us(o.body);if(typeof s.url!="string"||!s.url.startsWith("https://")||!s.headers||typeof s.headers!="object")throw new De(502,JSON.stringify({error:"The project's host did not say where to put the image. Build again in a moment."}));e.step?.("uploading");let a=await r.put(s.url,n,s.headers);if(a.status!==200){let l=/<Code>([^<]+)<\/Code>/.exec(a.body)?.[1],m=l==="BadDigest"||l==="XAmzContentSHA256Mismatch"?"The image changed or was damaged while it was uploading. Build again.":l==="AccessDenied"||l==="SignatureDoesNotMatch"||l==="RequestTimeTooSkewed"?"The upload link was refused (it lasts 15 minutes, and this computer's clock must be right). Build again.":`The upload to storage failed (${a.status}). Build again in a moment.`;throw new De(a.status===403?401:502,JSON.stringify({error:m}))}e.step?.("storing");let c=r.now()+(e.waitMs??rh),i=e.pollMs??oh,u=!1;for(;!u;){let l=await r.call("POST",`${t}${Qg}`,{});if(l.status===200||l.status===202)u=!0;else if(l.status===429&&r.now()<c)await r.sleep(i*5);else throw new De(l.status,l.body)}for(;;){let l=await r.call("GET",t,{});if(l.status!==200)throw new De(l.status,l.body);let m=us(l.body);if(m.state==="stored")return Fl(l.body);if(m.state==="failed")throw new De(typeof m.status=="number"?m.status:502,JSON.stringify({error:m.error??"The image could not be stored. Build again in a moment."}));if(r.now()>=c)throw new De(504,JSON.stringify({error:"The project's host took too long to store the image. Build again in a moment."}));await r.sleep(i)}}le();var sh=/^[a-z0-9][a-z0-9_-]{0,31}$/;function ah(e){return sh.test(e)}function Kl(e){return ah(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 ih=/^sha256:[0-9a-f]{64}$/,lh=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,ch=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,dh=/^(?:[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 uh(e){return ih.test(e)}function Gl(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),!uh(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),c=n.lastIndexOf(":");if(c>a&&(s=n.slice(c+1),n=n.slice(0,c),!lh.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",l=i[0]??"";return i.length>1&&(l.includes(".")||l.includes(":")||l==="localhost")&&(u=l.toLowerCase(),i.shift()),dh.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=>!ch.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(l)} is not a registry address.`}}var c$=["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 ps(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 ph(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 l=ps(t.slice(r+1));if(!l)return null;t=`${t.slice(0,r+1)}${(l[0]<<8|l[1]).toString(16)}:${(l[2]<<8|l[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=l=>l===""?[]:l.split(":"),a=s(o[0]??""),c=o.length===2?s(o[1]??""):[];if([...a,...c].some(l=>!/^[0-9a-fA-F]{1,4}$/.test(l)))return null;let i=a.length+c.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(l=>parseInt(l,16)),...u,...c.map(l=>parseInt(l,16))]}function mh(e,t,n){let r=ps(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 fh=[["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 d$=1024**3;var Hl=16,gh=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,hh=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function yh(e,t){let n=e??[],r={error:`${t}: ports are up to ${Hl} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Hl)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 wh(e){let t=e.trim().toLowerCase(),n=hh.exec(t);if(n){let r=ps(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,c=(s&a)>>>0,i=[24,16,8,0].map(u=>c>>>u&255);return fh.some(([u,l])=>o>=l&&mh(i,u,l))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return ph(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.`}:gh.test(t)?t:/^[a-z0-9-]{1,63}$/.test(t)?{error:`${e} is not a whole host name. Give it with its domain, like api.example.com.`}:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function ql(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=r?t.slice(0,n):t,s=yh(r?t.slice(n+1).split(",").map(c=>c.trim()):[],o.trim());if("error"in s)return s;let a=wh(o);return typeof a!="string"?a:{destination:a,ports:s}}function ms(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var Ah=new Set(["pending","pulling","starting","restarting"]),Rh=5*6e4;function vr(e){let t=Kl(e);if(t)throw new Error(t)}function hs(e,t,n){vr(t);let r=Gl(n.image);if("error"in r)throw new Error(r.error);return P("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 Gt(e){return P("cloud-project-containers",{ref:e})}function ys(e,t){return vr(t),P("cloud-project-containers",{ref:e,action:"delete",name:t})}function ws(e,t){return vr(t),P("cloud-project-containers",{ref:e,action:"restart",name:t})}function _r(e,t){return P("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 Yl(e,t){let n=Date.now()+Rh,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await Gt(e)).containers.find(o=>o.name===t)??null,!r||!Ah.has(r.status))return r;return r}function bs(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 Xl(e,t,n){await Zl(e,t,await hs(e,t,n),n.wait!==!1)}async function Zl(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(b(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await Yl(e,t)??o),w({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${O(t)} deployed.`);return}d(`${O(t)} ${bs(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(b(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d(O(" Anything written inside the container is lost when it restarts or is redeployed.")),d(b(" Keep what matters in your database.")),n.allowance&&d(b(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var Th=2*1024**3,Oh=3*6e4;async function Nh(e){let t;try{t=await Sr(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 Sr(gs(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Eh(e),file:e}}function Ih(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function Ss(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 Ch(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?Ss(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 Dh(e){return new Promise((t,n)=>{let r=bh("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Ql(e,t,n,r){return new Promise((o,s)=>{let a=new URL(e),c=(a.protocol==="http:"?vh:$h)(a,{method:t,headers:n});c.on("response",i=>{let u=[];i.on("data",l=>u.push(l)),i.on("end",()=>o({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",s)}),c.on("error",i=>{s(i)}),r(c)})}async function Mh(e,t,n){let r=Object.keys(n).length>0?n:{"content-type":"application/octet-stream","content-length":String((await Sr(t)).size)};return Ql(e,"PUT",r,o=>{let s=Vl(t);s.on("error",a=>o.destroy(a)),s.pipe(o)})}function jh(e){return new Promise((t,n)=>{let r=Sh("sha256");Vl(e).on("error",n).on("data",o=>r.update(o)).on("end",()=>t(r.digest("hex")))})}var Lh={call:(e,t,n)=>Ql(t,e,{"content-length":"0",...n},r=>r.end()),put:Mh,sleep:e=>new Promise(t=>setTimeout(t,e)),now:()=>Date.now()},ec={docker:Dh,scratch:()=>_h(gs(Ph(),"snoutdata-build-")),remove:e=>kh(e,{recursive:!0,force:!0}),size:async e=>(await Sr(e)).size,hash:jh,transport:Lh,ask:e=>P("cloud-project-containers",e),say:e=>d(b(e)),now:()=>Date.now()};function Wl(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function zl(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 Uh(e,t,n,r=ec){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 zl(r,e,t),s=await r.scratch();try{let a=gs(s,"image.tar");if(r.say(`${t}: building ${n.file?xh(n.file):n.context} for linux/arm64`),await r.docker(Ih({...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 c=await r.size(a).catch(()=>0);if(c<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(c>Th)throw new Error(`the image is ${Wl(c)}, 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()>Oh||(o=await zl(r,e,t));let i=await r.hash(a);r.say(`${t}: uploading the image (${Wl(c)})`);try{return await Bl({ticketUrl:o.url,file:a,bytes:c,sha256:i,transport:r.transport,step:u=>{u==="storing"&&r.say(`${t}: storing the image in the project`)}})}catch(u){throw u instanceof De?new Error(Ch(u.status,u.body)):u}}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(Ss(o instanceof Error?o.message:String(o)))}}async function tc(e,t,n,r=ec){vr(t);let o=await Nh(n.path),s=await Uh(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(c){throw new Error(Ss(c instanceof Error?c.message:String(c)))}await Zl(e,t,a,n.wait!==!1)}async function nc(e){let t=await Gt(e);w(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(b(" Containers run on the Plus and Pro plans.")):d(b(" snoutdata containers deploy <name> <image>"));return}d(W([["name","status","image","url"],...t.containers.map(r=>[r.name,bs(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(b(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(b(` Sent this month: ${fs(n.usedBytes)} of ${fs(n.allowanceBytes)}. At ${fs(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function kr(e,t){if(t===null)return P("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=ql(r);if("error"in o)throw new Error(o.error);n.push(o)}return P("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Fh(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).`,b(" 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(` ${ms(n)}${n.ports.length===0?b(" (any port)"):""}`);return t.push(b(` and always: ${e.allowlist.alwaysAllowed.map(ms).join(", ")}, and each other`)),t.push(b(" snoutdata containers egress clear to allow the public internet again")),t}async function $r(e,t){let n=t.clear?await kr(e,null):t.set?await kr(e,t.set):await Gt(e);w({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Fh(n))d(r);(t.set||t.clear)&&d(b(" Running containers follow it within a minute; nothing restarts."))})}function fs(e){return`${Math.round(e/1024**3*10)/10} GB`}async function rc(e,t){await ws(e,t);let n=await Yl(e,t);w({ref:e,containers:n?[n]:[]},()=>{d(n?`${O(t)} ${bs(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function oc(e,t){let n=await ys(e,t);w(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function Jl(e){return`${b(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?O(e.message):e.message}`}async function sc(e,t,n){let r=((await _r(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(w({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(b(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${Jl(s)}
490
- `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await _r(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${Jl(a)}
491
- `),o=a.at}}pe();xe();le();function ic(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function qt(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function lc(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 qt()}]}return e.map(n=>{let r=ic(n);if(!r)throw new g(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function cc(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 qt()})}else for(let s of t){let a=ic(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=hn(s.name);if(a)throw new g(a)}let o=null;for(let s of r)o=await P("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});w(o,()=>{for(let s of r)d(`${O(s.name)} set`);d(b(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(b(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function dc(e,t){let n=hn(t);if(n)throw new g(n);let r=await P("cloud-project-secrets",{ref:e,action:"unset",name:t});w(r,()=>{d(`${t} is gone.`)})}async function uc(e){let t=await P("cloud-project-secrets",{ref:e});w(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(b(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${W([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,st(n.updatedAt)])])}
492
- `),d(b("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as xn}from"node:child_process";import{appendFileSync as Dc,existsSync as Or,readFileSync as Mc,statSync as xy,unlinkSync as Ey,writeFileSync as zt}from"node:fs";import{dirname as Ay,join as Nr}from"node:path";import{tmpdir as Ry}from"node:os";import{open as jc}from"node:fs/promises";function pc(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 mc(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 fc(e){let t=e.split(`
493
- `).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 _s={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Hh=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Pr=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"]),Kh=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Gh={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},qh=new Set(["push.settings"]),Wh=["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 zh(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=Wh.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]:"",c=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:c,owner:a}}function Jh(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 c=t.get(s)??new Set;c.add(a),t.set(s,c)}}return t}function gc(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
494
- `)){let u=zh(i);if(!u)continue;let{type:l,schema:m,name:h,owner:x}=u;if(l==="TABLE DATA"||l==="SEQUENCE SET"){let ee=/^(schema_migrations|migrations)$/.test(h)&&Pr.has(m);if(/realtime/.test(m)||m==="snout_live"||ee||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 z=Gh[m];if(z){z==="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(z,[...n.get(z)??[],u]);continue}if(Pr.has(m)){s++;continue}r.push(u.line);continue}if(l==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(l==="SCHEMA"?Pr.has(h):Pr.has(m))||Kh.test(h)||Hh.test(x)&&l!=="SCHEMA"){s++;continue}r.push(u.line)}let a=Jh(t),c=["auth","storage","push"];return{platformData:c.filter(i=>n.has(i)).map(i=>{let u=n.get(i),l=u.filter(h=>h.type==="TABLE DATA"&&qh.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:l,tables:m,lines:Vh(u,a)}}),rest:r,products:c.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function Vh(e,t){let n=c=>`${c.schema}.${c.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(l=>!r.has(l)||o.has(l)||l===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}pe();xe();we();le();pe();we();var vs=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],yc=["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"],wc=["off","advise","auto"];function wn(e){return e==="done"||e==="failed"||e==="cancelled"}function bn(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new T("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Ye(ey(e.project))}let n=e.admin??hc(t.LEPIS_ADMIN_URL);if(!n)throw new T("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??hc(t.LEPIS_ADMIN_TOKEN);if(!r)throw new T("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new ks(Yh(n),r)}function hc(e){return e===void 0||e===""?void 0:e}function Yh(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new T("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new T("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new T("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!Xh(n.hostname))throw new T("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 Xh(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var Zh=6e4,ks=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))}`)}autopilot(){return this.#e("GET","/v1/autopilot")}setAutopilot(t){return this.#e("POST","/v1/settings",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(Zh)})}catch(i){let u=zn(i),l=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new T(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${l}`)}let a=await s.text(),c;try{c=a===""?{}:JSON.parse(a)}catch{throw new T("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw bc(s.status,c);return c}},Qh={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function bc(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?Qh[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":rt(e));return s==="not-signed-in"?new T(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new T(s,r,o)}function ey(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new T("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var ty={"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","table.auto":"auto","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"},ny="cloud-project-shards",Ye=class{target;ref;#t;constructor(t,n=P){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:ht(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))}})}autopilot(){return this.#e({action:"autopilot"})}setAutopilot(t){return this.#e({action:"settings",settings: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=ty[t.op];if(!n)throw new T("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 T("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 T("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(ny,{ref:this.ref,...t})}catch(n){throw ry(n)}}};function ht(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 ry(e){if(!(e instanceof $e))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return bc(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":rt(e.status);return new T(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function _n(e){if(Array.isArray(e))return`[${e.map(_n).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)}:${_n(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var xs=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}
495
- ${_n(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
496
- ${_n(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
497
- ${_n(t)}`)}},oy=new xs,Es=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","table.auto","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],sy="2024-11-05";function Y(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var j={type:"string"},Sc={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:[...Es]}},required:["op"],additionalProperties:!0},xr={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},yt={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)."},vn=[{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:Y({})},{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:Y({})},{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:Y({name:j,region:j,teamId:j},["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:Y({ref:j},["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:Y({ref:j},["ref"])},{name:"scale_project",description:"More capacity for a project's app API (auth, the data API and the router): 1 to 3 instances of it take its calls. The database is not copied by this, and it is not failover: keeping a project running is the cloud's job on every plan. Without instances, says how many it runs now and how many the plan allows. Without confirm: true it only returns the plan for that number and changes nothing: show it to the user, and when they agree call again with confirm: true. A refusal (the plan, not open yet, a sharded project, no room) comes back in a sentence to show as it is.",inputSchema:Y({ref:j,instances:{type:"integer",enum:[1,2,3]},confirm:{type:"boolean"}},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:Y({ref:j},["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), or a COMMIT, END or ROLLBACK that would end the transaction the file runs in, is refused before anything runs; a file starting with -- snoutdata:no-transaction runs as written, outside a transaction.",inputSchema:Y({ref:j,dir:j,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:Y({ref:j,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:Y({ref:j},["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:Y({ref:j},["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:Y({ref:j},["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:Y({})},{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:Y({})},{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:Y({name:j,expires:j,project:j},["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:Y({id:j},["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:Y({ref:j,name:j,dir:j,entrypoint:j,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:Y({ref:j},["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:Y({ref:j,name:j,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:Y({ref:j,name:j},["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:Y({ref:j},["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:Y({ref:j,name:j,image:j,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:Y({ref:j,name:j},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:Y({ref:j,name:j},["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:Y({ref:j,name:j,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:Y({ref:j},["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, "140.82.112.0/20: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:Y({ref:j,allow:{type:"array",items:j},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:Y({ref:j},["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:Y({ref:j,name:j,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:Y({ref:j},["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:Y({ref:j},["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:Y({ref:j,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:Y({ref:j},["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:Y({ref:j,hostname:j},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:Y({ref:j,hostname:j},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:Y({ref:j,hostname:j},["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:Y({ref:j},["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:Y({ref:j,at:j,name:j},["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:Y({project:yt})},{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:Y({project:yt,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:Y({project:yt,operation:Sc},["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:Y({project:yt,operation:Sc,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:Y({project:yt,id:xr})},{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:Y({project:yt,id:xr,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:Y({project:yt,id:xr},["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:Y({project:yt,id:xr},["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:Y({ref:j},["ref"])}],ay=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"]),iy=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),_c=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function As(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(As)};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 Rs(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 ly(e){return{...e,annotations:ay.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:iy.has(e.name)}}}function cy(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function kn(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Er(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function A(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return kn(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function dy(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Sn(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 $s(e){return{id:e.id,op:e.op,state:e.state,finished:wn(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 kc(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"||!Es.includes(r.op))return`${t}: operation.op must be one of ${Es.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 Ps(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 uy(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var py="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 my(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 A(e,py,!0);s=r.shards(a)}catch(u){return A(e,u instanceof Error?u.message:String(u),!0)}let c=o.shardsPlans??oy,i=a??"";try{switch(t){case"cluster_status":return A(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,l=await s.advice(u);for(let m of l.advice)m.plan&&c.remember(m.request,i);return A(e,{summary:l.summary,advice:l.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:Sn(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:l.facts,assumptions:l.assumptions,...l.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=kc(n,t);if(typeof u=="string")return A(e,u,!0);let l=await s.plan(u);return c.remember(u,i),A(e,{plan:Sn(l),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=kc(n,t);if(typeof u=="string")return A(e,u,!0);if(n.confirm!==!0||!c.shown(u,i)){let m=await s.plan(u);c.remember(u,i);let h=n.confirm===!0;return A(e,{ran:!1,plan:Sn(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 l=await s.submit(u);return c.forget(u,i),l.job===void 0?A(e,{ran:!0,done:l.done,answer:l.answer}):A(e,{ran:!0,job:l.job,plan:Sn(l.plan),next:`Follow it with cluster_watch_job {"id": ${l.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return A(e,{jobs:await s.jobs()});let u=Ps(n);if(u===null)return A(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let l=await s.job(u);return A(e,{...$s(l),plan:l.plan&&Array.isArray(l.plan.steps)?Sn(l.plan):null})}case"cluster_watch_job":{let u=Ps(n);if(u===null)return A(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let l=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+l*1e3;for(;;){let h=await s.job(u);if(wn(h.state))return A(e,$s(h),h.state!=="done");if(Date.now()>=m)return A(e,{...$s(h),note:`Still ${h.state} after ${l}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=Ps(n);if(u===null)return A(e,`${t} needs id, a job number.`,!0);let l=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return A(e,{...l,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return Er(e,-32602,`unknown tool: ${t}`)}}catch(u){return A(e,uy(u),!0)}}function te(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function vc(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 kn(r,{protocolVersion:sy,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return kn(r,{});case"tools/list":return kn(r,{tools:cy([...vn.map(ly),...n.borrowed??[]])});case"tools/call":return fy(r,s,t,n);default:return Er(r,-32601,`unknown method: ${o}`)}}async function fy(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(vn.some(l=>l.name===o)||(r.borrowed?.some(l=>l.name===o)??!1)))return Er(e,-32602,`unknown tool: ${o}`);let c=["get_connection_url","pause_project","resume_project","scale_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_")&&vn.some(l=>l.name===o))return my(e,o,s,n,r);let i=te(s,"ref");if(c.includes(o)&&!i)return A(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!_c.includes(o))return A(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${_c.join(", ")}.`,!0);try{switch(o){case"whoami":return A(e,await n.whoami());case"list_projects":return A(e,As(await n.listProjects()));case"create_project":{let l=te(s,"name");return l?A(e,await n.createProject({name:l,region:te(s,"region")??void 0,teamId:te(s,"teamId")??void 0})):A(e,"create_project needs a name.",!0)}case"get_connection_url":{let l=await n.connection(i);return A(e,{url:l.uri,state:l.state,wakesInstantly:l.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 A(e,await n.setProjectState("pause",i));case"resume_project":return A(e,await n.setProjectState("resume",i));case"scale_project":{let l=s.instances;if(l!==void 0&&l!==1&&l!==2&&l!==3)return A(e,"instances is 1, 2 or 3.",!0);let m=s.confirm===!0&&l!==void 0;return A(e,await n.scaleProject(i,l,m))}case"usage":{let l=typeof s.days=="number"?s.days:void 0;return A(e,await n.usage(i,l))}case"export_status":return A(e,await n.exportStatus(i));case"start_export":return A(e,await n.requestExport(i));case"reset_password":{let l=await n.resetPassword(i),m=await n.connection(i);return A(e,{url:m.uri,appliesIn:l.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 A(e,await n.listTeams());case"get_project":return A(e,As(await n.getProject(i)));case"list_products":return A(e,await n.getProducts(i));case"set_product":{let l=te(s,"product");return l!=="auth"&&l!=="storage"&&l!=="data-api"?A(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?A(e,"set_product needs enabled: true or false.",!0):A(e,await n.setProduct(i,l,s.enabled))}case"list_domains":return A(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let l=te(s,"hostname");return l?A(e,await n.domainAction(i,o.split("_")[0],l)):A(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let l=await n.restoreWindow(i),m=l&&l.restore?Rs(i,l.restore):null;return A(e,m?{...l,reason:m}:l)}case"restore_to_point":{let l=te(s,"at");return l?A(e,await n.restoreTo(i,l,te(s,"name")??void 0)):A(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let l=te(s,"ref"),m=te(s,"name");return!l||!m?A(e,"deploy_function needs a project ref and a function name.",!0):A(e,await n.deployFunction(l,m,{dir:te(s,"dir")??void 0,entrypoint:te(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let l=te(s,"ref");return l?A(e,await n.listFunctions(l)):A(e,"list_functions needs a project ref.",!0)}case"size_function":{let l=te(s,"ref"),m=te(s,"name");if(!l||!m)return A(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,R=s.reset===!0;return!R&&h===void 0&&x===void 0?A(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):A(e,await n.sizeFunction(l,m,{memoryMb:h,concurrency:x,reset:R}))}case"delete_function":{let l=te(s,"ref"),m=te(s,"name");return!l||!m?A(e,"delete_function needs a project ref and a function name.",!0):A(e,await n.deleteFunction(l,m))}case"list_containers":{let l=te(s,"ref");return l?A(e,await n.listContainers(l)):A(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let l=te(s,"ref"),m=te(s,"name"),h=te(s,"image");if(!l||!m||!h)return A(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 A(e,await n.deployContainer(l,m,{image:h,...x===void 0?{}:{port:x}}))}case"restart_container":case"delete_container":{let l=te(s,"ref"),m=te(s,"name");return!l||!m?A(e,`${o} needs a project ref and a container name.`,!0):A(e,o==="restart_container"?await n.restartContainer(l,m):await n.deleteContainer(l,m))}case"container_logs":{let l=te(s,"ref");if(!l)return A(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return A(e,await n.containerLogs(l,{name:te(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"container_egress":{let l=te(s,"ref");return l?A(e,await n.containerEgress(l)):A(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let l=te(s,"ref");if(!l)return A(e,"set_container_egress needs a project ref.",!0);let m=s.clear===!0,h=s.allow;return h!==void 0&&(!Array.isArray(h)||!h.every(x=>typeof x=="string"))?A(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):m===(h!==void 0)?A(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):A(e,await n.setContainerEgress(l,m?null:h))}case"list_function_secrets":{let l=te(s,"ref");return l?A(e,await n.listFunctionSecrets(l)):A(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let l=te(s,"ref");if(!l)return A(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return A(e,await n.functionLogs(l,{name:te(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return A(e,await n.listTokens());case"create_token":{let l=te(s,"name");if(!l)return A(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=te(s,"expires"),h=m?dy(m):void 0;return h===null?A(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):A(e,await n.createToken(l,h,te(s,"project")??void 0))}case"revoke_token":{let l=te(s,"id");return l?A(e,await n.revokeToken(l)):A(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return A(e,await n.pushMigrations(i,{dir:te(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?A(e,await n.setProjectState("delete",i)):A(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(l=>l.name===o)?n.callBorrowed?kn(e,await n.callBorrowed(o,s)):A(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Er(e,-32602,`unknown tool: ${o}`)}}catch(l){return A(e,l instanceof Error?l.message:String(l),!0)}}function $c(e){let t=e.split(`
498
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Ar=["auth","storage","data-api","push"];function Rt(e){return{error:e instanceof Error?e.message:String(e)}}async function Wt(e){let[t,n,r,o]=await Promise.all([P("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Rt),P("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}),Rt),P("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Rt),P("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Rt)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:gy}}var gy={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function xc(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Ar.includes(t))throw new g(`say which product: ${Ar.join(", ")}`);return t}async function Ts(e,t,n){if(t==="auth"){let r=await P("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 P("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 P(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 Ec(e){let t=await Wt(e);w(t,()=>{let n=(c,i,u="")=>[c,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?O("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${W([["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")])}
499
- `);let s=c=>!!(c&&typeof c=="object"&&"enabled"in c&&!c.enabled),a=["auth","storage","data-api","push"].find(c=>s(c==="data-api"?o:t[c]));a&&d(b(` snoutdata products enable ${a} --ref ${e}`))})}async function Ac(e,t,n){let r=await Ts(e,t,n);w(r,()=>d(r.note))}async function Rr(e){let t=await P("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Os(e,t,n){if(!n)throw new g(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await P("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Pc(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(`${W(n)}
500
- `)}async function Rc(e,t,n){if(t===void 0||t==="list"){let o=await Rr(e);w(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${W([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":O("no"),s.certificate?.state??""])])}
501
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new g(`unknown command: domains ${t}`);let r=await Os(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(lr()||Pc(r.domain,e),V("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),w(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||Pc(r.domain,e)),r.next&&d(r.next)})}async function Ns(e){return P("cloud-project-restore",{ref:e},"GET")}async function Is(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 P("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Tc(e){let t=await Ns(e);w(t,()=>{let n=t.restore,r=Rs(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(b(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function Oc(e,t,n){let r=await Is(e,t,n);w(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${O(r.project.name)} (${r.project.ref}).`),d(b(" snoutdata projects list to see when it is ready"))})}async function Cs(e){let{projects:t}=await je(),n=t.find(c=>c.ref===e);if(!n)throw new g(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([Wt(e),P("cloud-project-functions",{ref:e}).then(c=>(c.functions??[]).map(i=>i.name),Rt),P("cloud-project-secrets",{ref:e}).then(c=>(c.secrets??[]).map(i=>i.name),Rt),Rr(e).then(c=>c.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Rt)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Nc(e){let t=await Cs(e);w(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(`${W([["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)]])}
502
- `)})}pe();we();pt();pe();le();import{existsSync as hy,mkdirSync as yy,readFileSync as wy,renameSync as by,writeFileSync as Sy}from"node:fs";import{homedir as Ds}from"node:os";import{dirname as _y,join as ky}from"node:path";import{rootCertificates as vy}from"node:tls";var $y="SNOUTDATA_DB_SSLMODE";function Py(e=Ds()){let t=ky(e,".snoutdata","ca-roots.pem"),n=`${vy.join(`
524
+ `:""}`),!t.yes&&!await(t.ask??Gh)(ot.ask(r.ref,n)))throw new C("failed",ot.declined);let o=await R("cloud-project-spread",{ref:e,instances:n});b({ref:o.ref,instances:o.copies,changed:o.changed,...o.preparing?{preparing:!0,message:o.message}:{}},()=>{d(ot.done(o.ref,o.copies)),o.message&&d(o.message)})}pe();de();async function uc(e){let t=await R("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});b(t,()=>{process.stdout.write(`${t.token}
525
+ `),d(""),d(`${M(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(v(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function pc(){let{tokens:e}=await R("cloud-token-list");b({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${X([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?M(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",gt(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
526
+ `)})}async function mc(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await R("cloud-token-revoke",t);b(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}pe();Ae();de();async function fc(e){let t=await He(e);b({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(`${hc(t.appKey,t.serverKey)}
527
+ `),Wh(t.keysIssuedAt)})}async function gc(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 h("keys rotate needs --force, because this cannot be undone");let n=Dr(await R("cloud-project-rotate-jwt",{ref:e}));b(n,()=>{process.stdout.write(`${hc(n.appKey,n.serverKey)}
528
+ `),d(""),d(n.breaks),d(v(" snoutdata keys --json to read them again later"))})}function hc(e,t){return X([["KEY","VALUE"],["app key",e],["server key",t]])}function Wh(e){d(""),d(`The ${M("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${M("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(v(` issued ${e.slice(0,10)}`))}pe();Ae();de();import{readdir as Vh,readFile as Yh,stat as Xh}from"node:fs/promises";import{join as js,relative as Zh,sep as Qh}from"node:path";var yc=["functions"];async function Ls(e){try{return(await Xh(e)).isDirectory()}catch{return!1}}async function ey(e,t){if(t){if(!await Ls(t))throw new h(`${t} is not a directory`);return t}for(let n of yc){let r=js(n,e);if(await Ls(r))return r}throw new h(`no folder found for ${e} at ${yc[0]}/${e}; use --dir to say where it is.`)}async function wc(e){let t=[];async function n(r){for(let o of await Vh(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=js(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Yh(s),c=a.toString("utf8");if(Buffer.compare(Buffer.from(c,"utf8"),a)!==0)throw new h(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:Zh(e,s).split(Qh).join("/"),text:c})}}return await n(e),t}async function ty(e,t,n){let r=await wc(t);if(r.length===0)throw new h(`${t} has no files in it`);let o=n??(r.some(c=>c.path==="index.ts")?"index.ts":"");if(!o)throw new h(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=js(t,"..","_shared");if(!await Ls(s))return{entrypoint:o,files:r};let a=(await wc(s)).map(c=>({path:`_shared/${c.path}`,text:c.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(c=>({path:`${e}/${c.path}`,text:c.text})),...a]}}async function Us(e,t,n){jt(t);let r=await ey(t,n.dir),{entrypoint:o,files:s}=await ty(t,r,n.entrypoint),a=Gl({entrypoint:o,files:s});return{answer:await R("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 Sc(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Us(e,t,n),c=r.functions.find(i=>i.name===t);b(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${M(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),c&&d(` ${c.url}`),n.verifyJwt||(d(""),d(M(" Anybody who knows this URL can run it. No API key is required.")),d(v(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(v(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Fs(e){return R("cloud-project-functions",{ref:e})}function Bs(e,t){return jt(t),R("cloud-project-functions",{ref:e,action:"delete",name:t})}function Hs(e,t,n){if(jt(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new h("functions size needs --memory MB, --concurrency N, or --reset");return R("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function ny(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 kc(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 _c(e,t,n){let r=await Hs(e,t,n),o=r.functions.find(s=>s.name===t);b(r,()=>{let s=o?ny(o,r.limit):null;d(`${M(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(v(" The next request runs at it; workers already running finish what they hold."));let a=kc(r.limit);a&&d(v(` ${a}`))})}async function vc(e){let t=await Fs(e);b(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(v(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${X([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":M("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])])}
529
+ `),t.limit.maxFunctions!==null&&d(v(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=kc(t.limit);n&&d(v(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function bc(e){let t=e.dropped_before>0?v(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?v(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?M("error "):"";return`${v(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function $c(e,t,n){let r=a=>R("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(b({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(v(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${bc(a)}
530
+ `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(c=>setTimeout(c,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let c of a)process.stdout.write(`${bc(c)}
531
+ `),s=c.at}}async function Pc(e,t){let n=await Bs(e,t);b(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}pe();import{spawn as xy}from"node:child_process";import{createHash as Ey}from"node:crypto";import{createReadStream as Mc}from"node:fs";import{mkdtemp as Ay,rm as Ry,stat as Gr}from"node:fs/promises";import{request as Ty}from"node:http";import{request as Oy}from"node:https";import{tmpdir as Ny}from"node:os";import{basename as Iy,dirname as Cy,join as Ws}from"node:path";var oy="x-snoutdata-upload-bytes",sy="x-snoutdata-upload-sha256",ay="/done",iy=/^[0-9a-f]{64}$/,ly=/^sha256:[0-9a-f]{64}$/;function cy(e){return iy.test(e)}var Ke=class extends Error{constructor(n,r){super(`the upload failed (${n})`);this.status=n;this.body=r}};function Ks(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function xc(e){let t=String(Ks(e).digest??"");if(!ly.test(t))throw new Ke(502,JSON.stringify({error:"The upload finished without saying what it stored, so there is nothing to deploy. Build again."}));return t}var dy=15*6e4,uy=2e3;async function Ec(e){let{ticketUrl:t,file:n,transport:r}=e;if(!cy(e.sha256))throw new Error("an upload is declared with its SHA-256, in hex");let o=await r.call("POST",t,{[oy]:String(e.bytes),[sy]:e.sha256});if(o.status===405){e.step?.("uploading");let l=await r.put(t,n,{});if(l.status!==200)throw new Ke(l.status,l.body);return xc(l.body)}if(o.status!==200)throw new Ke(o.status,o.body);let s=Ks(o.body);if(typeof s.url!="string"||!s.url.startsWith("https://")||!s.headers||typeof s.headers!="object")throw new Ke(502,JSON.stringify({error:"The project's host did not say where to put the image. Build again in a moment."}));e.step?.("uploading");let a=await r.put(s.url,n,s.headers);if(a.status!==200){let l=/<Code>([^<]+)<\/Code>/.exec(a.body)?.[1],m=l==="BadDigest"||l==="XAmzContentSHA256Mismatch"?"The image changed or was damaged while it was uploading. Build again.":l==="AccessDenied"||l==="SignatureDoesNotMatch"||l==="RequestTimeTooSkewed"?"The upload link was refused (it lasts 15 minutes, and this computer's clock must be right). Build again.":`The upload to storage failed (${a.status}). Build again in a moment.`;throw new Ke(a.status===403?401:502,JSON.stringify({error:m}))}e.step?.("storing");let c=r.now()+(e.waitMs??dy),i=e.pollMs??uy,u=!1;for(;!u;){let l=await r.call("POST",`${t}${ay}`,{});if(l.status===200||l.status===202)u=!0;else if(l.status===429&&r.now()<c)await r.sleep(i*5);else throw new Ke(l.status,l.body)}for(;;){let l=await r.call("GET",t,{});if(l.status!==200)throw new Ke(l.status,l.body);let m=Ks(l.body);if(m.state==="stored")return xc(l.body);if(m.state==="failed")throw new Ke(typeof m.status=="number"?m.status:502,JSON.stringify({error:m.error??"The image could not be stored. Build again in a moment."}));if(r.now()>=c)throw new Ke(504,JSON.stringify({error:"The project's host took too long to store the image. Build again in a moment."}));await r.sleep(i)}}de();var py=/^[a-z0-9][a-z0-9_-]{0,31}$/;function my(e){return py.test(e)}function Rc(e){return my(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 fy=/^sha256:[0-9a-f]{64}$/,gy=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,hy=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,yy=/^(?:[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 wy(e){return fy.test(e)}function Tc(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),!wy(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),c=n.lastIndexOf(":");if(c>a&&(s=n.slice(c+1),n=n.slice(0,c),!gy.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",l=i[0]??"";return i.length>1&&(l.includes(".")||l.includes(":")||l==="localhost")&&(u=l.toLowerCase(),i.shift()),yy.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=>!hy.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(l)} is not a registry address.`}}var xP=["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 Gs(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 by(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 l=Gs(t.slice(r+1));if(!l)return null;t=`${t.slice(0,r+1)}${(l[0]<<8|l[1]).toString(16)}:${(l[2]<<8|l[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=l=>l===""?[]:l.split(":"),a=s(o[0]??""),c=o.length===2?s(o[1]??""):[];if([...a,...c].some(l=>!/^[0-9a-fA-F]{1,4}$/.test(l)))return null;let i=a.length+c.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(l=>parseInt(l,16)),...u,...c.map(l=>parseInt(l,16))]}function Sy(e,t,n){let r=Gs(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 ky=[["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 EP=1024**3;var Ac=16,_y=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,vy=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function $y(e,t){let n=e??[],r={error:`${t}: ports are up to ${Ac} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Ac)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 Py(e){let t=e.trim().toLowerCase(),n=vy.exec(t);if(n){let r=Gs(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,c=(s&a)>>>0,i=[24,16,8,0].map(u=>c>>>u&255);return ky.some(([u,l])=>o>=l&&Sy(i,u,l))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return by(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.`}:_y.test(t)?t:/^[a-z0-9-]{1,63}$/.test(t)?{error:`${e} is not a whole host name. Give it with its domain, like api.example.com.`}:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function Oc(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=r?t.slice(0,n):t,s=$y(r?t.slice(n+1).split(",").map(c=>c.trim()):[],o.trim());if("error"in s)return s;let a=Py(o);return typeof a!="string"?a:{destination:a,ports:s}}function qs(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var My=new Set(["pending","pulling","starting","restarting"]),Dy=5*6e4;function Wr(e){let t=Rc(e);if(t)throw new Error(t)}function Js(e,t,n){Wr(t);let r=Tc(n.image);if("error"in r)throw new Error(r.error);return R("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 on(e){return R("cloud-project-containers",{ref:e})}function Vs(e,t){return Wr(t),R("cloud-project-containers",{ref:e,action:"delete",name:t})}function Ys(e,t){return Wr(t),R("cloud-project-containers",{ref:e,action:"restart",name:t})}function qr(e,t){return R("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 Dc(e,t){let n=Date.now()+Dy,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await on(e)).containers.find(o=>o.name===t)??null,!r||!My.has(r.status))return r;return r}function Xs(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 Lc(e,t,n){await jc(e,t,await Js(e,t,n),n.wait!==!1)}async function jc(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(v(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await Dc(e,t)??o),b({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${M(t)} deployed.`);return}d(`${M(t)} ${Xs(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(v(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d(M(" Anything written inside the container is lost when it restarts or is redeployed.")),d(v(" Keep what matters in your database.")),n.allowance&&d(v(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var Ly=2*1024**3,jy=3*6e4;async function Uy(e){let t;try{t=await Gr(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 Gr(Ws(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Cy(e),file:e}}function Fy(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function Zs(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 By(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?Zs(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 Hy(e){return new Promise((t,n)=>{let r=xy("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Uc(e,t,n,r){return new Promise((o,s)=>{let a=new URL(e),c=(a.protocol==="http:"?Ty:Oy)(a,{method:t,headers:n});c.on("response",i=>{let u=[];i.on("data",l=>u.push(l)),i.on("end",()=>o({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",s)}),c.on("error",i=>{s(i)}),r(c)})}async function Ky(e,t,n){let r=Object.keys(n).length>0?n:{"content-type":"application/octet-stream","content-length":String((await Gr(t)).size)};return Uc(e,"PUT",r,o=>{let s=Mc(t);s.on("error",a=>o.destroy(a)),s.pipe(o)})}function Gy(e){return new Promise((t,n)=>{let r=Ey("sha256");Mc(e).on("error",n).on("data",o=>r.update(o)).on("end",()=>t(r.digest("hex")))})}var qy={call:(e,t,n)=>Uc(t,e,{"content-length":"0",...n},r=>r.end()),put:Ky,sleep:e=>new Promise(t=>setTimeout(t,e)),now:()=>Date.now()},Fc={docker:Hy,scratch:()=>Ay(Ws(Ny(),"snoutdata-build-")),remove:e=>Ry(e,{recursive:!0,force:!0}),size:async e=>(await Gr(e)).size,hash:Gy,transport:qy,ask:e=>R("cloud-project-containers",e),say:e=>d(v(e)),now:()=>Date.now()};function Nc(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function Ic(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 zy(e,t,n,r=Fc){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 Ic(r,e,t),s=await r.scratch();try{let a=Ws(s,"image.tar");if(r.say(`${t}: building ${n.file?Iy(n.file):n.context} for linux/arm64`),await r.docker(Fy({...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 c=await r.size(a).catch(()=>0);if(c<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(c>Ly)throw new Error(`the image is ${Nc(c)}, 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()>jy||(o=await Ic(r,e,t));let i=await r.hash(a);r.say(`${t}: uploading the image (${Nc(c)})`);try{return await Ec({ticketUrl:o.url,file:a,bytes:c,sha256:i,transport:r.transport,step:u=>{u==="storing"&&r.say(`${t}: storing the image in the project`)}})}catch(u){throw u instanceof Ke?new Error(By(u.status,u.body)):u}}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(Zs(o instanceof Error?o.message:String(o)))}}async function Bc(e,t,n,r=Fc){Wr(t);let o=await Uy(n.path),s=await zy(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(c){throw new Error(Zs(c instanceof Error?c.message:String(c)))}await jc(e,t,a,n.wait!==!1)}async function Hc(e){let t=await on(e);b(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(v(" Containers run on the Plus and Pro plans.")):d(v(" snoutdata containers deploy <name> <image>"));return}d(X([["name","status","image","url"],...t.containers.map(r=>[r.name,Xs(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(v(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(v(` Sent this month: ${zs(n.usedBytes)} of ${zs(n.allowanceBytes)}. At ${zs(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function zr(e,t){if(t===null)return R("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=Oc(r);if("error"in o)throw new Error(o.error);n.push(o)}return R("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Wy(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).`,v(" 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(` ${qs(n)}${n.ports.length===0?v(" (any port)"):""}`);return t.push(v(` and always: ${e.allowlist.alwaysAllowed.map(qs).join(", ")}, and each other`)),t.push(v(" snoutdata containers egress clear to allow the public internet again")),t}async function Jr(e,t){let n=t.clear?await zr(e,null):t.set?await zr(e,t.set):await on(e);b({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Wy(n))d(r);(t.set||t.clear)&&d(v(" Running containers follow it within a minute; nothing restarts."))})}function zs(e){return`${Math.round(e/1024**3*10)/10} GB`}async function Kc(e,t){await Ys(e,t);let n=await Dc(e,t);b({ref:e,containers:n?[n]:[]},()=>{d(n?`${M(t)} ${Xs(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Gc(e,t){let n=await Vs(e,t);b(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function Cc(e){return`${v(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?M(e.message):e.message}`}async function qc(e,t,n){let r=((await qr(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(b({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(v(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${Cc(s)}
532
+ `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await qr(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${Cc(a)}
533
+ `),o=a.at}}pe();Ae();de();function Wc(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function sn(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Jc(e,t){if(e.length===0)throw new h("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new h("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await sn()}]}return e.map(n=>{let r=Wc(n);if(!r)throw new h(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function Vc(e,t,n){if(t.length===0)throw new h("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new h("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await sn()})}else for(let s of t){let a=Wc(s);if(!a)throw new h(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Dn(s.name);if(a)throw new h(a)}let o=null;for(let s of r)o=await R("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});b(o,()=>{for(let s of r)d(`${M(s.name)} set`);d(v(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(v(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function Yc(e,t){let n=Dn(t);if(n)throw new h(n);let r=await R("cloud-project-secrets",{ref:e,action:"unset",name:t});b(r,()=>{d(`${t} is gone.`)})}async function Xc(e){let t=await R("cloud-project-secrets",{ref:e});b(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(v(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${X([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,gt(n.updatedAt)])])}
534
+ `),d(v("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as zn}from"node:child_process";import{appendFileSync as kd,existsSync as to,readFileSync as _d,statSync as Iw,unlinkSync as Cw,writeFileSync as ln}from"node:fs";import{dirname as Mw,join as no}from"node:path";import{tmpdir as Dw}from"node:os";import{open as vd}from"node:fs/promises";function Zc(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 Qc(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 ed(e){let t=e.split(`
535
+ `).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 Qs={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Vy=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Vr=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"]),Yy=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Xy={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},Zy=new Set(["push.settings"]),Qy=["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 ew(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=Qy.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]:"",c=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:c,owner:a}}function tw(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 c=t.get(s)??new Set;c.add(a),t.set(s,c)}}return t}function td(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
536
+ `)){let u=ew(i);if(!u)continue;let{type:l,schema:m,name:y,owner:T}=u;if(l==="TABLE DATA"||l==="SEQUENCE SET"){let oe=/^(schema_migrations|migrations)$/.test(y)&&Vr.has(m);if(/realtime/.test(m)||m==="snout_live"||oe||m==="public"&&y==="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"&&y==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let Z=Xy[m];if(Z){Z==="storage"&&(y==="objects"||y==="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(Z,[...n.get(Z)??[],u]);continue}if(Vr.has(m)){s++;continue}r.push(u.line);continue}if(l==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(y)||(l==="SCHEMA"?Vr.has(y):Vr.has(m))||Yy.test(y)||Vy.test(T)&&l!=="SCHEMA"){s++;continue}r.push(u.line)}let a=tw(t),c=["auth","storage","push"];return{platformData:c.filter(i=>n.has(i)).map(i=>{let u=n.get(i),l=u.filter(y=>y.type==="TABLE DATA"&&Zy.has(`${y.schema}.${y.name}`)).map(y=>`${y.schema}.${y.name}`),m=u.filter(y=>y.type==="TABLE DATA").map(y=>`${y.schema}.${y.name}`);return{product:i,clear:l,tables:m,lines:nw(u,a)}}),rest:r,products:c.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function nw(e,t){let n=c=>`${c.schema}.${c.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(l=>!r.has(l)||o.has(l)||l===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}pe();Ae();be();de();pe();be();var ta=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],rd=["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"],od=["off","advise","auto"];function jn(e){return e==="done"||e==="failed"||e==="cancelled"}function Un(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new C("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new st(iw(e.project))}let n=e.admin??nd(t.LEPIS_ADMIN_URL);if(!n)throw new C("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??nd(t.LEPIS_ADMIN_TOKEN);if(!r)throw new C("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new ea(rw(n),r)}function nd(e){return e===void 0||e===""?void 0:e}function rw(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new C("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new C("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new C("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!ow(n.hostname))throw new C("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 ow(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var sw=6e4,ea=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))}`)}check(){return this.#e("GET","/v1/check")}autopilot(){return this.#e("GET","/v1/autopilot")}setAutopilot(t){return this.#e("POST","/v1/settings",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(sw)})}catch(i){let u=yr(i),l=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new C(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${l}`)}let a=await s.text(),c;try{c=a===""?{}:JSON.parse(a)}catch{throw new C("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw sd(s.status,c);return c}},aw={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function sd(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?aw[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":pt(e));return s==="not-signed-in"?new C(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new C(s,r,o)}function iw(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new C("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var lw={"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","table.auto":"auto","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"},cw="cloud-project-shards",st=class{target;ref;#t;constructor(t,n=R){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:At(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))}})}check(){return this.#e({action:"check"})}autopilot(){return this.#e({action:"autopilot"})}setAutopilot(t){return this.#e({action:"settings",settings: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=lw[t.op];if(!n)throw new C("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 C("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 C("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(cw,{ref:this.ref,...t})}catch(n){throw dw(n)}}};function At(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 dw(e){if(!(e instanceof Re))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return sd(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":pt(e.status);return new C(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Bn(e){if(Array.isArray(e))return`[${e.map(Bn).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)}:${Bn(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var oa=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}
537
+ ${Bn(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
538
+ ${Bn(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
539
+ ${Bn(t)}`)}},uw=new oa,sa=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","table.auto","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],pw="2024-11-05";function ee(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var J={type:"string"},ad={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:[...sa]}},required:["op"],additionalProperties:!0},Yr={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},yt={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)."},Kn=[{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:ee({})},{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:ee({})},{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:ee({name:J,region:J,teamId:J},["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:ee({ref:J},["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:ee({ref:J},["ref"])},{name:"scale_project",description:"More capacity for a project's app API (auth, the data API and the router): 1 to 3 instances of it take its calls. The database is not copied by this, and it is not failover: keeping a project running is the cloud's job on every plan. Without instances, says how many it runs now and how many the plan allows. Without confirm: true it only returns the plan for that number and changes nothing: show it to the user, and when they agree call again with confirm: true. A refusal (the plan, not open yet, a sharded project, no room) comes back in a sentence to show as it is.",inputSchema:ee({ref:J,instances:{type:"integer",enum:[1,2,3]},confirm:{type:"boolean"}},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:ee({ref:J},["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), or a COMMIT, END or ROLLBACK that would end the transaction the file runs in, is refused before anything runs; a file starting with -- snoutdata:no-transaction runs as written, outside a transaction.",inputSchema:ee({ref:J,dir:J,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:ee({ref:J,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:ee({ref:J},["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:ee({ref:J},["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:ee({ref:J},["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:ee({})},{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:ee({})},{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:ee({name:J,expires:J,project:J},["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:ee({id:J},["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:ee({ref:J,name:J,dir:J,entrypoint:J,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:ee({ref:J},["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:ee({ref:J,name:J,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:ee({ref:J,name:J},["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:ee({ref:J},["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:ee({ref:J,name:J,image:J,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:ee({ref:J,name:J},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:ee({ref:J,name:J},["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:ee({ref:J,name:J,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:ee({ref:J},["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, "140.82.112.0/20: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:ee({ref:J,allow:{type:"array",items:J},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:ee({ref:J},["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:ee({ref:J,name:J,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:ee({ref:J},["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:ee({ref:J},["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:ee({ref:J,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:ee({ref:J},["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:ee({ref:J,hostname:J},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:ee({ref:J,hostname:J},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:ee({ref:J,hostname:J},["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:ee({ref:J},["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:ee({ref:J,at:J,name:J},["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:ee({project:yt})},{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:ee({project:yt,sampleMs:{type:"integer"}})},{name:"cluster_check",description:"The cluster's design check: reads every table's distribution key, foreign keys, unique keys and indexes, and says what keeps related rows off one node or makes a query scan (a table under a distributed one with no distribution key column, a foreign key without the key, a lookup that should be a reference table, a foreign key with no index). Each finding has a fix, SQL to run, or an `operation` cluster_run takes as it is. RUNS NOTHING.",inputSchema:ee({project:yt})},{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:ee({project:yt,operation:ad},["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:ee({project:yt,operation:ad,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:ee({project:yt,id:Yr})},{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:ee({project:yt,id:Yr,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:ee({project:yt,id:Yr},["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:ee({project:yt,id:Yr},["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:ee({ref:J},["ref"])}],mw=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_check","cluster_plan","cluster_jobs","cluster_watch_job"]),fw=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),id=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function aa(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(aa)};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 ia(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 gw(e){return{...e,annotations:mw.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:fw.has(e.name)}}}function hw(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Hn(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Xr(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function O(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Hn(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function yw(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Fn(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 na(e){return{id:e.id,op:e.op,state:e.state,finished:jn(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 ld(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"||!sa.includes(r.op))return`${t}: operation.op must be one of ${sa.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 ra(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 ww(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var bw="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 Sw(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 O(e,bw,!0);s=r.shards(a)}catch(u){return O(e,u instanceof Error?u.message:String(u),!0)}let c=o.shardsPlans??uw,i=a??"";try{switch(t){case"cluster_status":return O(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,l=await s.advice(u);for(let m of l.advice)m.plan&&c.remember(m.request,i);return O(e,{summary:l.summary,advice:l.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:Fn(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:l.facts,assumptions:l.assumptions,...l.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_check":{let u=await s.check();return O(e,{...u,...u.findings.length>0?{next:"Nothing has run. Show the user the findings, errors first. SQL is theirs to run; an operation is planned with cluster_plan and run with cluster_run."}:{}})}case"cluster_plan":{let u=ld(n,t);if(typeof u=="string")return O(e,u,!0);let l=await s.plan(u);return c.remember(u,i),O(e,{plan:Fn(l),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=ld(n,t);if(typeof u=="string")return O(e,u,!0);if(n.confirm!==!0||!c.shown(u,i)){let m=await s.plan(u);c.remember(u,i);let y=n.confirm===!0;return O(e,{ran:!1,plan:Fn(m),next:y?"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."},y)}let l=await s.submit(u);return c.forget(u,i),l.job===void 0?O(e,{ran:!0,done:l.done,answer:l.answer}):O(e,{ran:!0,job:l.job,plan:Fn(l.plan),next:`Follow it with cluster_watch_job {"id": ${l.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return O(e,{jobs:await s.jobs()});let u=ra(n);if(u===null)return O(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let l=await s.job(u);return O(e,{...na(l),plan:l.plan&&Array.isArray(l.plan.steps)?Fn(l.plan):null})}case"cluster_watch_job":{let u=ra(n);if(u===null)return O(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let l=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+l*1e3;for(;;){let y=await s.job(u);if(jn(y.state))return O(e,na(y),y.state!=="done");if(Date.now()>=m)return O(e,{...na(y),note:`Still ${y.state} after ${l}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(T=>setTimeout(T,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=ra(n);if(u===null)return O(e,`${t} needs id, a job number.`,!0);let l=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return O(e,{...l,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return Xr(e,-32602,`unknown tool: ${t}`)}}catch(u){return O(e,ww(u),!0)}}function re(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function cd(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 Hn(r,{protocolVersion:pw,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Hn(r,{});case"tools/list":return Hn(r,{tools:hw([...Kn.map(gw),...n.borrowed??[]])});case"tools/call":return kw(r,s,t,n);default:return Xr(r,-32601,`unknown method: ${o}`)}}async function kw(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Kn.some(l=>l.name===o)||(r.borrowed?.some(l=>l.name===o)??!1)))return Xr(e,-32602,`unknown tool: ${o}`);let c=["get_connection_url","pause_project","resume_project","scale_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_")&&Kn.some(l=>l.name===o))return Sw(e,o,s,n,r);let i=re(s,"ref");if(c.includes(o)&&!i)return O(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!id.includes(o))return O(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${id.join(", ")}.`,!0);try{switch(o){case"whoami":return O(e,await n.whoami());case"list_projects":return O(e,aa(await n.listProjects()));case"create_project":{let l=re(s,"name");return l?O(e,await n.createProject({name:l,region:re(s,"region")??void 0,teamId:re(s,"teamId")??void 0})):O(e,"create_project needs a name.",!0)}case"get_connection_url":{let l=await n.connection(i);return O(e,{url:l.uri,state:l.state,wakesInstantly:l.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 O(e,await n.setProjectState("pause",i));case"resume_project":return O(e,await n.setProjectState("resume",i));case"scale_project":{let l=s.instances;if(l!==void 0&&l!==1&&l!==2&&l!==3)return O(e,"instances is 1, 2 or 3.",!0);let m=s.confirm===!0&&l!==void 0;return O(e,await n.scaleProject(i,l,m))}case"usage":{let l=typeof s.days=="number"?s.days:void 0;return O(e,await n.usage(i,l))}case"export_status":return O(e,await n.exportStatus(i));case"start_export":return O(e,await n.requestExport(i));case"reset_password":{let l=await n.resetPassword(i),m=await n.connection(i);return O(e,{url:m.uri,appliesIn:l.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 O(e,await n.listTeams());case"get_project":return O(e,aa(await n.getProject(i)));case"list_products":return O(e,await n.getProducts(i));case"set_product":{let l=re(s,"product");return l!=="auth"&&l!=="storage"&&l!=="data-api"?O(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?O(e,"set_product needs enabled: true or false.",!0):O(e,await n.setProduct(i,l,s.enabled))}case"list_domains":return O(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let l=re(s,"hostname");return l?O(e,await n.domainAction(i,o.split("_")[0],l)):O(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let l=await n.restoreWindow(i),m=l&&l.restore?ia(i,l.restore):null;return O(e,m?{...l,reason:m}:l)}case"restore_to_point":{let l=re(s,"at");return l?O(e,await n.restoreTo(i,l,re(s,"name")??void 0)):O(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let l=re(s,"ref"),m=re(s,"name");return!l||!m?O(e,"deploy_function needs a project ref and a function name.",!0):O(e,await n.deployFunction(l,m,{dir:re(s,"dir")??void 0,entrypoint:re(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let l=re(s,"ref");return l?O(e,await n.listFunctions(l)):O(e,"list_functions needs a project ref.",!0)}case"size_function":{let l=re(s,"ref"),m=re(s,"name");if(!l||!m)return O(e,"size_function needs a project ref and a function name.",!0);let y=typeof s.memoryMb=="number"?s.memoryMb:void 0,T=typeof s.concurrency=="number"?s.concurrency:void 0,A=s.reset===!0;return!A&&y===void 0&&T===void 0?O(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):O(e,await n.sizeFunction(l,m,{memoryMb:y,concurrency:T,reset:A}))}case"delete_function":{let l=re(s,"ref"),m=re(s,"name");return!l||!m?O(e,"delete_function needs a project ref and a function name.",!0):O(e,await n.deleteFunction(l,m))}case"list_containers":{let l=re(s,"ref");return l?O(e,await n.listContainers(l)):O(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let l=re(s,"ref"),m=re(s,"name"),y=re(s,"image");if(!l||!m||!y)return O(e,"deploy_container needs a project ref, a container name and an image.",!0);let T=typeof s.port=="number"?s.port:void 0;return O(e,await n.deployContainer(l,m,{image:y,...T===void 0?{}:{port:T}}))}case"restart_container":case"delete_container":{let l=re(s,"ref"),m=re(s,"name");return!l||!m?O(e,`${o} needs a project ref and a container name.`,!0):O(e,o==="restart_container"?await n.restartContainer(l,m):await n.deleteContainer(l,m))}case"container_logs":{let l=re(s,"ref");if(!l)return O(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return O(e,await n.containerLogs(l,{name:re(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"container_egress":{let l=re(s,"ref");return l?O(e,await n.containerEgress(l)):O(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let l=re(s,"ref");if(!l)return O(e,"set_container_egress needs a project ref.",!0);let m=s.clear===!0,y=s.allow;return y!==void 0&&(!Array.isArray(y)||!y.every(T=>typeof T=="string"))?O(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):m===(y!==void 0)?O(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):O(e,await n.setContainerEgress(l,m?null:y))}case"list_function_secrets":{let l=re(s,"ref");return l?O(e,await n.listFunctionSecrets(l)):O(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let l=re(s,"ref");if(!l)return O(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return O(e,await n.functionLogs(l,{name:re(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return O(e,await n.listTokens());case"create_token":{let l=re(s,"name");if(!l)return O(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=re(s,"expires"),y=m?yw(m):void 0;return y===null?O(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):O(e,await n.createToken(l,y,re(s,"project")??void 0))}case"revoke_token":{let l=re(s,"id");return l?O(e,await n.revokeToken(l)):O(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return O(e,await n.pushMigrations(i,{dir:re(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?O(e,await n.setProjectState("delete",i)):O(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(l=>l.name===o)?n.callBorrowed?Hn(e,await n.callBorrowed(o,s)):O(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Xr(e,-32602,`unknown tool: ${o}`)}}catch(l){return O(e,l instanceof Error?l.message:String(l),!0)}}function dd(e){let t=e.split(`
540
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Zr=["auth","storage","data-api","push"];function Ut(e){return{error:e instanceof Error?e.message:String(e)}}async function an(e){let[t,n,r,o]=await Promise.all([R("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Ut),R("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}),Ut),R("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Ut),R("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Ut)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:_w}}var _w={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function pd(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Zr.includes(t))throw new h(`say which product: ${Zr.join(", ")}`);return t}async function la(e,t,n){if(t==="auth"){let r=await R("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 R("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 R(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 md(e){let t=await an(e);b(t,()=>{let n=(c,i,u="")=>[c,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?M("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${X([["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")])}
541
+ `);let s=c=>!!(c&&typeof c=="object"&&"enabled"in c&&!c.enabled),a=["auth","storage","data-api","push"].find(c=>s(c==="data-api"?o:t[c]));a&&d(v(` snoutdata products enable ${a} --ref ${e}`))})}async function fd(e,t,n){let r=await la(e,t,n);b(r,()=>d(r.note))}async function Qr(e){let t=await R("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function ca(e,t,n){if(!n)throw new h(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await R("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function ud(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(`${X(n)}
542
+ `)}async function gd(e,t,n){if(t===void 0||t==="list"){let o=await Qr(e);b(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${X([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":M("no"),s.certificate?.state??""])])}
543
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new h(`unknown command: domains ${t}`);let r=await ca(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(Nr()||ud(r.domain,e),te("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),b(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||ud(r.domain,e)),r.next&&d(r.next)})}async function da(e){return R("cloud-project-restore",{ref:e},"GET")}async function ua(e,t,n){if(Number.isNaN(Date.parse(t)))throw new h(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return R("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function hd(e){let t=await da(e);b(t,()=>{let n=t.restore,r=ia(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(v(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function yd(e,t,n){let r=await ua(e,t,n);b(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${M(r.project.name)} (${r.project.ref}).`),d(v(" snoutdata projects list to see when it is ready"))})}async function pa(e){let{projects:t}=await We(),n=t.find(c=>c.ref===e);if(!n)throw new h(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([an(e),R("cloud-project-functions",{ref:e}).then(c=>(c.functions??[]).map(i=>i.name),Ut),R("cloud-project-secrets",{ref:e}).then(c=>(c.secrets??[]).map(i=>i.name),Ut),Qr(e).then(c=>c.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Ut)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function wd(e){let t=await pa(e);b(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(`${X([["name",n.name],["ref",n.ref],["state",n.serverSilent?`${n.state} (its server is not answering)`: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)]])}
544
+ `)})}pe();be();Pt();pe();de();import{existsSync as vw,mkdirSync as $w,readFileSync as Pw,renameSync as xw,writeFileSync as Ew}from"node:fs";import{homedir as ma}from"node:os";import{dirname as Aw,join as Rw}from"node:path";import{rootCertificates as Tw}from"node:tls";var Ow="SNOUTDATA_DB_SSLMODE";function Nw(e=ma()){let t=Rw(e,".snoutdata","ca-roots.pem"),n=`${Tw.join(`
503
545
  `)}
504
- `,r=null;try{r=hy(t)?wy(t,"utf8"):null}catch{r=null}if(r!==n){yy(_y(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Sy(o,n,{mode:420}),by(o,t)}return t}function $n(e,t=process.env,n=Ds()){if(e==="disable")return{mode:"disable"};let r=t[$y]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Py(n)}}function Pn(e,t=process.env,n=Ds()){let{mode:r,rootCert:o}=$n(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Lc(e){try{let{projects:t}=await je(),n=t.find(r=>r.ref===e);n?.readOnly?ie(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&ie(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function js(e){await Lc(e);let t=await Ce(e);w(t,()=>{process.stdout.write(`${t.uri}
505
- `)})}async function Uc(e,t){if(t.length===0&&!be()){let o=Ft();V("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 Lc(e);let n=await Ce(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=xn("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Pn(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new T("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new T("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Fc(e,t,n="DATABASE_URL"){let r=Nr(e,".env"),o=`${n}=${t}`,s=Ty(e);if(!Or(r))return zt(r,`${o}
506
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&xy(r).mode&63&&ie(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Mc(r,"utf8"),c=new RegExp(`^${n}=.*$`,"m");return c.test(a)?(zt(r,a.replace(c,o)),{path:r,replaced:!0,ignored:s}):(Dc(r,a.endsWith(`
546
+ `,r=null;try{r=vw(t)?Pw(t,"utf8"):null}catch{r=null}if(r!==n){$w(Aw(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Ew(o,n,{mode:420}),xw(o,t)}return t}function Gn(e,t=process.env,n=ma()){if(e==="disable")return{mode:"disable"};let r=t[Ow]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Nw(n)}}function qn(e,t=process.env,n=ma()){let{mode:r,rootCert:o}=Gn(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function $d(e){try{let{projects:t}=await We(),n=t.find(r=>r.ref===e);n?.readOnly?ce(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&ce(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function ga(e){await $d(e);let t=await He(e);b(t,()=>{process.stdout.write(`${t.uri}
547
+ `)})}async function Pd(e,t){if(t.length===0&&!Se()){let o=en();te("usage",`db psql with no statements opens an interactive session, and ${o?Qt(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await $d(e);let n=await He(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=zn("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...qn(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new C("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new C("failed",String(a)))}),r.on("close",a=>o(a??0))})}function xd(e,t,n="DATABASE_URL"){let r=no(e,".env"),o=`${n}=${t}`,s=Lw(e);if(!to(r))return ln(r,`${o}
548
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Iw(r).mode&63&&ce(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=_d(r,"utf8"),c=new RegExp(`^${n}=.*$`,"m");return c.test(a)?(ln(r,a.replace(c,o)),{path:r,replaced:!0,ignored:s}):(kd(r,a.endsWith(`
507
549
  `)?`${o}
508
550
  `:`
509
551
  ${o}
510
- `),{path:r,replaced:!1,ignored:s})}function Ty(e){let t=e;for(;!Or(Nr(t,".git"));){let s=Ay(t);if(s===t)return!1;t=s}let n=Nr(e,".gitignore"),r=Or(n)?Mc(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Dc(n,r===""||r.endsWith(`
552
+ `),{path:r,replaced:!1,ignored:s})}function Lw(e){let t=e;for(;!to(no(t,".git"));){let s=Mw(t);if(s===t)return!1;t=s}let n=no(e,".gitignore"),r=to(n)?_d(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(kd(n,r===""||r.endsWith(`
511
553
  `)?`.env
512
554
  `:`
513
555
  .env
514
- `),!0)}async function Bc(e,t={}){if(t.statusOnly){let{export:a}=await Ht(e);return w({ref:e,export:a},()=>Ms(a)),0}let n=await mn(e);if(!n.export.pending&&n.export.url&&!t.out)return w({ref:e,export:n.export},()=>Ms(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Ny(e,t.timeoutMs);if(r.error&&V("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return w({ref:e,export:r},()=>Ms(r)),0;r.url||V("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Iy(r.url,t.out),s=r.rolesSql?Oy(t.out):null;return s&&r.rolesSql&&zt(s,r.rolesSql.endsWith(`
556
+ `),!0)}async function Ed(e,t={}){if(t.statusOnly){let{export:a}=await nn(e);return b({ref:e,export:a},()=>fa(a)),0}let n=await In(e);if(!n.export.pending&&n.export.url&&!t.out)return b({ref:e,export:n.export},()=>fa(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Uw(e,t.timeoutMs);if(r.error&&te("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return b({ref:e,export:r},()=>fa(r)),0;r.url||te("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Fw(r.url,t.out),s=r.rolesSql?jw(t.out):null;return s&&r.rolesSql&&ln(s,r.rolesSql.endsWith(`
515
557
  `)?r.rolesSql:`${r.rolesSql}
516
- `,"utf8"),w({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${it(o)} to ${t.out}.`),d(b(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(b("Into any other Postgres, in this order:")),d(b(` psql --dbname <your database> -f ${s}`)),d(b(` pg_restore --dbname <your database> ${t.out}`)),d(b("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(b("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(b("Owners and grants are skipped: this export carries no roles file.")))}),0}function Oy(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Ny(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Ht(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 Iy(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 jc(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 Ms(e){if(e.error){ie(`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 ${st(e.completedAt)}, ${it(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
517
- `),e.urlExpiresAt&&d(b(`That link stops working ${st(e.urlExpiresAt)}.`))):d(b("The link has expired. Run the export again to sign a new one; the copy is kept."))}function it(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 Hc(e,t){let n=t.file;n||V("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Or(n)||V("usage",`no file at ${n}`,{file:n});let r=await jc(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=pc(o.slice(0,s.bytesRead)),c=await Ce(e);c.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await je(),u=i.find(ee=>ee.ref===e),l=await Cy(c);l===null&&V("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=mc(a,{existingTables:l,readOnly:u?.readOnly===!0},{force:t.force});m.ok||V("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let ee=await Cc("pg_restore",["--list",n]),z=await Cc("pg_restore",["--section=post-data","--file=-",n]);if((ee.code!==0||z.code!==0)&&V("failed",`pg_restore could not read ${n}: ${(ee.stderr||z.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=gc(ee.stdout,z.stdout),h.products.length>0){let ce=await Wt(e),C=L=>!!(L&&typeof L=="object"&&"enabled"in L&&L.enabled),de=h.products.filter(L=>!C(ce[L]));de.length>0&&V("conflict",`this dump holds ${de.length>1?`${de.slice(0,-1).join(", ")} and ${de[de.length-1]}`:de[0]} data, and ${e} has ${de.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${de.length===1?"it":"them"} on, wait a minute, and run this again:
518
- ${de.map(L=>` snoutdata products enable ${L} --ref ${e}`).join(`
519
- `)}`,{ref:e,file:n,refusal:"products-off",products:de})}}if(h&&h.platformData.length>0){let ee=h.platformData.flatMap(({tables:C})=>C),z=await Dy(c,ee);z===null&&V("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let ce=h.platformData.filter(({tables:C})=>C.some(de=>z.includes(de))).map(({product:C})=>C);ce.length>0&&V("conflict",`${ce.join(", ")} on ${e} ${ce.length===1?"has":"have"} not finished setting up ${ce.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:ce})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let x=h?await My(c,n,h):await Tr(c,m.tool,n),R=fc(x.stderr);for(let ee of h?.notes??[])d(b(ee));return x.code!==0&&R.fatal.length===0&&R.total>0?(w({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:R.harmless},()=>{d(`Restored into ${e}.`),d(`${R.harmless} error${R.harmless===1?"":"s"} above ${R.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&&V("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:R.fatal}),w({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function Cy(e){return new Promise(t=>{let n=xn("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,...Pn(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{ie(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 Dy(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=xn("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,...Pn(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(`
520
- `).map(c=>c.trim()).filter(Boolean):null))})}function Cc(e,t){return new Promise(n=>{let r=xn(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=>{ie(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 My(e,t,n){let r=Nr(Ry(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:c,clear:i,lines:u}of n.platformData){if(i.length>0){zt(`${r}.sql`,`set role ${_s[c]};
521
- ${i.map(h=>`delete from ${h};`).join(`
558
+ `,"utf8"),b({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${wt(o)} to ${t.out}.`),d(v(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(v("Into any other Postgres, in this order:")),d(v(` psql --dbname <your database> -f ${s}`)),d(v(` pg_restore --dbname <your database> ${t.out}`)),d(v("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(v("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(v("Owners and grants are skipped: this export carries no roles file.")))}),0}function jw(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Uw(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await nn(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 Fw(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 vd(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 fa(e){if(e.error){ce(`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 ${gt(e.completedAt)}, ${wt(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
559
+ `),e.urlExpiresAt&&d(v(`That link stops working ${gt(e.urlExpiresAt)}.`))):d(v("The link has expired. Run the export again to sign a new one; the copy is kept."))}function wt(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 Ad(e,t){let n=t.file;n||te("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),to(n)||te("usage",`no file at ${n}`,{file:n});let r=await vd(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Zc(o.slice(0,s.bytesRead)),c=await He(e);c.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await We(),u=i.find(oe=>oe.ref===e),l=await Bw(c);l===null&&te("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=Qc(a,{existingTables:l,readOnly:u?.readOnly===!0},{force:t.force});m.ok||te("conflict",m.reason,{ref:e,file:n,refusal:m.code});let y=null;if(m.tool==="pg_restore"){let oe=await Sd("pg_restore",["--list",n]),Z=await Sd("pg_restore",["--section=post-data","--file=-",n]);if((oe.code!==0||Z.code!==0)&&te("failed",`pg_restore could not read ${n}: ${(oe.stderr||Z.stderr).trim().slice(0,300)}`,{ref:e,file:n}),y=td(oe.stdout,Z.stdout),y.products.length>0){let le=await an(e),G=V=>!!(V&&typeof V=="object"&&"enabled"in V&&V.enabled),ue=y.products.filter(V=>!G(le[V]));ue.length>0&&te("conflict",`this dump holds ${ue.length>1?`${ue.slice(0,-1).join(", ")} and ${ue[ue.length-1]}`:ue[0]} data, and ${e} has ${ue.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ue.length===1?"it":"them"} on, wait a minute, and run this again:
560
+ ${ue.map(V=>` snoutdata products enable ${V} --ref ${e}`).join(`
561
+ `)}`,{ref:e,file:n,refusal:"products-off",products:ue})}}if(y&&y.platformData.length>0){let oe=y.platformData.flatMap(({tables:G})=>G),Z=await Hw(c,oe);Z===null&&te("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let le=y.platformData.filter(({tables:G})=>G.some(ue=>Z.includes(ue))).map(({product:G})=>G);le.length>0&&te("conflict",`${le.join(", ")} on ${e} ${le.length===1?"has":"have"} not finished setting up ${le.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:le})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let T=y?await Kw(c,n,y):await eo(c,m.tool,n),A=ed(T.stderr);for(let oe of y?.notes??[])d(v(oe));return T.code!==0&&A.fatal.length===0&&A.total>0?(b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:A.harmless},()=>{d(`Restored into ${e}.`),d(`${A.harmless} error${A.harmless===1?"":"s"} above ${A.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):(T.code!==0&&te("failed",`${m.tool} exited ${T.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:A.fatal}),b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function Bw(e){return new Promise(t=>{let n=zn("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,...qn(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{ce(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 Hw(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=zn("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,...qn(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(`
562
+ `).map(c=>c.trim()).filter(Boolean):null))})}function Sd(e,t){return new Promise(n=>{let r=zn(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=>{ce(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 Kw(e,t,n){let r=no(Dw(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:c,clear:i,lines:u}of n.platformData){if(i.length>0){ln(`${r}.sql`,`set role ${Qs[c]};
563
+ ${i.map(y=>`delete from ${y};`).join(`
522
564
  `)}
523
- `,"utf8");let m=await Tr(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}zt(`${r}.platform`,`${u.join(`
565
+ `,"utf8");let m=await eo(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}ln(`${r}.platform`,`${u.join(`
524
566
  `)}
525
- `,"utf8");let l=await Tr(e,"pg_restore",t,["--data-only",`--role=${_s[c]}`,`--use-list=${r}.platform`]);o=o||l.code,s+=l.stderr}zt(`${r}.rest`,`${n.rest.join(`
567
+ `,"utf8");let l=await eo(e,"pg_restore",t,["--data-only",`--role=${Qs[c]}`,`--use-list=${r}.platform`]);o=o||l.code,s+=l.stderr}ln(`${r}.rest`,`${n.rest.join(`
526
568
  `)}
527
- `,"utf8");let a=await Tr(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{Ey(`${r}${a}`)}catch{}}}function Tr(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 c=xn(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Pn(e.ssl)}}),i="";c.stderr?.on("data",u=>{let l=String(u);i+=l,process.stderr.write(l)}),c.on("error",u=>{ie(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),c.on("close",u=>a({code:u??0,stderr:i}))})}pe();xe();le();function Ly(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 Kc(e,t,n){let r=Ly(t,n.level);if(r.action==="list"){let s=await P("cloud-project-db-access",{ref:e,action:"list"});w(s,()=>Uy(s));return}if(r.action==="grant"){let s=await P("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});w(s,()=>Fy(s));return}let o=await P("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});w(o,()=>Hy(o))}function Uy(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(`${W([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?O(n.email):n.email,n.level,n.role])])}
528
- `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(b(` psql "${t.connection.keywords}"`)))}function Fy(e){e.connection&&process.stdout.write(`${e.connection.keywords}
529
- `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${O(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(b(` psql "${e.connection.keywords}"`))}function By(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Hy(e){let t=By(e);for(let n of t.slice(0,-1))d(n);ie(t[t.length-1])}pe();xe();le();var Ky=6e4;async function Ls(e,t={}){return P("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function Gy(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 qy(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Gc(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 Wy={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 qc(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[qy(e.at),Wy[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function zy(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function Jy(e){let t=e.inspect;if(d(O(`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(O(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(b(` 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,Gc(Date.now()-a.joined_at),Gc(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>Ky?" (silent: likely gone)":"")]);d(W([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
569
+ `,"utf8");let a=await eo(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{Cw(`${r}${a}`)}catch{}}}function eo(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 c=zn(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...qn(e.ssl)}}),i="";c.stderr?.on("data",u=>{let l=String(u);i+=l,process.stderr.write(l)}),c.on("error",u=>{ce(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),c.on("close",u=>a({code:u??0,stderr:i}))})}pe();Ae();de();function qw(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new h(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new h("db access list takes no argument");if(t!==void 0)throw new h("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new h("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new h(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new h("db access revoke needs an email address or a role name");if(t!==void 0)throw new h("--level is for db access grant");return{action:"revoke",who:r};default:throw new h(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Rd(e,t,n){let r=qw(t,n.level);if(r.action==="list"){let s=await R("cloud-project-db-access",{ref:e,action:"list"});b(s,()=>zw(s));return}if(r.action==="grant"){let s=await R("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});b(s,()=>Ww(s));return}let o=await R("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});b(o,()=>Vw(o))}function zw(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(`${X([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?M(n.email):n.email,n.level,n.role])])}
570
+ `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(v(` psql "${t.connection.keywords}"`)))}function Ww(e){e.connection&&process.stdout.write(`${e.connection.keywords}
571
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${M(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(v(` psql "${e.connection.keywords}"`))}function Jw(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Vw(e){let t=Jw(e);for(let n of t.slice(0,-1))d(n);ce(t[t.length-1])}pe();Ae();de();var Yw=6e4;async function ha(e,t={}){return R("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function Xw(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new h(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function Zw(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Td(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 Qw={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 Od(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[Zw(e.at),Qw[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function eb(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function tb(e){let t=e.inspect;if(d(M(`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(M(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(v(` 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,Td(Date.now()-a.joined_at),Td(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>Yw?" (silent: likely gone)":"")]);d(X([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
530
572
  `).map(a=>` ${a}`).join(`
531
- `))}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} ${zy(a)}`)}}}async function Wc(e,t={}){if(t.watch&&lr())throw new g("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await Ls(e,{channel:t.channel});if(w(n.inspect,()=>Jy(n)),!t.watch)return 0;d(""),d(b("Watching. New events appear below as they happen; Ctrl+C to stop."));let r=n.at+1,o=t.intervalMs??2e3;for(;;){await new Promise(a=>setTimeout(a,o));let s=await Ls(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${qc(a)}
532
- `);if(s.events.length>0){r=Math.max(...s.events.map(c=>c.at))+1;let a=s.inspect.channels.map(c=>`${c.name}: ${Object.keys(c.presence).join(", ")||"nobody tracked"}`).join("; ");process.stdout.write(b(` now ${a||"no channels open"}
533
- `))}}}async function zc(e,t={}){let n=t.since?Gy(t.since):0,r=await Ls(e,{channel:t.channel,since:n});return w({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(`${qc(o)}
534
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(b(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}pe();xe();we();le();var Ir=[{id:"bedrock",name:"Amazon Bedrock",mark:"AB",blurb:"Claude, Llama, Mistral, Qwen and more, in your own AWS account.",kind:"bedrock",suggestedName:"bedrock",region:!0,auth:"bedrock",connect:{console:{label:"AWS IAM console",href:"https://console.aws.amazon.com/iam/home#/users"},steps:["In IAM, make a user for the AI gateway (or reuse one) with a policy allowing bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream and bedrock:Converse on the models you will serve.","Under the user's Security credentials, choose Create access key, for an application running outside AWS, and copy the access key ID and the secret. The secret is shown once.","Or use a Bedrock API key instead: Bedrock console, API keys, Generate.","Pick the region the models are in. Inference profiles starting with us. or global. route across regions from there."]},model:{link:{label:"Bedrock model catalog",href:"https://console.aws.amazon.com/bedrock/home#/model-catalog"},steps:["Open the Bedrock model catalog in the region you picked and choose the model.","If it asks for access, request it under Model access first; Anthropic models ask for a short use-case form once.","Copy its inference profile ID (it starts with us., eu., apac. or global.). A model with no inference profile is called by its model ID."],example:"us.anthropic.claude-sonnet-4-5-20250929-v1:0"},price:{link:{label:"Bedrock pricing",href:"https://aws.amazon.com/bedrock/pricing/"},steps:["Find the model on Bedrock's pricing page under on-demand. AWS quotes per 1,000 tokens for some models: multiply by 1,000 for the per-million rate asked here.",'Leave the prices blank if the shipped catalogue already knows the model; the model list shows "from the catalogue" when it does.']},caching:!0},{id:"anthropic",name:"Anthropic",mark:"An",blurb:"Claude, straight from Anthropic's API.",kind:"anthropic",suggestedName:"anthropic",baseUrl:"https://api.anthropic.com/v1",auth:"api_key",connect:{console:{label:"Anthropic Console, API keys",href:"https://console.anthropic.com/settings/keys"},steps:["In the Anthropic Console, open Settings, API keys, and choose Create key.","Name it after this gateway and copy it. It starts with sk-ant- and is shown once.","Make sure the organisation has credit or a billing plan, or every call is refused."]},model:{link:{label:"Claude models",href:"https://docs.anthropic.com/en/docs/about-claude/models/overview"},steps:["Open Anthropic's models overview and copy the model's API name, not its display name."],example:"claude-sonnet-4-5"},price:{link:{label:"Anthropic API pricing",href:"https://www.anthropic.com/pricing#api"},steps:["Copy the input and output rates per million tokens. Cache reads and cache writes have their own rates on the same page.","Leave the prices blank if the shipped catalogue already knows the model."]},caching:!0},{id:"openai",name:"OpenAI",mark:"OA",blurb:"GPT models, straight from OpenAI's API.",kind:"openai",suggestedName:"openai",baseUrl:"https://api.openai.com/v1",auth:"api_key",connect:{console:{label:"OpenAI API keys",href:"https://platform.openai.com/api-keys"},steps:["On the OpenAI platform, open API keys and choose Create new secret key.","Give it a project and the permission to call models, and copy it. It starts with sk- and is shown once.","Check the project has billing set up; a key without it is refused on the first call."]},model:{link:{label:"OpenAI models",href:"https://platform.openai.com/docs/models"},steps:["Open OpenAI's models page, choose the model and copy its id from the top of its page."],example:"gpt-5-mini"},price:{link:{label:"OpenAI API pricing",href:"https://openai.com/api/pricing/"},steps:['Copy the input and output rates per million tokens; "cached input" is the cache read rate. OpenAI caches by itself, so there is no cache write rate.',"Leave the prices blank if the shipped catalogue already knows the model."]},caching:!1},{id:"openrouter",name:"OpenRouter",mark:"OR",blurb:"Hundreds of models from many providers behind one key.",kind:"openai",suggestedName:"openrouter",baseUrl:"https://openrouter.ai/api/v1",auth:"api_key",connect:{console:{label:"OpenRouter keys",href:"https://openrouter.ai/settings/keys"},steps:["On OpenRouter, open Settings, Keys, and choose Create Key. A credit limit on the key is a good second fence.","Copy it. It starts with sk-or- and is shown once.","Add credits to the account, or every paid model is refused."]},model:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Find the model on OpenRouter and copy its id from under its name: the provider, a slash and the model."],example:"anthropic/claude-sonnet-4.5"},price:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Each model's page shows its input and output price per million tokens. Copy those; OpenRouter adds no fee on top of them for a key you pay for with credits."]},caching:!0},{id:"ollama",name:"Ollama",mark:"Ol",blurb:"A model on a machine of your own, such as the home server.",kind:"openai",suggestedName:"home-ollama",editableUrl:!0,urlExample:"https://home-ai.snoutdata.com/v1",auth:"optional",connect:{console:{label:"Ollama documentation",href:"https://github.com/ollama/ollama/blob/main/docs/openai.md"},steps:["The address must reach Ollama from this gateway, so not localhost: a public HTTPS name, such as one through a Cloudflare tunnel, ending in /v1.","Never leave it open: put a check in front of it (a Cloudflare rule that blocks any request without a secret header) and give that header's name and secret here.","Check it from your own machine first: a request to the address plus /models with the header should list the models, and one without it should be refused."]},model:{steps:["On the Ollama machine, run ollama pull with the model first, if it is not there yet.","Run ollama list and copy the NAME column exactly, tag included."],example:"snout-fim:1.0"},price:{steps:["Your machine charges nothing per token, so the price is what you choose to count against a person's budget: a small rate, such as $0.05 in and $0.10 out, keeps a runaway caller inside it.","Do not leave the price blank: the catalogue does not know your models, and a model nothing prices is refused to every signed-in user."]},caching:!1},{id:"compatible",name:"Any OpenAI-compatible server",mark:"{}",blurb:"vLLM, LM Studio, Together, Groq, DeepSeek and the rest.",kind:"openai",suggestedName:"my-provider",editableUrl:!0,urlExample:"https://api.example.com/v1",auth:"optional",connect:{steps:["Find the server's OpenAI-compatible address in its documentation; it usually ends in /v1.","Make an API key in its console and copy it. A server that checks a header of its own instead of a Bearer token takes the header's name too."]},model:{steps:["Copy the model id from the provider's model list, or from what the address plus /models returns."],example:"deepseek-chat"},price:{steps:["Copy the input and output rates per million tokens from the provider's pricing page.","For a server of your own, choose a small rate so budgets still count it; a model nothing prices is refused to every signed-in user."]},caching:!1}];function Jc(e,t){if(t.kind!==e.kind)return!1;if(e.kind!=="openai")return!0;let n=t.base_url.replace(/\/+$/,"");return e.baseUrl?n===e.baseUrl:!Ir.some(r=>r.baseUrl&&r.kind==="openai"&&r.baseUrl===n)}function Vc(e){return(e.trim().split("/").pop()??"").replace(/^(us|eu|apac|global)\.(anthropic|meta|mistral|amazon|cohere|qwen|deepseek|openai)\./,"").replace(/-v\d+:\d+$/,"").replace(/:/g,"-").toLowerCase()}function Yc(e,t,n){let o=e.auth==="bedrock"&&n.aws===!0?n.accessKeyId?.trim()&&n.secretAccessKey?.trim()?{access_key_id:n.accessKeyId.trim(),secret_access_key:n.secretAccessKey.trim()}:null:n.apiKey?.trim()?{api_key:n.apiKey.trim()}:null;if(e.auth!=="optional"&&!o)return{problem:"Paste the key from the first steps; Psyche checks it with the provider before keeping it."};if(e.kind!=="bedrock"&&!n.baseUrl?.trim())return{problem:"Give the address."};if(e.kind==="bedrock"&&!n.region?.trim())return{problem:"Give the region the model runs in."};let s={kind:e.kind};return e.kind==="bedrock"?s.region=n.region.trim():(s.base_url=n.baseUrl.trim(),n.authHeader?.trim()&&(s.auth_header=n.authHeader.trim())),o&&(s.credential=t),{provider:s,credential:o}}function Yy(e){return e===void 0||e.trim()===""?void 0:Number(e)}function Xc(e){if(!e.upstream.trim())return{problem:"Give the model's id, as the provider names it."};let[t,n,r,o,s]=[e.input,e.output,e.cacheRead,e.cacheWrite,e.maxOutput].map(Yy);if([t,n,r,o].some(c=>c!==void 0&&(!Number.isFinite(c)||c<0)))return{problem:"Prices are numbers, zero or more."};if(t===void 0!=(n===void 0))return{problem:"Give both the input and the output rate, or neither to take the catalogue's."};let a={provider:e.provider,upstream:e.upstream.trim()};return e.caching!==void 0&&(a.prompt_caching=e.caching),e.enabled!==void 0&&(a.enabled=e.enabled),e.access&&(a.access=e.access),t!==void 0&&n!==void 0&&(a.price={input:t,output:n},r!==void 0&&(a.price.cache_read=r),o!==void 0&&(a.price.cache_write=o)),s!==void 0&&Number.isFinite(s)&&s>0&&(a.max_output_tokens=Math.round(s)),{model:a}}var Dr=class{send;constructor(t){this.send=t}providers(){return this.list("admin/providers")}putProvider(t,n){return this.call("PUT",`admin/providers/${Xe(t)}`,n)}deleteProvider(t){return this.none("DELETE",`admin/providers/${Xe(t)}`)}upstreamModels(t){return typeof t=="string"?this.list(`admin/providers/${Xe(t)}/upstream-models`):this.call("POST","admin/upstream-models",t).then(n=>n?.data??[])}models(){return this.list("admin/models")}putModel(t,n){return this.call("PUT",`admin/models/${Cr(t)}`,n)}deleteModel(t){return this.call("DELETE",`admin/models/${Cr(t)}`)}routes(){return this.list("admin/routes")}putRoute(t,n){return this.call("PUT",`admin/routes/${Cr(t)}`,n)}deleteRoute(t){return this.none("DELETE",`admin/routes/${Cr(t)}`)}health(){return this.list("admin/health")}credentials(){return this.list("admin/credentials")}putCredential(t,n){return this.call("PUT",`admin/credentials/${Xe(t)}`,n)}deleteCredential(t){return this.none("DELETE",`admin/credentials/${Xe(t)}`)}keys(){return this.list("admin/keys")}createKey(t){return this.call("POST","admin/keys",t)}revokeKey(t){return this.none("DELETE",`admin/keys/${Xe(t)}`)}budgets(){return this.list("admin/budgets")}createBudget(t){return this.call("POST","admin/budgets",t)}editBudget(t,n){return this.call("PATCH",`admin/budgets/${Xe(t)}`,n)}deleteBudget(t){return this.none("DELETE",`admin/budgets/${Xe(t)}`)}limits(){return this.list("admin/limits")}createLimit(t){return this.call("POST","admin/limits",t)}deleteLimit(t){return this.none("DELETE",`admin/limits/${Xe(t)}`)}groups(){return this.list("admin/groups")}usage(t={}){return this.call("GET",`admin/usage${Zc(t)}`)}requests(t={}){return this.call("GET",`admin/requests${Zc(t)}`)}async list(t){return(await this.send("GET",t))?.data??[]}async call(t,n,r){return await this.send(t,n,r)}async none(t,n){await this.send(t,n)}};function Xe(e){return encodeURIComponent(e)}function Cr(e){return e.split("/").map(Xe).join("/")}function Zc(e){let t=Object.entries(e).filter(([,n])=>n!=null&&n!=="").map(([n,r])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(r))}`);return t.length?`?${t.join("&")}`:""}function En(e){if(e==null||e==="")return null;let t=Number(e);return Number.isFinite(t)?t:null}async function jr(e,t){return P("cloud-project-ai",t===void 0?{ref:e}:{ref:e,enable:t})}function Xy(e){if(e instanceof $e){let t=e.body?.error;if(t&&typeof t=="object"&&typeof t.message=="string")return new $e(e.status,t.message,typeof t.code=="string"?t.code:null,e.body)}return e}function Zy(e){return async(t,n,r)=>{try{return await P("cloud-project-ai",{ref:e,admin:{method:t,path:n,...r===void 0?{}:{body:r}}})}catch(o){throw Xy(o)}}}function ke(e){return new Dr(Zy(e))}function Qy(e){return e instanceof $e&&(e.status===502||e.status===404&&e.serverCode!=="ai-off")}function Us(e){return Number.isInteger(e)?`$${e}`:Math.abs(e*100-Math.round(e*100))<1e-9?`$${e.toFixed(2)}`:`$${e}`}function ed(e){return e?`${Us(e.input)} in, ${Us(e.output)} out`:"no price"}function td(e){return e.every_user?"every user":e.user_id?`user ${e.user_id}`:e.key_id?`key ${e.key_id}`:e.group_id?`group ${e.group_id}`:"the project"}function Fs(e){let t=e.period==="rolling"?`per ${e.days??"?"} days`:e.period==="since"?`since ${e.starts_on??"?"}`:`a ${e.period}`,n=En(e.amount);return`${n===null?`$${e.amount}`:Us(n)} ${t}, ${e.hard?"hard":"soft"}`}function ew(e){let t=En(e);return t===null?"-":`$${t.toFixed(t!==0&&t<.01?4:2)}`}function Mr(e){return e===null?"never":new Date(e*1e3).toISOString().slice(0,16).replace("T"," ")}function nd(e,t=Date.now()){return new Date(t-(e-1)*864e5).toISOString().slice(0,10)}function tw(e,t){return`${e} ${t}${e===1?"":"s"}`}async function rd(e){let t=await jr(e);if(!t.ai.enabled){w({ref:e,enabled:!1,endpoint:t.ai.endpoint},()=>{d(`AI is off for ${e}.`),d(b(` snoutdata ai enable --ref ${e} ${t.ai.restartsDatabase}`))});return}let n=ke(e),[r,o,s]=await Promise.allSettled([n.models(),n.budgets(),n.usage({by:"day",from:nd(30)})]),a=[r,o,s].find(h=>h.status==="rejected");if(a&&Qy(a.reason)){w({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!1},()=>{d(`AI is on for ${e}, and its gateway is still starting. Try again in a minute.`)});return}if(a)throw a.reason;let c=r.value,i=o.value,u=s.value.data,l=u.reduce((h,x)=>h+(En(x.cost)??0),0),m=u.reduce((h,x)=>h+x.requests,0);w({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!0,models:c,budgets:i,last30Days:{requests:m,cost:l}},()=>{d(`${O(`AI for ${e}`)}: on, at ${t.ai.endpoint}`),d(""),c.length===0?(d("No models yet. Add one:"),d(b(" snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(sd(c)),d(""),d(i.length===0?"No budgets.":i.map(h=>`Budget: ${td(h)}, ${Fs(h)}`).join(`
535
- `)),d(`Last 30 days: ${tw(m,"request")}, $${l.toFixed(2)}.`)})}async function od(e,t){let n=await jr(e,t);w(n,()=>{t?(d(`AI is on for ${e}. ${n.ai.restartsDatabase} The gateway answers at ${n.ai.endpoint} within a minute or two.`),d(b(" next: snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(`AI is off for ${e}. Its providers, models, keys and log stay in the project's database, for when it is switched on again.`)})}function sd(e){return W([["MODEL","PROVIDER","UPSTREAM","PRICE PER M TOKENS","SERVED"],...e.map(t=>[t.name,t.provider,t.upstream,ed(t.price)+(t.price_from==="catalogue"?" (catalogue)":""),t.served?"yes":`no: ${t.problem??(t.enabled?"not served":"disabled")}`])])}async function ad(e){let t=await ke(e).models();w(t,()=>{d(t.length===0?`No models on ${e}'s gateway yet.`:sd(t))})}async function id(e){let t=await ke(e).providers();w(t,()=>{if(t.length===0){d(`No providers on ${e}'s gateway yet. \`snoutdata ai models add\` makes one with its first model.`);return}d(W([["PROVIDER","KIND","WHERE","KEY","MODELS"],...t.map(n=>[n.name,n.kind,n.kind==="bedrock"?n.region??"":n.base_url,nw(n),n.models.join(", ")||"-"])]));for(let n of t.filter(r=>r.problem))ie(`${n.name}: ${n.problem}`)})}function nw(e){return e.key==="environment"?"from the environment":e.key==="none"||e.credential===null?"none":e.credential_stored?"stored":"missing"}async function ld(e,t){if(!t)throw new g("ai providers remove needs the provider's name (see `snoutdata ai providers`)");await ke(e).deleteProvider(t),w({ref:e,removed:t},()=>d(`Removed the provider ${t}.`))}function rw(e){let t=Ir.map(r=>r.id).join("|");if(!e)throw new g(`ai models add needs --source ${t}: where the model runs`);let n=Ir.find(r=>r.id===e);if(!n)throw new g(`--source is one of ${t}, not "${e}"`);return n}function ow(e){d(O(`Adding a model from ${e.name}`));let t=[["The key",e.connect],["The model's id",e.model],["Its price",e.price]];for(let[n,r]of t){d(""),d(O(n));for(let s of r.steps)d(` - ${s}`);let o=r.console??r.link;o&&d(b(` ${o.label}: ${o.href}`))}d("")}function sw(e,t,n){if(n)return{name:n,existing:t.find(o=>o.name===n)??null};let r=t.filter(o=>Jc(e,o));if(r.length===1)return{name:r[0].name,existing:r[0]};if(r.length>1)throw new g(`${e.name} is connected more than once (${r.map(o=>o.name).join(", ")}): say which with --provider`);return{name:e.suggestedName,existing:t.find(o=>o.name===e.suggestedName)??null}}async function cd(e,t,n){let r=rw(n.source),o=n.upstream?.trim();if(!o)throw ow(r),new g(`Give the model's id as ${r.name} names it, with --model (for example ${r.model.example})`);let s=t?.trim()||Vc(o),a=ke(e),[c,i]=await Promise.all([a.providers(),a.models()]);if(i.some(C=>C.name===s))throw new g(`${e} already has a model called ${s}. Name this one differently, or remove that one first.`);let{name:u,existing:l}=sw(r,c,n.provider);if(l&&l.kind!==r.kind)throw new g(`The provider ${u} speaks ${l.kind}, not ${r.name}: choose another --provider name`);if(l?.origin==="file"&&n.stdin)throw new g(`The provider ${u} comes from the gateway's providers file, so its key is set there, not here`);let m=n.stdin?await qt():void 0,h=r.auth==="bedrock"&&n.accessKeyId!==void 0,x=null;if(!l){if(r.auth!=="optional"&&!m)throw new g(h?"Pipe the AWS secret access key in with --stdin; --access-key-id is its id":`${r.name} needs a key: pipe it in with --stdin (snoutdata ai models add ... --stdin < key.txt), so it is in neither the shell history nor ps`);let C=Yc(r,u,{baseUrl:n.baseUrl??r.baseUrl,region:n.region,apiKey:h?void 0:m,aws:h,accessKeyId:n.accessKeyId,secretAccessKey:h?m:void 0,authHeader:n.authHeader});if("problem"in C)throw new g(r.kind==="bedrock"&&!n.region?"Give the region the model runs in, with --region":n.baseUrl===void 0&&r.kind!=="bedrock"?"Give the provider's address, with --base-url":C.problem);x=C}let R=Xc({provider:u,upstream:o,input:n.priceIn,output:n.priceOut,caching:r.caching,enabled:!0,access:"everyone"});if("problem"in R)throw new g(R.problem.replace("input and the output rate","--price-in and --price-out"));let ee=!1,z=!1,ce;try{if(x&&(await a.putProvider(u,x.provider),ee=!0),ce=await a.putModel(s,R.model),z=!0,x?.credential)await a.putCredential(u,x.credential);else if(l&&m){if(!l.credential)throw new g(`The provider ${u} takes no key`);await a.putCredential(l.credential,h?{access_key_id:n.accessKeyId,secret_access_key:m}:{api_key:m})}}catch(C){throw z&&await a.deleteModel(s).catch(()=>{}),ee&&await a.deleteProvider(u).catch(()=>{}),C}ce=(await a.models()).find(C=>C.name===s)??ce,w(ce,()=>{d(`Added ${O(s)} (${o} on ${u}${ee?", a new provider":""}), at ${ed(ce.price)}${ce.price_from==="catalogue"?" from the shipped catalogue":""}.`),ce.price||ie("Nothing prices this model, so signed-in users are refused it. Give it a price with --price-in and --price-out."),!ce.served&&ce.problem&&ie(`It is not served yet: ${ce.problem}`),d(b(` snoutdata ai test --model ${s}`))})}async function dd(e,t){if(!t)throw new g("ai models remove needs the model's name (see `snoutdata ai models`)");let n=await ke(e).deleteModel(t);w(n,()=>d(n.disabled?`${t} comes from the gateway's providers file, so it was disabled rather than removed.`:`Removed ${t}.`))}function aw(e){return(e??"").split(",").map(t=>t.trim()).filter(t=>t!=="")}function iw(e){return W([["ROUTE","TRIES, IN ORDER","SORT","SKIPS"],...e.map(t=>[t.name,t.tried.join(", ")||"-",t.sort,t.skipped.join(", ")||"-"])])}async function ud(e){let t=await ke(e).routes();w(t,()=>{if(t.length===0){d(`No routes on ${e}'s gateway. A route is one name for several models, each tried when the one before fails:`),d(b(" snoutdata ai routes set NAME --models first,second [--sort price]"));return}d(iw(t))})}async function pd(e,t,n){if(!t)throw new g("ai routes set needs the route's name: snoutdata ai routes set NAME --models first,second");let r=aw(n.models);if(r.length===0)throw new g("ai routes set needs --models, the models to try in order, comma-separated (see `snoutdata ai models`)");let o=n.sort??"order";if(o!=="order"&&o!=="price"&&o!=="latency"&&o!=="throughput")throw new g(`--sort is order (as written), price (the cheapest first), latency or throughput (the fastest first, as measured), not ${o}`);let s=ke(e);await s.putRoute(t,{targets:r,sort:o});let a=(await s.routes()).find(c=>c.name===t);w(a??{name:t,targets:r,sort:o},()=>{let c=a?.tried??r;d(`${O(t)} tries ${c.join(", then ")}${o==="price"?", the cheapest first":o==="latency"||o==="throughput"?`, the fastest first by ${o} once measured`:""}.`),a&&a.skipped.length>0&&ie(`skipped, not served: ${a.skipped.join(", ")}`),d(b(` Call it as the model "${t}", or: snoutdata ai test --model ${t}`))})}async function md(e,t){if(!t)throw new g("ai routes remove needs the route's name (see `snoutdata ai routes`)");await ke(e).deleteRoute(t),w({name:t,removed:!0},()=>d(`Removed the route ${t}. Its models are untouched.`))}function lw(e,t=Date.now()/1e3){return e.revoked_at!==null?"revoked":e.expires_at!==null&&e.expires_at<=t?"expired":e.expires_at!==null?`until ${Mr(e.expires_at).slice(0,10)}`:"active"}async function fd(e){let t=await ke(e).keys();w(t,()=>{if(t.length===0){d(`No keys on ${e}'s gateway. \`snoutdata ai keys create --name my-server\` makes one for a server.`);return}d(W([["NAME","ID","KEY","MODELS","CREATED","LAST USED","STATE"],...t.map(n=>[n.name,n.id,n.hint,n.models?.join(", ")??"all",Mr(n.created_at),n.last_used_at!==null?Mr(n.last_used_at):n.last_used_on??"never",lw(n)])]))})}async function gd(e,t){if(!t.name)throw new g("ai keys create needs --name: what the key is for, shown in ai keys");if(t.expiresDays!==void 0&&t.expiresDays<=0)throw new g("--expires is a number of days, 1 or more");let n=t.models?.split(",").map(s=>s.trim()).filter(Boolean),r=await ke(e).createKey({name:t.name,...n&&n.length>0?{models:n}:{},...t.expiresDays!==void 0?{expires_in:t.expiresDays*86400}:{}}),o=await jr(e);w({...r,endpoint:o.ai.endpoint},()=>{process.stdout.write(`${r.key}
536
- `),d(""),d(`${O(r.name)}: ${r.models?`for ${r.models.join(", ")}`:"for every model"}, ${r.expires_at?`until ${Mr(r.expires_at)}`:"until revoked"}.`),d("This is the only time it is shown: only its hash is kept. It is a server's key; a browser calls with the app key and a signed-in user instead."),d(b(` OPENAI_BASE_URL=${o.ai.endpoint} OPENAI_API_KEY=<that key>`))})}async function hd(e,t){if(!t)throw new g("ai keys revoke needs the key's id (see `snoutdata ai keys`)");await ke(e).revokeKey(t),w({ref:e,revoked:t},()=>d(`Revoked ${t}. A call with it is refused from now on.`))}async function yd(e){let t=await ke(e).budgets();w(t,()=>{if(t.length===0){d(`No budgets on ${e}'s gateway. A browser call needs two hard ones:`),d(b(" snoutdata ai budgets set project --amount 5")),d(b(" snoutdata ai budgets set users --amount 0.50"));return}d(W([["ID","FOR","BUDGET"],...t.map(n=>[n.id,td(n),Fs(n)])]))})}function cw(e){if(e==="project"||e==="users")return e;throw new g("ai budgets set takes project (the whole project) or users (each signed-in user)")}function dw(e,t,n){return e.find(r=>r.period===n&&!r.key_id&&!r.user_id&&!r.group_id&&(t==="users"?r.every_user:!r.every_user))}async function wd(e,t,n){let r=cw(t),o=Number(n.amount);if(n.amount===void 0||!Number.isFinite(o)||o<0)throw new g("ai budgets set needs --amount, in dollars, zero or more");let s=n.period??"day";if(s!=="day"&&s!=="month")throw new g("--period is day or month");let a=ke(e),c=dw(await a.budgets(),r,s),i=c?await a.editBudget(c.id,{amount:o,hard:!n.soft}):await a.createBudget({name:r==="users"?"every user":"project",amount:o,period:s,hard:!n.soft,...r==="users"?{every_user:!0}:{}});w(i,()=>d(`${c?"Changed":"Set"}: ${r==="users"?"every user":"the project"}, ${Fs(i)}.`))}async function bd(e,t){if(!t)throw new g("ai budgets remove needs the budget's id (see `snoutdata ai budgets`)");await ke(e).deleteBudget(t),w({ref:e,removed:t},()=>d(`Removed the budget ${t}.`))}var Qc=["day","model","provider","operation","key","user","group"];async function Sd(e,t){let n=t.by??"model";if(!Qc.includes(n))throw new g(`--by is one of ${Qc.join("|")}`);let r=t.days??30;if(r<1||r>366)throw new g("--days is 1 to 366");let o=await ke(e).usage({by:n,from:nd(r)});w(o,()=>{if(o.data.length===0){d(`No calls to ${e}'s gateway from ${o.from} to ${o.to}.`);return}let s=o.data.reduce((a,c)=>a+(En(c.cost)??0),0);d(W([[n.toUpperCase(),"REQUESTS","FAILED","TOKENS IN","TOKENS OUT","COST"],...o.data.map(a=>[String(a[n]??"-"),String(a.requests),String(a.failed),String(a.prompt_tokens),String(a.completion_tokens),ew(a.cost)+(a.unpriced>0?` (+${a.unpriced} unpriced)`:"")])])),d(`${o.from} to ${o.to}: $${s.toFixed(2)}.`)})}async function _d(e,t){let n=await jr(e);if(!n.ai.enabled)throw new T("not-found",`AI is off for ${e}. Switch it on first: snoutdata ai enable.`);let r=ke(e),s=(await r.models()).filter(x=>x.served&&x.embeddings===null),a=t.model??s[0]?.name;if(!a)throw new T("not-found",`${e}'s gateway serves no chat model yet. Add one: snoutdata ai models add.`);let c=await r.createKey({name:"snoutdata ai test",models:[a],expires_in:600}),i=Date.now(),u=0,l={};try{let x=await fetch(`${n.ai.endpoint}/chat/completions`,{method:"POST",headers:{authorization:`Bearer ${c.key}`,"content-type":"application/json"},body:JSON.stringify({model:a,max_tokens:64,messages:[{role:"user",content:t.prompt??"Say hello in five words."}]})});u=x.status;let R=await x.text();try{l=R?JSON.parse(R):{}}catch{l={error:{message:fr(x.status,x.statusText,R)}}}}finally{await r.revokeKey(c.id).catch(()=>{})}let m=Date.now()-i;if(u<200||u>=300){let x=l.error?.message??`the gateway answered ${u}`;throw new T(rt(u,l.error?.code??null),`${a} through ${e}'s gateway: ${x}`,{model:a,status:u,gatewayCode:l.error?.code??null})}let h=l.choices?.[0]?.message?.content??"";return w({ref:e,model:a,reply:h,usage:l.usage??null,ms:m},()=>{process.stdout.write(`${h}
537
- `),d(b(`${a}, ${m} ms, ${l.usage?.prompt_tokens??"?"} tokens in, ${l.usage?.completion_tokens??"?"} out, through ${n.ai.endpoint}`))}),0}pe();xe();le();import{readFile as pw}from"node:fs/promises";var mw="api.snoutdata.com";function $d(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}.${mw}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function fw(e){return $d(e,await P("cloud-project-auth",{ref:e,action:"status"}))}async function Lr(e,t){return $d(e,await P("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Bs(e){let t=e.google,n="off";t.ready?n=O("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(`${W([["auth",e.enabled?O("on"):"off"],["site url",e.siteUrl??b("not set")],["redirects",e.redirects.length?e.redirects.join(", "):b("none")],["google",n],["google client id",t.clientId??b("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?O("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
538
- `),e.enabled||d(b(` snoutdata products enable auth --ref ${e.ref}`))}async function gw(){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 Pd(e,t,n,r){if(t===void 0||t==="show"){let o=await fw(e);w(o,()=>Bs(o));return}if(t==="google"){if(n[0]==="off"){let a=await Lr(e,{googleEnabled:!1});w(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 gw();if(!a)throw new g("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Lr(e,o);w(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Bs(s),d(b(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new g('auth anonymous takes "on" or "off"');let s=await Lr(e,{anonymousEnabled:o==="on"});w(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(b(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new g("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Lr(e,o);w(s,()=>Bs(s));return}if(t==="templates"){let o=await P("cloud-project-auth",{ref:e,action:"templates"});w(o,()=>vd(o.templates));return}if(t==="template"){let o=n[0];if(!o||!kd.includes(o))throw new g(`auth template needs a kind: ${kd.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 c=await pw(r.file,"utf8"),i=await P("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(l=>l.kind===o)?.subject;s={kind:o,subject:u,body:c}}let a=await P("cloud-project-auth",{ref:e,action:"template",template:s});w(a,()=>{vd(a.templates),d(b(" 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 kd=["confirmation","recovery","magic_link","invite","email_change"];function vd(e){process.stdout.write(`${W([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?O("yours"):"ours",t.subject])])}
539
- `)}pe();le();we();import{createHash as Dw}from"node:crypto";import{existsSync as Mw,readdirSync as jw,readFileSync as Lw,statSync as Uw}from"node:fs";import{isAbsolute as Fw,join as Ws,relative as Bw,resolve as Hw}from"node:path";function hw(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Ur(e,t,n={}){let r=hw(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let l=o.get(u.name);if(!l){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}l.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,l)=>u<l?-1:u>l?1:0)[t.length-1],c=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!c.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 Jt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Hs,createHmac as Fr,pbkdf2Sync as yw,randomBytes as ww,timingSafeEqual as bw}from"node:crypto";import{readFileSync as Sw}from"node:fs";import{homedir as _w}from"node:os";import{connect as kw,isIP as vw}from"node:net";import{connect as $w}from"node:tls";var bt=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)}},he=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},Pw=196608,xw=80877103;function xd(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function wt(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function Ew(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 Ed(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 Ks=class{constructor(t,n=ww(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(R=>[R.slice(0,1),R.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 he("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new he("the server answered the password exchange with no salt or iteration count");let a=yw(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),c=Fr("sha256",a).update("Client Key").digest(),i=Hs("sha256").update(c).digest(),u=`c=biws,r=${r}`,l=`${this.firstBare},${t},${u}`,m=Fr("sha256",i).update(l).digest(),h=Buffer.alloc(c.length);for(let R=0;R<h.length;R++)h[R]=c[R]^m[R];let x=Fr("sha256",a).update("Server Key").digest();return this.serverSignature=Fr("sha256",x).update(l).digest(),`${u},p=${h.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new he(`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||!bw(o,this.serverSignature))throw new he("the server could not prove it knows the password, so this is not the database it claims to be")}},Gs=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 he(n.message))),t.on("close",()=>this.fail(new he("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 he("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 Aw(e){return new Promise((t,n)=>{let r=c=>{if(a(),c.length!==1){n(new he("the server sent more than its answer to the TLS request, so the connection was closed"));return}t(String.fromCharCode(c[0]))},o=c=>{a(),n(new he(c.message))},s=()=>{a(),n(new he("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 Rw(e,t){return new Promise((n,r)=>{let o=kw({host:e,port:t}),s=a=>r(new he(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function Tw(e,t,n,r){let o=await Rw(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(xw,4),o.write(s),await Aw(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new he(`${e.host} does not accept TLS, and this connection requires it (sslmode ${t})`)}let c=t==="verify-ca"||t==="verify-full";return new Promise((i,u)=>{let l=$w({socket:o,host:e.host,...vw(e.host)===0?{servername:e.host}:{},rejectUnauthorized:c,...c&&n?{ca:Sw(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(l),l.once("secureConnect",()=>{l.off("error",m),i(l)});let m=h=>u(new he(`TLS to ${e.host} failed: ${h.message}`));l.once("error",m)})}var Br=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=$n(t.ssl,r,n.home??_w()),s=o.rootCert??(r.PGSSLROOTCERT||void 0),a=o.mode==="require"&&s?"verify-ca":o.mode,c=n.connectTimeoutMs??12e4,i=[],u=()=>{for(let h of i)h.destroy()},l,m=new Promise((h,x)=>{l=setTimeout(()=>{u(),x(new he(`${t.host}:${t.port} did not answer within ${Math.round(c/1e3)} seconds`))},c)});try{return await Promise.race([(async()=>{let h=await Tw(t,a,s,R=>i.push(R)),x=new e(h,new Gs(h));return await x.startup(t),x})(),m])}catch(h){throw u(),h}finally{clearTimeout(l)}}send(t,n){this.socket.write(xd(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[wt(s),wt(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(Pw,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",wt(t.password));else if(a===5){let c=Hs("md5").update(t.password+t.user).digest("hex"),i=Hs("md5").update(Buffer.concat([Buffer.from(c),s.body.subarray(4,8)])).digest("hex");this.send("p",wt(`md5${i}`))}else if(a===10){let c=Ew(s.body,4);if(!c.includes("SCRAM-SHA-256"))throw new he(`the server asked for ${c.join(", ")||"a password exchange"}, which this client does not speak`);o=new Ks(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([wt("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 he(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new bt(Ed(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",wt(t));let r=[],o=null,s=null;for(;;){let a=await this.reader.next();switch(a.type){case"T":r=[];break;case"D":{let c=a.body.readInt16BE(0),i=[],u=2;for(let l=0;l<c;l++){let m=a.body.readInt32BE(u);u+=4,m<0?i.push(null):(i.push(a.body.toString("utf8",u,u+m)),u+=m)}r.push(i);break}case"C":o=a.body.toString("utf8",0,Math.max(0,a.body.indexOf(0)));break;case"E":s??=new bt(Ed(a.body));break;case"G":n===void 0?this.send("f",wt("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",wt("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(xd("X",Buffer.alloc(0)))}};function Ad(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===`
573
+ `))}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} ${eb(a)}`)}}}async function Nd(e,t={}){if(t.watch&&Nr())throw new h("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await ha(e,{channel:t.channel});if(b(n.inspect,()=>tb(n)),!t.watch)return 0;d(""),d(v("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 ha(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Od(a)}
574
+ `);if(s.events.length>0){r=Math.max(...s.events.map(c=>c.at))+1;let a=s.inspect.channels.map(c=>`${c.name}: ${Object.keys(c.presence).join(", ")||"nobody tracked"}`).join("; ");process.stdout.write(v(` now ${a||"no channels open"}
575
+ `))}}}async function Id(e,t={}){let n=t.since?Xw(t.since):0,r=await ha(e,{channel:t.channel,since:n});return b({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(`${Od(o)}
576
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(v(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}pe();Ae();be();de();var ro=[{id:"bedrock",name:"Amazon Bedrock",mark:"AB",blurb:"Claude, Llama, Mistral, Qwen and more, in your own AWS account.",kind:"bedrock",suggestedName:"bedrock",region:!0,auth:"bedrock",connect:{console:{label:"AWS IAM console",href:"https://console.aws.amazon.com/iam/home#/users"},steps:["In IAM, make a user for the AI gateway (or reuse one) with a policy allowing bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream and bedrock:Converse on the models you will serve.","Under the user's Security credentials, choose Create access key, for an application running outside AWS, and copy the access key ID and the secret. The secret is shown once.","Or use a Bedrock API key instead: Bedrock console, API keys, Generate.","Pick the region the models are in. Inference profiles starting with us. or global. route across regions from there."]},model:{link:{label:"Bedrock model catalog",href:"https://console.aws.amazon.com/bedrock/home#/model-catalog"},steps:["Open the Bedrock model catalog in the region you picked and choose the model.","If it asks for access, request it under Model access first; Anthropic models ask for a short use-case form once.","Copy its inference profile ID (it starts with us., eu., apac. or global.). A model with no inference profile is called by its model ID."],example:"us.anthropic.claude-sonnet-4-5-20250929-v1:0"},price:{link:{label:"Bedrock pricing",href:"https://aws.amazon.com/bedrock/pricing/"},steps:["Find the model on Bedrock's pricing page under on-demand. AWS quotes per 1,000 tokens for some models: multiply by 1,000 for the per-million rate asked here.",'Leave the prices blank if the shipped catalogue already knows the model; the model list shows "from the catalogue" when it does.']},caching:!0},{id:"anthropic",name:"Anthropic",mark:"An",blurb:"Claude, straight from Anthropic's API.",kind:"anthropic",suggestedName:"anthropic",baseUrl:"https://api.anthropic.com/v1",auth:"api_key",connect:{console:{label:"Anthropic Console, API keys",href:"https://console.anthropic.com/settings/keys"},steps:["In the Anthropic Console, open Settings, API keys, and choose Create key.","Name it after this gateway and copy it. It starts with sk-ant- and is shown once.","Make sure the organisation has credit or a billing plan, or every call is refused."]},model:{link:{label:"Claude models",href:"https://docs.anthropic.com/en/docs/about-claude/models/overview"},steps:["Open Anthropic's models overview and copy the model's API name, not its display name."],example:"claude-sonnet-4-5"},price:{link:{label:"Anthropic API pricing",href:"https://www.anthropic.com/pricing#api"},steps:["Copy the input and output rates per million tokens. Cache reads and cache writes have their own rates on the same page.","Leave the prices blank if the shipped catalogue already knows the model."]},caching:!0},{id:"openai",name:"OpenAI",mark:"OA",blurb:"GPT models, straight from OpenAI's API.",kind:"openai",suggestedName:"openai",baseUrl:"https://api.openai.com/v1",auth:"api_key",connect:{console:{label:"OpenAI API keys",href:"https://platform.openai.com/api-keys"},steps:["On the OpenAI platform, open API keys and choose Create new secret key.","Give it a project and the permission to call models, and copy it. It starts with sk- and is shown once.","Check the project has billing set up; a key without it is refused on the first call."]},model:{link:{label:"OpenAI models",href:"https://platform.openai.com/docs/models"},steps:["Open OpenAI's models page, choose the model and copy its id from the top of its page."],example:"gpt-5-mini"},price:{link:{label:"OpenAI API pricing",href:"https://openai.com/api/pricing/"},steps:['Copy the input and output rates per million tokens; "cached input" is the cache read rate. OpenAI caches by itself, so there is no cache write rate.',"Leave the prices blank if the shipped catalogue already knows the model."]},caching:!1},{id:"openrouter",name:"OpenRouter",mark:"OR",blurb:"Hundreds of models from many providers behind one key.",kind:"openai",suggestedName:"openrouter",baseUrl:"https://openrouter.ai/api/v1",auth:"api_key",connect:{console:{label:"OpenRouter keys",href:"https://openrouter.ai/settings/keys"},steps:["On OpenRouter, open Settings, Keys, and choose Create Key. A credit limit on the key is a good second fence.","Copy it. It starts with sk-or- and is shown once.","Add credits to the account, or every paid model is refused."]},model:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Find the model on OpenRouter and copy its id from under its name: the provider, a slash and the model."],example:"anthropic/claude-sonnet-4.5"},price:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Each model's page shows its input and output price per million tokens. Copy those; OpenRouter adds no fee on top of them for a key you pay for with credits."]},caching:!0},{id:"ollama",name:"Ollama",mark:"Ol",blurb:"A model on a machine of your own, such as the home server.",kind:"openai",suggestedName:"home-ollama",editableUrl:!0,urlExample:"https://home-ai.snoutdata.com/v1",auth:"optional",connect:{console:{label:"Ollama documentation",href:"https://github.com/ollama/ollama/blob/main/docs/openai.md"},steps:["The address must reach Ollama from this gateway, so not localhost: a public HTTPS name, such as one through a Cloudflare tunnel, ending in /v1.","Never leave it open: put a check in front of it (a Cloudflare rule that blocks any request without a secret header) and give that header's name and secret here.","Check it from your own machine first: a request to the address plus /models with the header should list the models, and one without it should be refused."]},model:{steps:["On the Ollama machine, run ollama pull with the model first, if it is not there yet.","Run ollama list and copy the NAME column exactly, tag included."],example:"snout-fim:1.0"},price:{steps:["Your machine charges nothing per token, so the price is what you choose to count against a person's budget: a small rate, such as $0.05 in and $0.10 out, keeps a runaway caller inside it.","Do not leave the price blank: the catalogue does not know your models, and a model nothing prices is refused to every signed-in user."]},caching:!1},{id:"compatible",name:"Any OpenAI-compatible server",mark:"{}",blurb:"vLLM, LM Studio, Together, Groq, DeepSeek and the rest.",kind:"openai",suggestedName:"my-provider",editableUrl:!0,urlExample:"https://api.example.com/v1",auth:"optional",connect:{steps:["Find the server's OpenAI-compatible address in its documentation; it usually ends in /v1.","Make an API key in its console and copy it. A server that checks a header of its own instead of a Bearer token takes the header's name too."]},model:{steps:["Copy the model id from the provider's model list, or from what the address plus /models returns."],example:"deepseek-chat"},price:{steps:["Copy the input and output rates per million tokens from the provider's pricing page.","For a server of your own, choose a small rate so budgets still count it; a model nothing prices is refused to every signed-in user."]},caching:!1}];function Cd(e,t){if(t.kind!==e.kind)return!1;if(e.kind!=="openai")return!0;let n=t.base_url.replace(/\/+$/,"");return e.baseUrl?n===e.baseUrl:!ro.some(r=>r.baseUrl&&r.kind==="openai"&&r.baseUrl===n)}function Md(e){return(e.trim().split("/").pop()??"").replace(/^(us|eu|apac|global)\.(anthropic|meta|mistral|amazon|cohere|qwen|deepseek|openai)\./,"").replace(/-v\d+:\d+$/,"").replace(/:/g,"-").toLowerCase()}function Dd(e,t,n){let o=e.auth==="bedrock"&&n.aws===!0?n.accessKeyId?.trim()&&n.secretAccessKey?.trim()?{access_key_id:n.accessKeyId.trim(),secret_access_key:n.secretAccessKey.trim()}:null:n.apiKey?.trim()?{api_key:n.apiKey.trim()}:null;if(e.auth!=="optional"&&!o)return{problem:"Paste the key from the first steps; Psyche checks it with the provider before keeping it."};if(e.kind!=="bedrock"&&!n.baseUrl?.trim())return{problem:"Give the address."};if(e.kind==="bedrock"&&!n.region?.trim())return{problem:"Give the region the model runs in."};let s={kind:e.kind};return e.kind==="bedrock"?s.region=n.region.trim():(s.base_url=n.baseUrl.trim(),n.authHeader?.trim()&&(s.auth_header=n.authHeader.trim())),o&&(s.credential=t),{provider:s,credential:o}}function rb(e){return e===void 0||e.trim()===""?void 0:Number(e)}function Ld(e){if(!e.upstream.trim())return{problem:"Give the model's id, as the provider names it."};let[t,n,r,o,s]=[e.input,e.output,e.cacheRead,e.cacheWrite,e.maxOutput].map(rb);if([t,n,r,o].some(c=>c!==void 0&&(!Number.isFinite(c)||c<0)))return{problem:"Prices are numbers, zero or more."};if(t===void 0!=(n===void 0))return{problem:"Give both the input and the output rate, or neither to take the catalogue's."};let a={provider:e.provider,upstream:e.upstream.trim()};return e.caching!==void 0&&(a.prompt_caching=e.caching),e.enabled!==void 0&&(a.enabled=e.enabled),e.access&&(a.access=e.access),t!==void 0&&n!==void 0&&(a.price={input:t,output:n},r!==void 0&&(a.price.cache_read=r),o!==void 0&&(a.price.cache_write=o)),s!==void 0&&Number.isFinite(s)&&s>0&&(a.max_output_tokens=Math.round(s)),{model:a}}var so=class{send;constructor(t){this.send=t}providers(){return this.list("admin/providers")}putProvider(t,n){return this.call("PUT",`admin/providers/${at(t)}`,n)}deleteProvider(t){return this.none("DELETE",`admin/providers/${at(t)}`)}upstreamModels(t){return typeof t=="string"?this.list(`admin/providers/${at(t)}/upstream-models`):this.call("POST","admin/upstream-models",t).then(n=>n?.data??[])}models(){return this.list("admin/models")}putModel(t,n){return this.call("PUT",`admin/models/${oo(t)}`,n)}deleteModel(t){return this.call("DELETE",`admin/models/${oo(t)}`)}routes(){return this.list("admin/routes")}putRoute(t,n){return this.call("PUT",`admin/routes/${oo(t)}`,n)}deleteRoute(t){return this.none("DELETE",`admin/routes/${oo(t)}`)}health(){return this.list("admin/health")}credentials(){return this.list("admin/credentials")}putCredential(t,n){return this.call("PUT",`admin/credentials/${at(t)}`,n)}deleteCredential(t){return this.none("DELETE",`admin/credentials/${at(t)}`)}keys(){return this.list("admin/keys")}createKey(t){return this.call("POST","admin/keys",t)}revokeKey(t){return this.none("DELETE",`admin/keys/${at(t)}`)}budgets(){return this.list("admin/budgets")}createBudget(t){return this.call("POST","admin/budgets",t)}editBudget(t,n){return this.call("PATCH",`admin/budgets/${at(t)}`,n)}deleteBudget(t){return this.none("DELETE",`admin/budgets/${at(t)}`)}limits(){return this.list("admin/limits")}createLimit(t){return this.call("POST","admin/limits",t)}deleteLimit(t){return this.none("DELETE",`admin/limits/${at(t)}`)}groups(){return this.list("admin/groups")}usage(t={}){return this.call("GET",`admin/usage${jd(t)}`)}requests(t={}){return this.call("GET",`admin/requests${jd(t)}`)}async list(t){return(await this.send("GET",t))?.data??[]}async call(t,n,r){return await this.send(t,n,r)}async none(t,n){await this.send(t,n)}};function at(e){return encodeURIComponent(e)}function oo(e){return e.split("/").map(at).join("/")}function jd(e){let t=Object.entries(e).filter(([,n])=>n!=null&&n!=="").map(([n,r])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(r))}`);return t.length?`?${t.join("&")}`:""}function Wn(e){if(e==null||e==="")return null;let t=Number(e);return Number.isFinite(t)?t:null}async function io(e,t){return R("cloud-project-ai",t===void 0?{ref:e}:{ref:e,enable:t})}function ob(e){if(e instanceof Re){let t=e.body?.error;if(t&&typeof t=="object"&&typeof t.message=="string")return new Re(e.status,t.message,typeof t.code=="string"?t.code:null,e.body)}return e}function sb(e){return async(t,n,r)=>{try{return await R("cloud-project-ai",{ref:e,admin:{method:t,path:n,...r===void 0?{}:{body:r}}})}catch(o){throw ob(o)}}}function Pe(e){return new so(sb(e))}function ab(e){return e instanceof Re&&(e.status===502||e.status===404&&e.serverCode!=="ai-off")}function ya(e){return Number.isInteger(e)?`$${e}`:Math.abs(e*100-Math.round(e*100))<1e-9?`$${e.toFixed(2)}`:`$${e}`}function Fd(e){return e?`${ya(e.input)} in, ${ya(e.output)} out`:"no price"}function Bd(e){return e.every_user?"every user":e.user_id?`user ${e.user_id}`:e.key_id?`key ${e.key_id}`:e.group_id?`group ${e.group_id}`:"the project"}function wa(e){let t=e.period==="rolling"?`per ${e.days??"?"} days`:e.period==="since"?`since ${e.starts_on??"?"}`:`a ${e.period}`,n=Wn(e.amount);return`${n===null?`$${e.amount}`:ya(n)} ${t}, ${e.hard?"hard":"soft"}`}function ib(e){let t=Wn(e);return t===null?"-":`$${t.toFixed(t!==0&&t<.01?4:2)}`}function ao(e){return e===null?"never":new Date(e*1e3).toISOString().slice(0,16).replace("T"," ")}function Hd(e,t=Date.now()){return new Date(t-(e-1)*864e5).toISOString().slice(0,10)}function lb(e,t){return`${e} ${t}${e===1?"":"s"}`}async function Kd(e){let t=await io(e);if(!t.ai.enabled){b({ref:e,enabled:!1,endpoint:t.ai.endpoint},()=>{d(`AI is off for ${e}.`),d(v(` snoutdata ai enable --ref ${e} ${t.ai.restartsDatabase}`))});return}let n=Pe(e),[r,o,s]=await Promise.allSettled([n.models(),n.budgets(),n.usage({by:"day",from:Hd(30)})]),a=[r,o,s].find(y=>y.status==="rejected");if(a&&ab(a.reason)){b({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!1},()=>{d(`AI is on for ${e}, and its gateway is still starting. Try again in a minute.`)});return}if(a)throw a.reason;let c=r.value,i=o.value,u=s.value.data,l=u.reduce((y,T)=>y+(Wn(T.cost)??0),0),m=u.reduce((y,T)=>y+T.requests,0);b({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!0,models:c,budgets:i,last30Days:{requests:m,cost:l}},()=>{d(`${M(`AI for ${e}`)}: on, at ${t.ai.endpoint}`),d(""),c.length===0?(d("No models yet. Add one:"),d(v(" snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(qd(c)),d(""),d(i.length===0?"No budgets.":i.map(y=>`Budget: ${Bd(y)}, ${wa(y)}`).join(`
577
+ `)),d(`Last 30 days: ${lb(m,"request")}, $${l.toFixed(2)}.`)})}async function Gd(e,t){let n=await io(e,t);b(n,()=>{t?(d(`AI is on for ${e}. ${n.ai.restartsDatabase} The gateway answers at ${n.ai.endpoint} within a minute or two.`),d(v(" next: snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(`AI is off for ${e}. Its providers, models, keys and log stay in the project's database, for when it is switched on again.`)})}function qd(e){return X([["MODEL","PROVIDER","UPSTREAM","PRICE PER M TOKENS","SERVED"],...e.map(t=>[t.name,t.provider,t.upstream,Fd(t.price)+(t.price_from==="catalogue"?" (catalogue)":""),t.served?"yes":`no: ${t.problem??(t.enabled?"not served":"disabled")}`])])}async function zd(e){let t=await Pe(e).models();b(t,()=>{d(t.length===0?`No models on ${e}'s gateway yet.`:qd(t))})}async function Wd(e){let t=await Pe(e).providers();b(t,()=>{if(t.length===0){d(`No providers on ${e}'s gateway yet. \`snoutdata ai models add\` makes one with its first model.`);return}d(X([["PROVIDER","KIND","WHERE","KEY","MODELS"],...t.map(n=>[n.name,n.kind,n.kind==="bedrock"?n.region??"":n.base_url,cb(n),n.models.join(", ")||"-"])]));for(let n of t.filter(r=>r.problem))ce(`${n.name}: ${n.problem}`)})}function cb(e){return e.key==="environment"?"from the environment":e.key==="none"||e.credential===null?"none":e.credential_stored?"stored":"missing"}async function Jd(e,t){if(!t)throw new h("ai providers remove needs the provider's name (see `snoutdata ai providers`)");await Pe(e).deleteProvider(t),b({ref:e,removed:t},()=>d(`Removed the provider ${t}.`))}function db(e){let t=ro.map(r=>r.id).join("|");if(!e)throw new h(`ai models add needs --source ${t}: where the model runs`);let n=ro.find(r=>r.id===e);if(!n)throw new h(`--source is one of ${t}, not "${e}"`);return n}function ub(e){d(M(`Adding a model from ${e.name}`));let t=[["The key",e.connect],["The model's id",e.model],["Its price",e.price]];for(let[n,r]of t){d(""),d(M(n));for(let s of r.steps)d(` - ${s}`);let o=r.console??r.link;o&&d(v(` ${o.label}: ${o.href}`))}d("")}function pb(e,t,n){if(n)return{name:n,existing:t.find(o=>o.name===n)??null};let r=t.filter(o=>Cd(e,o));if(r.length===1)return{name:r[0].name,existing:r[0]};if(r.length>1)throw new h(`${e.name} is connected more than once (${r.map(o=>o.name).join(", ")}): say which with --provider`);return{name:e.suggestedName,existing:t.find(o=>o.name===e.suggestedName)??null}}async function Vd(e,t,n){let r=db(n.source),o=n.upstream?.trim();if(!o)throw ub(r),new h(`Give the model's id as ${r.name} names it, with --model (for example ${r.model.example})`);let s=t?.trim()||Md(o),a=Pe(e),[c,i]=await Promise.all([a.providers(),a.models()]);if(i.some(G=>G.name===s))throw new h(`${e} already has a model called ${s}. Name this one differently, or remove that one first.`);let{name:u,existing:l}=pb(r,c,n.provider);if(l&&l.kind!==r.kind)throw new h(`The provider ${u} speaks ${l.kind}, not ${r.name}: choose another --provider name`);if(l?.origin==="file"&&n.stdin)throw new h(`The provider ${u} comes from the gateway's providers file, so its key is set there, not here`);let m=n.stdin?await sn():void 0,y=r.auth==="bedrock"&&n.accessKeyId!==void 0,T=null;if(!l){if(r.auth!=="optional"&&!m)throw new h(y?"Pipe the AWS secret access key in with --stdin; --access-key-id is its id":`${r.name} needs a key: pipe it in with --stdin (snoutdata ai models add ... --stdin < key.txt), so it is in neither the shell history nor ps`);let G=Dd(r,u,{baseUrl:n.baseUrl??r.baseUrl,region:n.region,apiKey:y?void 0:m,aws:y,accessKeyId:n.accessKeyId,secretAccessKey:y?m:void 0,authHeader:n.authHeader});if("problem"in G)throw new h(r.kind==="bedrock"&&!n.region?"Give the region the model runs in, with --region":n.baseUrl===void 0&&r.kind!=="bedrock"?"Give the provider's address, with --base-url":G.problem);T=G}let A=Ld({provider:u,upstream:o,input:n.priceIn,output:n.priceOut,caching:r.caching,enabled:!0,access:"everyone"});if("problem"in A)throw new h(A.problem.replace("input and the output rate","--price-in and --price-out"));let oe=!1,Z=!1,le;try{if(T&&(await a.putProvider(u,T.provider),oe=!0),le=await a.putModel(s,A.model),Z=!0,T?.credential)await a.putCredential(u,T.credential);else if(l&&m){if(!l.credential)throw new h(`The provider ${u} takes no key`);await a.putCredential(l.credential,y?{access_key_id:n.accessKeyId,secret_access_key:m}:{api_key:m})}}catch(G){throw Z&&await a.deleteModel(s).catch(()=>{}),oe&&await a.deleteProvider(u).catch(()=>{}),G}le=(await a.models()).find(G=>G.name===s)??le,b(le,()=>{d(`Added ${M(s)} (${o} on ${u}${oe?", a new provider":""}), at ${Fd(le.price)}${le.price_from==="catalogue"?" from the shipped catalogue":""}.`),le.price||ce("Nothing prices this model, so signed-in users are refused it. Give it a price with --price-in and --price-out."),!le.served&&le.problem&&ce(`It is not served yet: ${le.problem}`),d(v(` snoutdata ai test --model ${s}`))})}async function Yd(e,t){if(!t)throw new h("ai models remove needs the model's name (see `snoutdata ai models`)");let n=await Pe(e).deleteModel(t);b(n,()=>d(n.disabled?`${t} comes from the gateway's providers file, so it was disabled rather than removed.`:`Removed ${t}.`))}function mb(e){return(e??"").split(",").map(t=>t.trim()).filter(t=>t!=="")}function fb(e){return X([["ROUTE","TRIES, IN ORDER","SORT","SKIPS"],...e.map(t=>[t.name,t.tried.join(", ")||"-",t.sort,t.skipped.join(", ")||"-"])])}async function Xd(e){let t=await Pe(e).routes();b(t,()=>{if(t.length===0){d(`No routes on ${e}'s gateway. A route is one name for several models, each tried when the one before fails:`),d(v(" snoutdata ai routes set NAME --models first,second [--sort price]"));return}d(fb(t))})}async function Zd(e,t,n){if(!t)throw new h("ai routes set needs the route's name: snoutdata ai routes set NAME --models first,second");let r=mb(n.models);if(r.length===0)throw new h("ai routes set needs --models, the models to try in order, comma-separated (see `snoutdata ai models`)");let o=n.sort??"order";if(o!=="order"&&o!=="price"&&o!=="latency"&&o!=="throughput")throw new h(`--sort is order (as written), price (the cheapest first), latency or throughput (the fastest first, as measured), not ${o}`);let s=Pe(e);await s.putRoute(t,{targets:r,sort:o});let a=(await s.routes()).find(c=>c.name===t);b(a??{name:t,targets:r,sort:o},()=>{let c=a?.tried??r;d(`${M(t)} tries ${c.join(", then ")}${o==="price"?", the cheapest first":o==="latency"||o==="throughput"?`, the fastest first by ${o} once measured`:""}.`),a&&a.skipped.length>0&&ce(`skipped, not served: ${a.skipped.join(", ")}`),d(v(` Call it as the model "${t}", or: snoutdata ai test --model ${t}`))})}async function Qd(e,t){if(!t)throw new h("ai routes remove needs the route's name (see `snoutdata ai routes`)");await Pe(e).deleteRoute(t),b({name:t,removed:!0},()=>d(`Removed the route ${t}. Its models are untouched.`))}function gb(e,t=Date.now()/1e3){return e.revoked_at!==null?"revoked":e.expires_at!==null&&e.expires_at<=t?"expired":e.expires_at!==null?`until ${ao(e.expires_at).slice(0,10)}`:"active"}async function eu(e){let t=await Pe(e).keys();b(t,()=>{if(t.length===0){d(`No keys on ${e}'s gateway. \`snoutdata ai keys create --name my-server\` makes one for a server.`);return}d(X([["NAME","ID","KEY","MODELS","CREATED","LAST USED","STATE"],...t.map(n=>[n.name,n.id,n.hint,n.models?.join(", ")??"all",ao(n.created_at),n.last_used_at!==null?ao(n.last_used_at):n.last_used_on??"never",gb(n)])]))})}async function tu(e,t){if(!t.name)throw new h("ai keys create needs --name: what the key is for, shown in ai keys");if(t.expiresDays!==void 0&&t.expiresDays<=0)throw new h("--expires is a number of days, 1 or more");let n=t.models?.split(",").map(s=>s.trim()).filter(Boolean),r=await Pe(e).createKey({name:t.name,...n&&n.length>0?{models:n}:{},...t.expiresDays!==void 0?{expires_in:t.expiresDays*86400}:{}}),o=await io(e);b({...r,endpoint:o.ai.endpoint},()=>{process.stdout.write(`${r.key}
578
+ `),d(""),d(`${M(r.name)}: ${r.models?`for ${r.models.join(", ")}`:"for every model"}, ${r.expires_at?`until ${ao(r.expires_at)}`:"until revoked"}.`),d("This is the only time it is shown: only its hash is kept. It is a server's key; a browser calls with the app key and a signed-in user instead."),d(v(` OPENAI_BASE_URL=${o.ai.endpoint} OPENAI_API_KEY=<that key>`))})}async function nu(e,t){if(!t)throw new h("ai keys revoke needs the key's id (see `snoutdata ai keys`)");await Pe(e).revokeKey(t),b({ref:e,revoked:t},()=>d(`Revoked ${t}. A call with it is refused from now on.`))}async function ru(e){let t=await Pe(e).budgets();b(t,()=>{if(t.length===0){d(`No budgets on ${e}'s gateway. A browser call needs two hard ones:`),d(v(" snoutdata ai budgets set project --amount 5")),d(v(" snoutdata ai budgets set users --amount 0.50"));return}d(X([["ID","FOR","BUDGET"],...t.map(n=>[n.id,Bd(n),wa(n)])]))})}function hb(e){if(e==="project"||e==="users")return e;throw new h("ai budgets set takes project (the whole project) or users (each signed-in user)")}function yb(e,t,n){return e.find(r=>r.period===n&&!r.key_id&&!r.user_id&&!r.group_id&&(t==="users"?r.every_user:!r.every_user))}async function ou(e,t,n){let r=hb(t),o=Number(n.amount);if(n.amount===void 0||!Number.isFinite(o)||o<0)throw new h("ai budgets set needs --amount, in dollars, zero or more");let s=n.period??"day";if(s!=="day"&&s!=="month")throw new h("--period is day or month");let a=Pe(e),c=yb(await a.budgets(),r,s),i=c?await a.editBudget(c.id,{amount:o,hard:!n.soft}):await a.createBudget({name:r==="users"?"every user":"project",amount:o,period:s,hard:!n.soft,...r==="users"?{every_user:!0}:{}});b(i,()=>d(`${c?"Changed":"Set"}: ${r==="users"?"every user":"the project"}, ${wa(i)}.`))}async function su(e,t){if(!t)throw new h("ai budgets remove needs the budget's id (see `snoutdata ai budgets`)");await Pe(e).deleteBudget(t),b({ref:e,removed:t},()=>d(`Removed the budget ${t}.`))}var Ud=["day","model","provider","operation","key","user","group"];async function au(e,t){let n=t.by??"model";if(!Ud.includes(n))throw new h(`--by is one of ${Ud.join("|")}`);let r=t.days??30;if(r<1||r>366)throw new h("--days is 1 to 366");let o=await Pe(e).usage({by:n,from:Hd(r)});b(o,()=>{if(o.data.length===0){d(`No calls to ${e}'s gateway from ${o.from} to ${o.to}.`);return}let s=o.data.reduce((a,c)=>a+(Wn(c.cost)??0),0);d(X([[n.toUpperCase(),"REQUESTS","FAILED","TOKENS IN","TOKENS OUT","COST"],...o.data.map(a=>[String(a[n]??"-"),String(a.requests),String(a.failed),String(a.prompt_tokens),String(a.completion_tokens),ib(a.cost)+(a.unpriced>0?` (+${a.unpriced} unpriced)`:"")])])),d(`${o.from} to ${o.to}: $${s.toFixed(2)}.`)})}async function iu(e,t){let n=await io(e);if(!n.ai.enabled)throw new C("not-found",`AI is off for ${e}. Switch it on first: snoutdata ai enable.`);let r=Pe(e),s=(await r.models()).filter(T=>T.served&&T.embeddings===null),a=t.model??s[0]?.name;if(!a)throw new C("not-found",`${e}'s gateway serves no chat model yet. Add one: snoutdata ai models add.`);let c=await r.createKey({name:"snoutdata ai test",models:[a],expires_in:600}),i=Date.now(),u=0,l={};try{let T=await fetch(`${n.ai.endpoint}/chat/completions`,{method:"POST",headers:{authorization:`Bearer ${c.key}`,"content-type":"application/json"},body:JSON.stringify({model:a,max_tokens:64,messages:[{role:"user",content:t.prompt??"Say hello in five words."}]})});u=T.status;let A=await T.text();try{l=A?JSON.parse(A):{}}catch{l={error:{message:jr(T.status,T.statusText,A)}}}}finally{await r.revokeKey(c.id).catch(()=>{})}let m=Date.now()-i;if(u<200||u>=300){let T=l.error?.message??`the gateway answered ${u}`;throw new C(pt(u,l.error?.code??null),`${a} through ${e}'s gateway: ${T}`,{model:a,status:u,gatewayCode:l.error?.code??null})}let y=l.choices?.[0]?.message?.content??"";return b({ref:e,model:a,reply:y,usage:l.usage??null,ms:m},()=>{process.stdout.write(`${y}
579
+ `),d(v(`${a}, ${m} ms, ${l.usage?.prompt_tokens??"?"} tokens in, ${l.usage?.completion_tokens??"?"} out, through ${n.ai.endpoint}`))}),0}pe();Ae();de();import{readFile as bb}from"node:fs/promises";var Sb="api.snoutdata.com";function du(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}.${Sb}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function kb(e){return du(e,await R("cloud-project-auth",{ref:e,action:"status"}))}async function lo(e,t){return du(e,await R("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function ba(e){let t=e.google,n="off";t.ready?n=M("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(`${X([["auth",e.enabled?M("on"):"off"],["site url",e.siteUrl??v("not set")],["redirects",e.redirects.length?e.redirects.join(", "):v("none")],["google",n],["google client id",t.clientId??v("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?M("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
580
+ `),e.enabled||d(v(` snoutdata products enable auth --ref ${e.ref}`))}async function _b(){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 uu(e,t,n,r){if(t===void 0||t==="show"){let o=await kb(e);b(o,()=>ba(o));return}if(t==="google"){if(n[0]==="off"){let a=await lo(e,{googleEnabled:!1});b(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new h('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await _b();if(!a)throw new h("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await lo(e,o);b(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):ba(s),d(v(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new h('auth anonymous takes "on" or "off"');let s=await lo(e,{anonymousEnabled:o==="on"});b(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(v(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new h("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await lo(e,o);b(s,()=>ba(s));return}if(t==="templates"){let o=await R("cloud-project-auth",{ref:e,action:"templates"});b(o,()=>cu(o.templates));return}if(t==="template"){let o=n[0];if(!o||!lu.includes(o))throw new h(`auth template needs a kind: ${lu.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new h(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let c=await bb(r.file,"utf8"),i=await R("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(l=>l.kind===o)?.subject;s={kind:o,subject:u,body:c}}let a=await R("cloud-project-auth",{ref:e,action:"template",template:s});b(a,()=>{cu(a.templates),d(v(" Your auth service restarts with it within about a minute."))});return}throw new h(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var lu=["confirmation","recovery","magic_link","invite","email_change"];function cu(e){process.stdout.write(`${X([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?M("yours"):"ours",t.subject])])}
581
+ `)}pe();de();be();import{createHash as Hb}from"node:crypto";import{existsSync as Kb,readdirSync as Gb,readFileSync as qb,statSync as zb}from"node:fs";import{isAbsolute as Wb,join as $a,relative as Jb,resolve as Vb}from"node:path";function vb(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function co(e,t,n={}){let r=vb(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let l=o.get(u.name);if(!l){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}l.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,l)=>u<l?-1:u>l?1:0)[t.length-1],c=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!c.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 cn(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Sa,createHmac as uo,pbkdf2Sync as $b,randomBytes as Pb,timingSafeEqual as xb}from"node:crypto";import{readFileSync as Eb}from"node:fs";import{homedir as Ab}from"node:os";import{connect as Rb,isIP as Tb}from"node:net";import{connect as Ob}from"node:tls";var Tt=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)}},ye=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},Nb=196608,Ib=80877103;function pu(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function Rt(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function Cb(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 mu(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 ka=class{constructor(t,n=Pb(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(A=>[A.slice(0,1),A.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 ye("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new ye("the server answered the password exchange with no salt or iteration count");let a=$b(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),c=uo("sha256",a).update("Client Key").digest(),i=Sa("sha256").update(c).digest(),u=`c=biws,r=${r}`,l=`${this.firstBare},${t},${u}`,m=uo("sha256",i).update(l).digest(),y=Buffer.alloc(c.length);for(let A=0;A<y.length;A++)y[A]=c[A]^m[A];let T=uo("sha256",a).update("Server Key").digest();return this.serverSignature=uo("sha256",T).update(l).digest(),`${u},p=${y.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new ye(`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||!xb(o,this.serverSignature))throw new ye("the server could not prove it knows the password, so this is not the database it claims to be")}},_a=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 ye(n.message))),t.on("close",()=>this.fail(new ye("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 ye("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 Mb(e){return new Promise((t,n)=>{let r=c=>{if(a(),c.length!==1){n(new ye("the server sent more than its answer to the TLS request, so the connection was closed"));return}t(String.fromCharCode(c[0]))},o=c=>{a(),n(new ye(c.message))},s=()=>{a(),n(new ye("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 Db(e,t){return new Promise((n,r)=>{let o=Rb({host:e,port:t}),s=a=>r(new ye(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function Lb(e,t,n,r){let o=await Db(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(Ib,4),o.write(s),await Mb(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new ye(`${e.host} does not accept TLS, and this connection requires it (sslmode ${t})`)}let c=t==="verify-ca"||t==="verify-full";return new Promise((i,u)=>{let l=Ob({socket:o,host:e.host,...Tb(e.host)===0?{servername:e.host}:{},rejectUnauthorized:c,...c&&n?{ca:Eb(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(l),l.once("secureConnect",()=>{l.off("error",m),i(l)});let m=y=>u(new ye(`TLS to ${e.host} failed: ${y.message}`));l.once("error",m)})}var po=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=Gn(t.ssl,r,n.home??Ab()),s=o.rootCert??(r.PGSSLROOTCERT||void 0),a=o.mode==="require"&&s?"verify-ca":o.mode,c=n.connectTimeoutMs??12e4,i=[],u=()=>{for(let y of i)y.destroy()},l,m=new Promise((y,T)=>{l=setTimeout(()=>{u(),T(new ye(`${t.host}:${t.port} did not answer within ${Math.round(c/1e3)} seconds`))},c)});try{return await Promise.race([(async()=>{let y=await Lb(t,a,s,A=>i.push(A)),T=new e(y,new _a(y));return await T.startup(t),T})(),m])}catch(y){throw u(),y}finally{clearTimeout(l)}}send(t,n){this.socket.write(pu(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[Rt(s),Rt(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(Nb,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",Rt(t.password));else if(a===5){let c=Sa("md5").update(t.password+t.user).digest("hex"),i=Sa("md5").update(Buffer.concat([Buffer.from(c),s.body.subarray(4,8)])).digest("hex");this.send("p",Rt(`md5${i}`))}else if(a===10){let c=Cb(s.body,4);if(!c.includes("SCRAM-SHA-256"))throw new ye(`the server asked for ${c.join(", ")||"a password exchange"}, which this client does not speak`);o=new ka(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([Rt("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 ye(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new Tt(mu(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",Rt(t));let r=[],o=null,s=null;for(;;){let a=await this.reader.next();switch(a.type){case"T":r=[];break;case"D":{let c=a.body.readInt16BE(0),i=[],u=2;for(let l=0;l<c;l++){let m=a.body.readInt32BE(u);u+=4,m<0?i.push(null):(i.push(a.body.toString("utf8",u,u+m)),u+=m)}r.push(i);break}case"C":o=a.body.toString("utf8",0,Math.max(0,a.body.indexOf(0)));break;case"E":s??=new Tt(mu(a.body));break;case"G":n===void 0?this.send("f",Rt("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",Rt("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(pu("X",Buffer.alloc(0)))}};function fu(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===`
540
582
  `).length,a=t.split(`
541
583
  `)[s]??"",c=o.length-(o.lastIndexOf(`
542
584
  `)+1),i=`LINE ${n+s}: `;r.push(`${i}${a.replace(/\r$/,"")}`,`${" ".repeat(i.length+c)}^`)}return e.detail&&r.push(`DETAIL: ${e.detail}`),e.hint&&r.push(`HINT: ${e.hint}`),e.where&&r.push(`CONTEXT: ${e.where}`),r.join(`
543
- `)}var Ow=/[A-Za-z0-9_$\u0080-￿]/,Nw=/[A-Za-z_\u0080-￿]/,Iw=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Cw(e){let t=qs(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function Rd(e){let t=qs(e).trim();if(/^rollback\s+(?:(?:work|transaction)\s+)?to\b/i.test(t))return null;let n=/^(commit|end|rollback|abort|prepare\s+transaction)\b/i.exec(t);return n?n[1].toUpperCase().replace(/\s+/," "):null}function qs(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function Td(e){let t=[],n=[],r=e.length,o=0,s=1,a=-1,c=1,i=0,u=0,l=[],m=0,h=()=>{a<0&&(a=o,c=s)},x=()=>{a=-1,i=0,u=0,l=[],m=0},R=L=>{let _e=Math.min(r,o+L);for(let Z=o;Z<_e;Z++)e.charCodeAt(Z)===10&&s++;o=_e},ee=L=>R(L<0?r-o:L-o),z=null,ce=()=>{let L=a>=0?e.slice(a,o).trim():"";if(qs(L).replace(/;/g,"").trim()!=="")if(!Cw(L))t.push({sql:L,line:c});else{let _e=e.indexOf(`
544
- `,o),Z=[];if(_e>=0){let ne=_e+1,Un=s+1;for(;ne<r;){let xt=e.indexOf(`
545
- `,ne),Fn=e.slice(ne,xt<0?r:xt).replace(/\r$/,"");if(ne=xt<0?r:xt+1,Un++,Fn==="\\.")break;Z.push(`${Fn}
546
- `)}z={from:_e+1,to:ne,line:Un}}t.push({sql:L,line:c,copyData:Z.join("")})}x()};for(;o<r;){if(z&&o>=z.from){o=z.to,s=z.line,z=null;continue}let L=e[o],_e=e[o+1];if(L===`
547
- `||L===" "||L===" "||L==="\r"||L==="\f"||L==="\v"){R(1);continue}if(L==="-"&&_e==="-"){ee(e.indexOf(`
548
- `,o));continue}if(L==="/"&&_e==="*"){h();let Z=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")Z++,R(2);else if(e[o]==="*"&&e[o+1]==="/"){if(Z--,R(2),Z===0)break}else R(1);continue}if(L==="'"){h(),C(!1);continue}if(L==='"'){for(h(),R(1);;){let Z=e.indexOf('"',o);if(Z<0){ee(-1);break}if(ee(Z+1),e[o]==='"'){R(1);continue}break}continue}if(L==="$"){h();let Z=Iw.exec(e.slice(o,o+64))?.[0];if(Z){R(Z.length);let ne=e.indexOf(Z,o);ee(ne<0?-1:ne+Z.length)}else R(1);continue}if(L==="\\"){let Z=e.indexOf(`
549
- `,o);n.push({line:s,text:e.slice(o,Z<0?r:Z).replace(/\r$/,"").trimEnd()}),ee(Z);continue}if(L==="("){h(),i++,R(1);continue}if(L===")"){h(),i>0&&i--,R(1);continue}if(L===";"){h(),R(1),i===0&&u===0&&ce();continue}if(Nw.test(L)){h();let Z=o+1;for(;Z<r&&Ow.test(e[Z]);)Z++;let ne=e.slice(o,Z);if(R(Z-o),e[o]==="'"){C(ne==="e"||ne==="E");continue}de(ne.toLowerCase());continue}if(L>="0"&&L<="9"){h();let Z=o+1;for(;Z<r&&/[0-9A-Za-z_.]/.test(e[Z]);)Z++;R(Z-o);continue}h(),R(1)}return ce(),{statements:t,metaCommands:n};function C(L){for(R(1);o<r;){let _e=e[o];if(L&&_e==="\\"){R(2);continue}if(_e==="'"){if(R(1),e[o]==="'"){R(1);continue}return}R(1)}}function de(L){m<4&&["create","function","procedure","or","replace"].includes(L)&&(l[m]=L[0]),m++,!(!(l[0]==="c"&&(l[1]==="f"||l[1]==="p"||l[1]==="o"&&l[2]==="r"&&(l[3]==="f"||l[3]==="p")))||i!==0)&&(L==="begin"?u++:L==="case"?u>=1&&u++:L==="end"&&u>0&&u--)}}function Nd(e){let t=Bw(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Hr="_snoutdata_migrations",Kw=`create table if not exists ${Hr} (
585
+ `)}var jb=/[A-Za-z0-9_$\u0080-￿]/,Ub=/[A-Za-z_\u0080-￿]/,Fb=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Bb(e){let t=va(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function gu(e){let t=va(e).trim();if(/^rollback\s+(?:(?:work|transaction)\s+)?to\b/i.test(t))return null;let n=/^(commit|end|rollback|abort|prepare\s+transaction)\b/i.exec(t);return n?n[1].toUpperCase().replace(/\s+/," "):null}function va(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function hu(e){let t=[],n=[],r=e.length,o=0,s=1,a=-1,c=1,i=0,u=0,l=[],m=0,y=()=>{a<0&&(a=o,c=s)},T=()=>{a=-1,i=0,u=0,l=[],m=0},A=V=>{let ve=Math.min(r,o+V);for(let ne=o;ne<ve;ne++)e.charCodeAt(ne)===10&&s++;o=ve},oe=V=>A(V<0?r-o:V-o),Z=null,le=()=>{let V=a>=0?e.slice(a,o).trim():"";if(va(V).replace(/;/g,"").trim()!=="")if(!Bb(V))t.push({sql:V,line:c});else{let ve=e.indexOf(`
586
+ `,o),ne=[];if(ve>=0){let fe=ve+1,Pn=s+1;for(;fe<r;){let Wt=e.indexOf(`
587
+ `,fe),ir=e.slice(fe,Wt<0?r:Wt).replace(/\r$/,"");if(fe=Wt<0?r:Wt+1,Pn++,ir==="\\.")break;ne.push(`${ir}
588
+ `)}Z={from:ve+1,to:fe,line:Pn}}t.push({sql:V,line:c,copyData:ne.join("")})}T()};for(;o<r;){if(Z&&o>=Z.from){o=Z.to,s=Z.line,Z=null;continue}let V=e[o],ve=e[o+1];if(V===`
589
+ `||V===" "||V===" "||V==="\r"||V==="\f"||V==="\v"){A(1);continue}if(V==="-"&&ve==="-"){oe(e.indexOf(`
590
+ `,o));continue}if(V==="/"&&ve==="*"){y();let ne=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")ne++,A(2);else if(e[o]==="*"&&e[o+1]==="/"){if(ne--,A(2),ne===0)break}else A(1);continue}if(V==="'"){y(),G(!1);continue}if(V==='"'){for(y(),A(1);;){let ne=e.indexOf('"',o);if(ne<0){oe(-1);break}if(oe(ne+1),e[o]==='"'){A(1);continue}break}continue}if(V==="$"){y();let ne=Fb.exec(e.slice(o,o+64))?.[0];if(ne){A(ne.length);let fe=e.indexOf(ne,o);oe(fe<0?-1:fe+ne.length)}else A(1);continue}if(V==="\\"){let ne=e.indexOf(`
591
+ `,o);n.push({line:s,text:e.slice(o,ne<0?r:ne).replace(/\r$/,"").trimEnd()}),oe(ne);continue}if(V==="("){y(),i++,A(1);continue}if(V===")"){y(),i>0&&i--,A(1);continue}if(V===";"){y(),A(1),i===0&&u===0&&le();continue}if(Ub.test(V)){y();let ne=o+1;for(;ne<r&&jb.test(e[ne]);)ne++;let fe=e.slice(o,ne);if(A(ne-o),e[o]==="'"){G(fe==="e"||fe==="E");continue}ue(fe.toLowerCase());continue}if(V>="0"&&V<="9"){y();let ne=o+1;for(;ne<r&&/[0-9A-Za-z_.]/.test(e[ne]);)ne++;A(ne-o);continue}y(),A(1)}return le(),{statements:t,metaCommands:n};function G(V){for(A(1);o<r;){let ve=e[o];if(V&&ve==="\\"){A(2);continue}if(ve==="'"){if(A(1),e[o]==="'"){A(1);continue}return}A(1)}}function ue(V){m<4&&["create","function","procedure","or","replace"].includes(V)&&(l[m]=V[0]),m++,!(!(l[0]==="c"&&(l[1]==="f"||l[1]==="p"||l[1]==="o"&&l[2]==="r"&&(l[3]==="f"||l[3]==="p")))||i!==0)&&(V==="begin"?u++:V==="case"?u>=1&&u++:V==="end"&&u>0&&u--)}}function wu(e){let t=Jb(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var mo="_snoutdata_migrations",Yb=`create table if not exists ${mo} (
550
592
  name text primary key,
551
593
  checksum text not null,
552
594
  applied_at timestamptz not null default now()
553
- );`;function Gw(e){if(!Mw(e)||!Uw(e).isDirectory())throw new Error(`no migrations folder at ${Nd(e)}. Put your .sql files there, or pass --dir.`);return jw(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Ws(e,t),r=Lw(n,"utf8");return{name:t,path:n,sql:r,checksum:Dw("sha256").update(r,"utf8").digest("hex")}})}var qw={connection:e=>Ce(e),open:e=>Br.open(e)};function Id(e,t){throw e instanceof he?new T("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof bt?new T("failed",`${t}'s database refused the connection: ${e.message}`):e}async function Cd(e,t,n){try{return await e.open(t)}catch(r){return Id(r,n)}}async function Ww(e,t,n){let r=await Cd(e,t,n);try{await r.query(Kw);let{rows:o}=await r.query(`select name, checksum from ${Hr};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof bt)throw new T("failed",`Could not read ${Hr} on ${n}: ${o.message}`);return Id(o,n)}finally{r.close()}}async function zw(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof bt)return`${r.line>0?`${n}:${r.line}: `:""}${Ad(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof he)return`the connection was lost: ${o.message}`;throw o}return null}function Od(e){return`'${e.replace(/'/g,"''")}'`}async function Dd(e,t={}){let n=await zs(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
554
- `);n.problems.every(o=>o.code==="psql-command"||o.code==="ends-transaction")&&V("failed",`Refused, and nothing ran:
555
- ${r}`,{ref:e,problems:n.problems}),V("conflict",`Refused. This folder and ${e} do not agree:
556
- ${r}`,{ref:e,problems:n.problems})}V("failed",`${n.failed} failed, and nothing after it was tried.
557
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return w(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 zs(e,t={},n=()=>{},r=qw){let o=Hw(t.dir===void 0?Ws(process.cwd(),"migrations"):Fw(t.dir)?t.dir:Ws(process.cwd(),t.dir)),s=[];function a(C){s.push(C),n(C)}let c=Gw(o);if(c.length===0)return a(`No .sql files in ${Nd(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 l=await Ww(r,u,e),m=Ur(c,l,{outOfOrder:t.outOfOrder});if(!m.ok)return{ref:e,directory:o,ok:!1,applied:[],problems:[...m.problems],notes:s};let h=new Map(c.map(C=>[C.name,C])),x=m.pending.map(C=>h.get(C.name)),R=new Map(x.map(C=>[C.name,Td(C.sql)])),ee=x.flatMap(C=>R.get(C.name).metaCommands.map(de=>({code:"psql-command",name:C.name,reason:`${C.name} line ${de.line} is a psql command (${de.text.length>60?`${de.text.slice(0,57)}...`:de.text}), which only psql runs. db push sends SQL to the database itself, so put the SQL it stands for in the file instead.`}))),z=x.filter(C=>Jt(C.sql)).flatMap(C=>R.get(C.name).statements.flatMap(de=>{let L=Rd(de.sql);return L?[{code:"ends-transaction",name:C.name,reason:`${C.name} line ${de.line} ends the transaction itself (${L}). db push already runs each file in one transaction and records it there, so a ${L} would keep what ran before it if a later statement failed. Take it out, or put -- snoutdata:no-transaction at the top of the file to run it exactly as written.`}]:[]}));if(ee.length>0||z.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:[...ee,...z],notes:s};if(x.length===0)return a(`${e} is up to date: ${l.length} migration${l.length===1?"":"s"} already applied.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};if(t.dryRun){for(let C of x)Jt(C.sql)||a(`${C.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:x.map(C=>C.name),notes:s}}let ce=[];for(let C of x){let de=`insert into ${Hr} (name, checksum) values (${Od(C.name)}, ${Od(C.checksum)});`,L=Jt(C.sql);L||a(`${C.name} runs outside a transaction, so an interruption can leave it half applied.`);let _e=[...L?[{sql:"begin;",line:0}]:[],...R.get(C.name).statements,{sql:de,line:0},...L?[{sql:"commit;",line:0}]:[]],Z=await Cd(r,u,e),ne;try{ne=await zw(Z,_e,C.name)}finally{Z.close()}if(ne!==null)return L&&(a("It ran inside a transaction, so nothing from it is in the database."),/cannot run inside a transaction block/i.test(ne)&&a("Put -- snoutdata:no-transaction at the top of the file to run it outside one.")),{ref:e,directory:o,ok:!1,applied:ce,failed:C.name,error:ne,notes:s};ce.push(C.name),a(` applied ${C.name}`)}return{ref:e,directory:o,ok:!0,applied:ce,notes:s}}pe();xe();le();import{readFile as Md}from"node:fs/promises";function Js(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 Jw(e){let t=await P("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function jd(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 Md(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function Ld(e){if(!e)throw new g("push credentials set fcm needs --file service-account.json");return{service_account:await Md(e,"utf8")}}async function Vw(e,t,n){let r=await P("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Yw(e,t){return await P("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Ud(e){let t=await Jw(e);w(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${W([["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}`:""]])}
558
- `),(!n||!r)&&d(b(` snoutdata push credentials set ${n?"fcm --file service-account.json":"apns --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app"} --ref ${e}`))})}async function Fd(e,t,n){let r=await Vw(e,t,n);w(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 Bd(e,t){let n=await Yw(e,t);w(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}pe();we();le();import{spawn as db}from"node:child_process";import{writeFileSync as ub}from"node:fs";import{isAbsolute as pb,resolve as mb}from"node:path";var Zw=" ";function J(e){return Zw.repeat(e)}function Ee(e){return JSON.stringify(e)}function Ze(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:Ee(e)}function Tt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Qw(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 eb={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"},tb={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function nb(e,t){let n=tb[e]?.[t];return n||(eb[t]??"unknown")}function Kr(e,t){let{base:n,array:r}=Qw(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=nb(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function rb(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Vs(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${J(n)}${Ze(t.name)}?: never`;let s=Kr(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${J(n)}${Ze(t.name)}${r?"?":""}: ${a}`}function ob(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,c)=>a===n[c])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function sb(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(`${J(t)}Relationships: []`),n;n.push(`${J(t)}Relationships: [`);for(let o of r)n.push(`${J(t+1)}{`),n.push(`${J(t+2)}foreignKeyName: ${Ee(o.constraintName)}`),n.push(`${J(t+2)}columns: [${o.columns.map(Ee).join(", ")}]`),n.push(`${J(t+2)}isOneToOne: ${ob(e,o)?"true":"false"}`),n.push(`${J(t+2)}referencedRelation: ${Ee(o.referencedTable)}`),n.push(`${J(t+2)}referencedColumns: [${o.referencedColumns.map(Ee).join(", ")}]`),n.push(`${J(t+1)}},`);return n.push(`${J(t)}]`),n}function Hd(e,t,n){let r=Tt(t.columns),o=[];o.push(`${J(n)}${Ze(t.name)}: {`),o.push(`${J(n+1)}Row: {`);for(let s of r)o.push(Vs(e,s,n+2,!1));if(o.push(`${J(n+1)}}`),t.kind!=="view"){o.push(`${J(n+1)}Insert: {`);for(let s of r)o.push(Vs(e,s,n+2,rb(s),!0));o.push(`${J(n+1)}}`),o.push(`${J(n+1)}Update: {`);for(let s of r)o.push(Vs(e,s,n+2,!0,!0));o.push(`${J(n+1)}}`)}return o.push(...sb(t,n+1)),o.push(`${J(n)}}`),o}function An(e,t){return[`${J(t)}${e}: {`,`${J(t+1)}[_ in never]: never`,`${J(t)}}`]}function ab(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}[]`:`${Kr(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??Kr(e,n)}function ib(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(`${J(n)}${Ze(t.name)}: {`),o.length===0)r.push(`${J(n+1)}Args: never`);else{r.push(`${J(n+1)}Args: {`);for(let s of o)r.push(`${J(n+2)}${Ze(s.name)}: ${Kr(e,s.dataType)}`);r.push(`${J(n+1)}}`)}return r.push(`${J(n+1)}Returns: ${ab(e,t)}`),r.push(`${J(n)}}`),r}function Vt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Kd(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[${Ee(h)}][${Ee("Enums")}][${Ee(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=Vt(m,n),x=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${Ee(h)}][${Ee(x)}][${Ee(m.name)}][${Ee("Row")}]`)}let c={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(Vt(m,n));for(let m of e.routines??[])i.add(Vt(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),l=[];l.push("export type Json ="),l.push(" | string"),l.push(" | number"),l.push(" | boolean"),l.push(" | null"),l.push(" | { [key: string]: Json | undefined }"),l.push(" | Json[]"),l.push(""),l.push("export type Database = {");for(let m of u){let h=Tt(e.tables.filter(z=>Vt(z,n)===m&&z.kind!=="view")),x=Tt(e.tables.filter(z=>Vt(z,n)===m&&z.kind==="view")),R=Tt((e.routines??[]).filter(z=>Vt(z,n)===m)),ee=Tt(o.filter(z=>(z.schema&&z.schema.length>0?z.schema:n)===m));if(l.push(`${J(1)}${Ze(m)}: {`),h.length===0)l.push(...An("Tables",2));else{l.push(`${J(2)}Tables: {`);for(let z of h)l.push(...Hd(c,z,3));l.push(`${J(2)}}`)}if(x.length===0)l.push(...An("Views",2));else{l.push(`${J(2)}Views: {`);for(let z of x)l.push(...Hd(c,z,3));l.push(`${J(2)}}`)}if(R.length===0)l.push(...An("Functions",2));else{l.push(`${J(2)}Functions: {`);for(let z of R)l.push(...ib(c,z,3));l.push(`${J(2)}}`)}if(ee.length===0)l.push(...An("Enums",2));else{l.push(`${J(2)}Enums: {`);for(let z of ee)l.push(`${J(3)}${Ze(z.name)}: ${z.values.map(Ee).join(" | ")}`);l.push(`${J(2)}}`)}l.push(...An("CompositeTypes",2)),l.push(`${J(1)}}`)}return l.push("}"),t.helpers!==!1&&(l.push(""),l.push(cb(n))),t.constants!==!1&&(l.push(""),l.push(lb(u,o,n))),`${l.join(`
595
+ );`;function Xb(e){if(!Kb(e)||!zb(e).isDirectory())throw new Error(`no migrations folder at ${wu(e)}. Put your .sql files there, or pass --dir.`);return Gb(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=$a(e,t),r=qb(n,"utf8");return{name:t,path:n,sql:r,checksum:Hb("sha256").update(r,"utf8").digest("hex")}})}var Zb={connection:e=>He(e),open:e=>po.open(e)};function bu(e,t){throw e instanceof ye?new C("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Tt?new C("failed",`${t}'s database refused the connection: ${e.message}`):e}async function Su(e,t,n){try{return await e.open(t)}catch(r){return bu(r,n)}}async function Qb(e,t,n){let r=await Su(e,t,n);try{await r.query(Yb);let{rows:o}=await r.query(`select name, checksum from ${mo};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Tt)throw new C("failed",`Could not read ${mo} on ${n}: ${o.message}`);return bu(o,n)}finally{r.close()}}async function eS(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Tt)return`${r.line>0?`${n}:${r.line}: `:""}${fu(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof ye)return`the connection was lost: ${o.message}`;throw o}return null}function yu(e){return`'${e.replace(/'/g,"''")}'`}async function ku(e,t={}){let n=await Pa(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
596
+ `);n.problems.every(o=>o.code==="psql-command"||o.code==="ends-transaction")&&te("failed",`Refused, and nothing ran:
597
+ ${r}`,{ref:e,problems:n.problems}),te("conflict",`Refused. This folder and ${e} do not agree:
598
+ ${r}`,{ref:e,problems:n.problems})}te("failed",`${n.failed} failed, and nothing after it was tried.
599
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return b(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 Pa(e,t={},n=()=>{},r=Zb){let o=Vb(t.dir===void 0?$a(process.cwd(),"migrations"):Wb(t.dir)?t.dir:$a(process.cwd(),t.dir)),s=[];function a(G){s.push(G),n(G)}let c=Xb(o);if(c.length===0)return a(`No .sql files in ${wu(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 l=await Qb(r,u,e),m=co(c,l,{outOfOrder:t.outOfOrder});if(!m.ok)return{ref:e,directory:o,ok:!1,applied:[],problems:[...m.problems],notes:s};let y=new Map(c.map(G=>[G.name,G])),T=m.pending.map(G=>y.get(G.name)),A=new Map(T.map(G=>[G.name,hu(G.sql)])),oe=T.flatMap(G=>A.get(G.name).metaCommands.map(ue=>({code:"psql-command",name:G.name,reason:`${G.name} line ${ue.line} is a psql command (${ue.text.length>60?`${ue.text.slice(0,57)}...`:ue.text}), which only psql runs. db push sends SQL to the database itself, so put the SQL it stands for in the file instead.`}))),Z=T.filter(G=>cn(G.sql)).flatMap(G=>A.get(G.name).statements.flatMap(ue=>{let V=gu(ue.sql);return V?[{code:"ends-transaction",name:G.name,reason:`${G.name} line ${ue.line} ends the transaction itself (${V}). db push already runs each file in one transaction and records it there, so a ${V} would keep what ran before it if a later statement failed. Take it out, or put -- snoutdata:no-transaction at the top of the file to run it exactly as written.`}]:[]}));if(oe.length>0||Z.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:[...oe,...Z],notes:s};if(T.length===0)return a(`${e} is up to date: ${l.length} migration${l.length===1?"":"s"} already applied.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};if(t.dryRun){for(let G of T)cn(G.sql)||a(`${G.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:T.map(G=>G.name),notes:s}}let le=[];for(let G of T){let ue=`insert into ${mo} (name, checksum) values (${yu(G.name)}, ${yu(G.checksum)});`,V=cn(G.sql);V||a(`${G.name} runs outside a transaction, so an interruption can leave it half applied.`);let ve=[...V?[{sql:"begin;",line:0}]:[],...A.get(G.name).statements,{sql:ue,line:0},...V?[{sql:"commit;",line:0}]:[]],ne=await Su(r,u,e),fe;try{fe=await eS(ne,ve,G.name)}finally{ne.close()}if(fe!==null)return V&&(a("It ran inside a transaction, so nothing from it is in the database."),/cannot run inside a transaction block/i.test(fe)&&a("Put -- snoutdata:no-transaction at the top of the file to run it outside one.")),{ref:e,directory:o,ok:!1,applied:le,failed:G.name,error:fe,notes:s};le.push(G.name),a(` applied ${G.name}`)}return{ref:e,directory:o,ok:!0,applied:le,notes:s}}pe();Ae();de();import{readFile as _u}from"node:fs/promises";function xa(e){if(e==="apns"||e==="fcm")return e;throw new h("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function tS(e){let t=await R("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function vu(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new h("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new h("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await _u(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function $u(e){if(!e)throw new h("push credentials set fcm needs --file service-account.json");return{service_account:await _u(e,"utf8")}}async function nS(e,t,n){let r=await R("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function rS(e,t){return await R("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Pu(e){let t=await tS(e);b(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${X([["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}`:""]])}
600
+ `),(!n||!r)&&d(v(` 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 xu(e,t,n){let r=await nS(e,t,n);b(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 Eu(e,t){let n=await rS(e,t);b(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}pe();be();de();import{spawn as yS}from"node:child_process";import{writeFileSync as wS}from"node:fs";import{isAbsolute as bS,resolve as SS}from"node:path";var sS=" ";function Q(e){return sS.repeat(e)}function Oe(e){return JSON.stringify(e)}function it(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:Oe(e)}function Ft(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function aS(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 iS={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"},lS={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function cS(e,t){let n=lS[e]?.[t];return n||(iS[t]??"unknown")}function fo(e,t){let{base:n,array:r}=aS(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=cS(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function dS(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Ea(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${Q(n)}${it(t.name)}?: never`;let s=fo(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${Q(n)}${it(t.name)}${r?"?":""}: ${a}`}function uS(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,c)=>a===n[c])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function pS(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(`${Q(t)}Relationships: []`),n;n.push(`${Q(t)}Relationships: [`);for(let o of r)n.push(`${Q(t+1)}{`),n.push(`${Q(t+2)}foreignKeyName: ${Oe(o.constraintName)}`),n.push(`${Q(t+2)}columns: [${o.columns.map(Oe).join(", ")}]`),n.push(`${Q(t+2)}isOneToOne: ${uS(e,o)?"true":"false"}`),n.push(`${Q(t+2)}referencedRelation: ${Oe(o.referencedTable)}`),n.push(`${Q(t+2)}referencedColumns: [${o.referencedColumns.map(Oe).join(", ")}]`),n.push(`${Q(t+1)}},`);return n.push(`${Q(t)}]`),n}function Au(e,t,n){let r=Ft(t.columns),o=[];o.push(`${Q(n)}${it(t.name)}: {`),o.push(`${Q(n+1)}Row: {`);for(let s of r)o.push(Ea(e,s,n+2,!1));if(o.push(`${Q(n+1)}}`),t.kind!=="view"){o.push(`${Q(n+1)}Insert: {`);for(let s of r)o.push(Ea(e,s,n+2,dS(s),!0));o.push(`${Q(n+1)}}`),o.push(`${Q(n+1)}Update: {`);for(let s of r)o.push(Ea(e,s,n+2,!0,!0));o.push(`${Q(n+1)}}`)}return o.push(...pS(t,n+1)),o.push(`${Q(n)}}`),o}function Jn(e,t){return[`${Q(t)}${e}: {`,`${Q(t+1)}[_ in never]: never`,`${Q(t)}}`]}function mS(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}[]`:`${fo(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??fo(e,n)}function fS(e,t,n){let r=[],o=Ft((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${Q(n)}${it(t.name)}: {`),o.length===0)r.push(`${Q(n+1)}Args: never`);else{r.push(`${Q(n+1)}Args: {`);for(let s of o)r.push(`${Q(n+2)}${it(s.name)}: ${fo(e,s.dataType)}`);r.push(`${Q(n+1)}}`)}return r.push(`${Q(n+1)}Returns: ${mS(e,t)}`),r.push(`${Q(n)}}`),r}function dn(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Ru(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let y=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${Oe(y)}][${Oe("Enums")}][${Oe(m.name)}]`)}let a=new Map;for(let m of e.tables){let y=dn(m,n),T=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${Oe(y)}][${Oe(T)}][${Oe(m.name)}][${Oe("Row")}]`)}let c={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(dn(m,n));for(let m of e.routines??[])i.add(dn(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),l=[];l.push("export type Json ="),l.push(" | string"),l.push(" | number"),l.push(" | boolean"),l.push(" | null"),l.push(" | { [key: string]: Json | undefined }"),l.push(" | Json[]"),l.push(""),l.push("export type Database = {");for(let m of u){let y=Ft(e.tables.filter(Z=>dn(Z,n)===m&&Z.kind!=="view")),T=Ft(e.tables.filter(Z=>dn(Z,n)===m&&Z.kind==="view")),A=Ft((e.routines??[]).filter(Z=>dn(Z,n)===m)),oe=Ft(o.filter(Z=>(Z.schema&&Z.schema.length>0?Z.schema:n)===m));if(l.push(`${Q(1)}${it(m)}: {`),y.length===0)l.push(...Jn("Tables",2));else{l.push(`${Q(2)}Tables: {`);for(let Z of y)l.push(...Au(c,Z,3));l.push(`${Q(2)}}`)}if(T.length===0)l.push(...Jn("Views",2));else{l.push(`${Q(2)}Views: {`);for(let Z of T)l.push(...Au(c,Z,3));l.push(`${Q(2)}}`)}if(A.length===0)l.push(...Jn("Functions",2));else{l.push(`${Q(2)}Functions: {`);for(let Z of A)l.push(...fS(c,Z,3));l.push(`${Q(2)}}`)}if(oe.length===0)l.push(...Jn("Enums",2));else{l.push(`${Q(2)}Enums: {`);for(let Z of oe)l.push(`${Q(3)}${it(Z.name)}: ${Z.values.map(Oe).join(" | ")}`);l.push(`${Q(2)}}`)}l.push(...Jn("CompositeTypes",2)),l.push(`${Q(1)}}`)}return l.push("}"),t.helpers!==!1&&(l.push(""),l.push(hS(n))),t.constants!==!1&&(l.push(""),l.push(gS(u,o,n))),`${l.join(`
559
601
  `)}
560
- `}function lb(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(`${J(1)}${Ze(o)}: {`),r.push(`${J(2)}Enums: {},`),r.push(`${J(1)}},`);continue}r.push(`${J(1)}${Ze(o)}: {`),r.push(`${J(2)}Enums: {`);for(let a of s)r.push(`${J(3)}${Ze(a.name)}: [${a.values.map(Ee).join(", ")}],`);r.push(`${J(2)}},`),r.push(`${J(1)}},`)}return r.push("} as const"),r.join(`
561
- `)}function cb(e){return`type DefaultSchemaName = Extract<keyof Database, ${Ee(e)}>
602
+ `}function gS(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=Ft(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${Q(1)}${it(o)}: {`),r.push(`${Q(2)}Enums: {},`),r.push(`${Q(1)}},`);continue}r.push(`${Q(1)}${it(o)}: {`),r.push(`${Q(2)}Enums: {`);for(let a of s)r.push(`${Q(3)}${it(a.name)}: [${a.values.map(Oe).join(", ")}],`);r.push(`${Q(2)}},`),r.push(`${Q(1)}},`)}return r.push("} as const"),r.join(`
603
+ `)}function hS(e){return`type DefaultSchemaName = Extract<keyof Database, ${Oe(e)}>
562
604
 
563
605
  type InSchema = { schema: keyof Database }
564
606
 
@@ -595,7 +637,7 @@ export type Enums<
595
637
  export type CompositeTypes<
596
638
  Choice extends keyof Database[DefaultSchemaName]["CompositeTypes"] | InSchema,
597
639
  Name extends Choice extends InSchema ? keyof Database[Choice["schema"]]["CompositeTypes"] : never = never,
598
- > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function fb(e){let t=$n(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 Gr(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 Ys(e,t){return new Promise(n=>{let r=db("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 gb=`
640
+ > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function kS(e){let t=Gn(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 go(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 Aa(e,t){return new Promise(n=>{let r=yS("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 _S=`
599
641
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
600
642
  select json_build_object(
601
643
  'tables', coalesce((
@@ -699,40 +741,41 @@ select json_build_object(
699
741
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
700
742
  ), '[]'::json)
701
743
  )::text
702
- `;function hb(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function yb(e,t,n=0){let r=e.tables.map(a=>{let c=a.primaryKey??[],i=new Set(c),u=a.columns.map(l=>({name:l.name,dataType:l.dataType,nullable:l.nullable,isPrimaryKey:i.has(l.name),default:l.default,autoIncrement:l.autoIncrement,...l.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:c,foreignKeys:(a.foreignKeys??[]).map(l=>({constraintName:l.constraintName,columns:l.columns??[],referencedTable:l.referencedTable,referencedColumns:l.referencedColumns??[]})),indexes:(a.indexes??[]).filter(l=>Array.isArray(l.columns)&&l.columns.every(m=>typeof m=="string")).map(l=>({name:l.name,unique:l.unique,columns:l.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(c=>({name:c.name,dataType:c.dataType,mode:c.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 wb=`select exists (
744
+ `;function vS(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function $S(e,t,n=0){let r=e.tables.map(a=>{let c=a.primaryKey??[],i=new Set(c),u=a.columns.map(l=>({name:l.name,dataType:l.dataType,nullable:l.nullable,isPrimaryKey:i.has(l.name),default:l.default,autoIncrement:l.autoIncrement,...l.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:c,foreignKeys:(a.foreignKeys??[]).map(l=>({constraintName:l.constraintName,columns:l.columns??[],referencedTable:l.referencedTable,referencedColumns:l.referencedColumns??[]})),indexes:(a.indexes??[]).filter(l=>Array.isArray(l.columns)&&l.columns.every(m=>typeof m=="string")).map(l=>({name:l.name,unique:l.unique,columns:l.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(c=>({name:c.name,dataType:c.dataType,mode:c.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 PS=`select exists (
703
745
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
704
746
  where n.nspname = 'lepis' and c.relname = 'relation'
705
- )::text`,bb=`select coalesce(json_agg(json_build_object(
747
+ )::text`,xS=`select coalesce(json_agg(json_build_object(
706
748
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
707
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function Sb(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:_b(t)}function _b(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let c of a??[]){let i=r(c);o.has(i)||(o.add(i),s.push(c))}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 kb(e,t){let n=await e(wb);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(bb);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 vb(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Gr(e.dbUrl):fb(await Ce($b(e))),r=!1,o=async R=>{if(e.sql)return e.sql(R);if(r&&e.ref)return Gd(e.ref,R);let ee=await Ys(n,R);return ee.code===127&&!e.dbUrl&&e.ref?(r=!0,Gd(e.ref,R)):ee},s=gb.replace("$SCHEMAS$",hb(t)),a=await o(s);a.code===127&&V("tool-missing",a.err.trim()),a.code!==0&&V("failed",a.err.trim()||`could not read the schema of ${n.label}`);let c=a.out.trim();c||V("failed",`${n.label} returned nothing for the schema query`);let i=Sb(c);i||V("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await kb(o,t),{schema:l,enums:m}=yb(i,n.database,Date.now()),h=Kd(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),x=null;return e.out&&(x=pb(e.out)?e.out:mb(process.cwd(),e.out),ub(x,h,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(R=>R.kind!=="view").length,views:l.tables.filter(R=>R.kind==="view").length,functions:(l.routines??[]).length,enums:m.length,path:x,distribution:u,types:h}}async function Gd(e,t,n=P){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 $b(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 qd(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&V("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await vb(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}.`),w(r,()=>{r.path||process.stdout.write(r.types)}),0}we();import{createHash as S_}from"node:crypto";import{existsSync as Ja,mkdirSync as __,readFileSync as Va,readdirSync as k_,statSync as v_,writeFileSync as $_}from"node:fs";import{dirname as P_,isAbsolute as _m,join as jn,resolve as km}from"node:path";import{pathToFileURL as x_}from"node:url";le();function E_(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:_m(e)||e.startsWith("./")||e.startsWith("../")?x_(km(e)).href:e}function A_(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(E_(e)):Promise.resolve().then(()=>(ym(),hm))}function R_(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 wm="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 bm(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Ln(){let e;try{e=await A_()}catch(t){V("tool-missing",R_(t))}return{ready:async t=>{let n=await bm(e,t);return typeof n=="string"?await e.podmanReady(wm)??n:e.podmanReady(wm,n.name)},ensureImage:async(t,n)=>{let r=await bm(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 vm(e){return jn(e,".snoutdata","local.json")}function So(e){let t=vm(e);if(!Ja(t))return null;try{let n=JSON.parse(Va(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 T_(e,t){let n=vm(e);return __(P_(n),{recursive:!0}),$_(n,`${JSON.stringify(t,null,2)}
708
- `,{encoding:"utf8",mode:384}),n}var O_=54322;async function $m(e,t){let n=await e.ensureImage(t,o=>d(o));n&&V("tool-missing",n);let r=await e.ready(t);r&&V("tool-missing",r),await e.prepare()}function Pm(e,t,n,r=Ys){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 N_(e,t={}){let n=t.cwd??process.cwd(),r=So(n);await $m(e,r?.ref??null);let o=r??e.mint(t.port??O_),s=r?null:T_(n,o),a=await e.start(o,m=>d(m)),c=e.uri(o);if(t.noMigrations)return{status:a,uri:c,applied:[],seeded:!1,created:s};let i=Pm(e,o,Gr(c)),u=await I_(i,n,t),l=await C_(i,n,u.length>0||!!s);return{status:a,uri:c,applied:u,seeded:l,created:s}}async function I_(e,t,n){let r=km(n.dir===void 0?jn(t,"migrations"):_m(n.dir)?n.dir:jn(t,n.dir));if(!Ja(r)||!v_(r).isDirectory())return[];let o=k_(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Va(jn(r,m),"utf8");return{name:m,sql:h,checksum:S_("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(D_);s.code!==0&&V(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${Mn}`);let a=await e(`select name || E'\\t' || checksum from ${Mn};`);a.code!==0&&V("failed",a.err.trim()||`could not read ${Mn}`);let c=a.out.split(`
709
- `).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=Ur(o,c,{outOfOrder:n.outOfOrder});i.ok||V("conflict",`Refused. This folder and the local database do not agree:
749
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function ES(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:AS(t)}function AS(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let c of a??[]){let i=r(c);o.has(i)||(o.add(i),s.push(c))}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 RS(e,t){let n=await e(PS);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(xS);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 TS(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?go(e.dbUrl):kS(await He(OS(e))),r=!1,o=async A=>{if(e.sql)return e.sql(A);if(r&&e.ref)return Tu(e.ref,A);let oe=await Aa(n,A);return oe.code===127&&!e.dbUrl&&e.ref?(r=!0,Tu(e.ref,A)):oe},s=_S.replace("$SCHEMAS$",vS(t)),a=await o(s);a.code===127&&te("tool-missing",a.err.trim()),a.code!==0&&te("failed",a.err.trim()||`could not read the schema of ${n.label}`);let c=a.out.trim();c||te("failed",`${n.label} returned nothing for the schema query`);let i=ES(c);i||te("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await RS(o,t),{schema:l,enums:m}=$S(i,n.database,Date.now()),y=Ru(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),T=null;return e.out&&(T=bS(e.out)?e.out:SS(process.cwd(),e.out),wS(T,y,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(A=>A.kind!=="view").length,views:l.tables.filter(A=>A.kind==="view").length,functions:(l.routines??[]).length,enums:m.length,path:T,distribution:u,types:y}}async function Tu(e,t,n=R){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 OS(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 Ou(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&te("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await TS(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}.`),b(r,()=>{r.path||process.stdout.write(r.types)}),0}be();import{createHash as D_}from"node:crypto";import{existsSync as Ri,mkdirSync as L_,readFileSync as Ti,readdirSync as j_,statSync as U_,writeFileSync as F_}from"node:fs";import{dirname as B_,isAbsolute as hf,join as sr,resolve as yf}from"node:path";import{pathToFileURL as H_}from"node:url";de();function K_(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:hf(e)||e.startsWith("./")||e.startsWith("../")?H_(yf(e)).href:e}function G_(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(K_(e)):Promise.resolve().then(()=>(pf(),uf))}function q_(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 mf="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 ff(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function ar(){let e;try{e=await G_()}catch(t){te("tool-missing",q_(t))}return{ready:async t=>{let n=await ff(e,t);return typeof n=="string"?await e.podmanReady(mf)??n:e.podmanReady(mf,n.name)},ensureImage:async(t,n)=>{let r=await ff(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 wf(e){return sr(e,".snoutdata","local.json")}function Vo(e){let t=wf(e);if(!Ri(t))return null;try{let n=JSON.parse(Ti(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 z_(e,t){let n=wf(e);return L_(B_(n),{recursive:!0}),F_(n,`${JSON.stringify(t,null,2)}
750
+ `,{encoding:"utf8",mode:384}),n}var W_=54322;async function bf(e,t){let n=await e.ensureImage(t,o=>d(o));n&&te("tool-missing",n);let r=await e.ready(t);r&&te("tool-missing",r),await e.prepare()}function Sf(e,t,n,r=Aa){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 J_(e,t={}){let n=t.cwd??process.cwd(),r=Vo(n);await bf(e,r?.ref??null);let o=r??e.mint(t.port??W_),s=r?null:z_(n,o),a=await e.start(o,m=>d(m)),c=e.uri(o);if(t.noMigrations)return{status:a,uri:c,applied:[],seeded:!1,created:s};let i=Sf(e,o,go(c)),u=await V_(i,n,t),l=await Y_(i,n,u.length>0||!!s);return{status:a,uri:c,applied:u,seeded:l,created:s}}async function V_(e,t,n){let r=yf(n.dir===void 0?sr(t,"migrations"):hf(n.dir)?n.dir:sr(t,n.dir));if(!Ri(r)||!U_(r).isDirectory())return[];let o=j_(r).filter(m=>m.endsWith(".sql")).map(m=>{let y=Ti(sr(r,m),"utf8");return{name:m,sql:y,checksum:D_("sha256").update(y,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(X_);s.code!==0&&te(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${or}`);let a=await e(`select name || E'\\t' || checksum from ${or};`);a.code!==0&&te("failed",a.err.trim()||`could not read ${or}`);let c=a.out.split(`
751
+ `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let y=m.indexOf(" ");return{name:m.slice(0,y),checksum:m.slice(y+1)}}),i=co(o,c,{outOfOrder:n.outOfOrder});i.ok||te("conflict",`Refused. This folder and the local database do not agree:
710
752
  ${i.problems.map(m=>` ${m.reason}`).join(`
711
- `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),l=[];for(let m of i.pending){let h=u.get(m.name),x=`insert into ${Mn} (name, checksum) values (${Sm(h.name)}, ${Sm(h.checksum)});`,R=Jt(h.sql);R||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let ee=R?`begin;
712
- ${h.sql}
713
- ${x}
753
+ `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),l=[];for(let m of i.pending){let y=u.get(m.name),T=`insert into ${or} (name, checksum) values (${gf(y.name)}, ${gf(y.checksum)});`,A=cn(y.sql);A||d(`${y.name} runs outside a transaction, so an interruption can leave it half applied.`);let oe=A?`begin;
754
+ ${y.sql}
755
+ ${T}
714
756
  commit;
715
- `:`${h.sql}
716
- ${x}
717
- `,z=await e(ee);z.code!==0&&V("failed",`${h.name} failed, and nothing after it was tried.
718
- ${z.err.trim()}`,{failed:h.name,applied:l}),l.push(h.name),d(` applied ${h.name}`)}return l}async function C_(e,t,n){let r=jn(t,"seed.sql");if(!n||!Ja(r))return!1;let o=await e(Va(r,"utf8"));return o.code!==0&&V("failed",`seed.sql failed.
719
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var Mn="_snoutdata_migrations",D_=`create table if not exists ${Mn} (
757
+ `:`${y.sql}
758
+ ${T}
759
+ `,Z=await e(oe);Z.code!==0&&te("failed",`${y.name} failed, and nothing after it was tried.
760
+ ${Z.err.trim()}`,{failed:y.name,applied:l}),l.push(y.name),d(` applied ${y.name}`)}return l}async function Y_(e,t,n){let r=sr(t,"seed.sql");if(!n||!Ri(r))return!1;let o=await e(Ti(r,"utf8"));return o.code!==0&&te("failed",`seed.sql failed.
761
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var or="_snoutdata_migrations",X_=`create table if not exists ${or} (
720
762
  name text primary key,
721
763
  checksum text not null,
722
764
  applied_at timestamptz not null default now()
723
- );`;function Sm(e){return`'${e.replace(/'/g,"''")}'`}function M_(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 xm(e,t={}){let n=await N_(e,t);return w(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${O("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${O("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${O("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
724
- `),d(b(" snoutdata gen types typescript --local > database.types.ts")),d(b(" snoutdata stop"))}),0}async function Em(e,t={}){let n=t.cwd??process.cwd(),r=So(n);r||V("not-found","there is no local database for this folder. `snoutdata start` makes one."),await $m(e,r.ref);let o=await e.stop(r.ref);return w(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function Am(e,t={}){let n=t.cwd??process.cwd(),r=So(n);if(!r)return w({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 w({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return w({...s,uri:s.uri},()=>{d(` ${O("Project")} ${s.ref}`),d(` ${O("State")} ${s.ready?"running":s.status}`),d(` ${O("Port")} ${s.port}`),d(` ${O("Size")} ${M_(s.databaseBytes)}`),s.services.length>0&&d(` ${O("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
725
- `)}),0}async function Rm(e,t=process.cwd()){let n=So(t);n||V("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Pm(e,n,Gr(r))}}pe();le();Zn();ln();ct();import{resolve as Tm}from"node:path";function j_(){let e=n=>at(n),t;return{shards:n=>{if(n!==void 0)return bn({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??=bn({}),t},whoami:()=>Et(),listProjects:async()=>({...await je(),local:L_()}),localProject:n=>e(n),createProject:async n=>{let r=await P("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await gn(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)=>P("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>Ht(n),requestExport:n=>mn(n),resetPassword:n=>P("cloud-project-reset-password",{ref:n}),listTeams:()=>P("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?es(s,r,o):is(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?yr(r):ls(n)},deleteFunction:(n,r)=>{let o=e(n);return o?ns(o,r):cs(n,r)},sizeFunction:(n,r,o)=>ds(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?br(r):P("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>P("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>Gt(n),deployContainer:(n,r,o)=>hs(n,r,o),restartContainer:(n,r)=>ws(n,r),deleteContainer:(n,r)=>ys(n,r),containerLogs:(n,r)=>_r(n,r),containerEgress:async n=>{let r=await Gt(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await kr(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>P("cloud-token-list",{}),createToken:(n,r,o)=>P("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>P("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:yr(r).functions.map(o=>o.name),secretNames:br(r).secrets.map(o=>o.name)}:Cs(n)},getProducts:n=>Wt(n),setProduct:(n,r,o)=>Ts(n,r,o),listDomains:n=>Rr(n),domainAction:(n,r,o)=>Os(n,r,o),restoreWindow:n=>Ns(n),restoreTo:(n,r,o)=>Is(n,r,o),setProjectState:async(n,r)=>{let o=await P(`cloud-project-${n}`,{ref:r});try{let s=await fn(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.`}}},scaleProject:(n,r,o)=>P("cloud-project-spread",o?{ref:n,instances:r}:{ref:n,preview:!0,...r===void 0?{}:{instances:r}}),connection:n=>Ce(n),pushMigrations:(n,r)=>zs(n,{dir:r.dir,dryRun:r.dryRun})}}function L_(){let e=qn().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Mt(process.cwd())?.local;if(t&&!e.some(n=>Tm(n.folder)===Tm(t)))try{let n=ut(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Om="app_";async function U_(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Yn();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Ii(t)).map(o=>({name:`${Om}${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 Nm(e){pn(!0);let{tools:t,config:n}=await U_(),r=j_();n&&(r.callBorrowed=(i,u)=>Ci(n,i.slice(Om.length),u));let o={...e,borrowed:t};ie(t.length>0?`snoutdata mcp: ${vn.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 Et();ie(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(ie(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return ie("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;ie(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}ie(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function c(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",u=>{let l=$c(s+u);s=l.rest;for(let m of l.messages)c(F_(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 F_(e,t,n){let r;try{r=JSON.parse(e)}catch{ie(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await vc(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
765
+ );`;function gf(e){return`'${e.replace(/'/g,"''")}'`}function Z_(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 kf(e,t={}){let n=await J_(e,t);return b(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${M("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${M("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${M("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
766
+ `),d(v(" snoutdata gen types typescript --local > database.types.ts")),d(v(" snoutdata stop"))}),0}async function _f(e,t={}){let n=t.cwd??process.cwd(),r=Vo(n);r||te("not-found","there is no local database for this folder. `snoutdata start` makes one."),await bf(e,r.ref);let o=await e.stop(r.ref);return b(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function vf(e,t={}){let n=t.cwd??process.cwd(),r=Vo(n);if(!r)return b({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 b({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return b({...s,uri:s.uri},()=>{d(` ${M("Project")} ${s.ref}`),d(` ${M("State")} ${s.ready?"running":s.status}`),d(` ${M("Port")} ${s.port}`),d(` ${M("Size")} ${Z_(s.databaseBytes)}`),s.services.length>0&&d(` ${M("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
767
+ `)}),0}async function $f(e,t=process.cwd()){let n=Vo(t);n||te("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Sf(e,n,go(r))}}pe();de();_r();An();_t();import{resolve as Pf}from"node:path";function Q_(){let e=n=>ht(n),t;return{shards:n=>{if(n!==void 0)return Un({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??=Un({}),t},whoami:()=>Lt(),listProjects:async()=>({...await We(),local:ev()}),localProject:n=>e(n),createProject:async n=>{let r=await R("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Mn(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)=>R("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>nn(n),requestExport:n=>In(n),resetPassword:n=>R("cloud-project-reset-password",{ref:n}),listTeams:()=>R("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Ns(s,r,o):Us(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Br(r):Fs(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Cs(o,r):Bs(n,r)},sizeFunction:(n,r,o)=>Hs(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?Kr(r):R("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>R("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>on(n),deployContainer:(n,r,o)=>Js(n,r,o),restartContainer:(n,r)=>Ys(n,r),deleteContainer:(n,r)=>Vs(n,r),containerLogs:(n,r)=>qr(n,r),containerEgress:async n=>{let r=await on(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await zr(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>R("cloud-token-list",{}),createToken:(n,r,o)=>R("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>R("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:Br(r).functions.map(o=>o.name),secretNames:Kr(r).secrets.map(o=>o.name)}:pa(n)},getProducts:n=>an(n),setProduct:(n,r,o)=>la(n,r,o),listDomains:n=>Qr(n),domainAction:(n,r,o)=>ca(n,r,o),restoreWindow:n=>da(n),restoreTo:(n,r,o)=>ua(n,r,o),setProjectState:async(n,r)=>{let o=await R(`cloud-project-${n}`,{ref:r});try{let s=await Cn(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.`}}},scaleProject:(n,r,o)=>R("cloud-project-spread",o?{ref:n,instances:r}:{ref:n,preview:!0,...r===void 0?{}:{instances:r}}),connection:n=>He(n),pushMigrations:(n,r)=>Pa(n,{dir:r.dir,dryRun:r.dryRun})}}function ev(){let e=gr().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Dt(process.cwd())?.local;if(t&&!e.some(n=>Pf(n.folder)===Pf(t)))try{let n=$t(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var xf="app_";async function tv(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Sr();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await wl(t)).map(o=>({name:`${xf}${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 Ef(e){Nn(!0);let{tools:t,config:n}=await tv(),r=Q_();n&&(r.callBorrowed=(i,u)=>bl(n,i.slice(xf.length),u));let o={...e,borrowed:t};ce(t.length>0?`snoutdata mcp: ${Kn.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 Lt();ce(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(ce(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return ce("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;ce(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}ce(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function c(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",u=>{let l=dd(s+u);s=l.rest;for(let m of l.messages)c(nv(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 nv(e,t,n){let r;try{r=JSON.parse(e)}catch{ce(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await cd(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
726
768
  `)}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)}})}
727
- `)}}ln();pe();le();function B_(e){return e>=1?"over":e>=.8?"near":"ok"}function Im(e,t){let n=null,r=null,o=0,s=0;for(let c of e)o+=Number.isFinite(c.computeSeconds)?c.computeSeconds:0,s+=Number.isFinite(c.connections)?c.connections:0,!(c.dbBytes===null||!Number.isFinite(c.dbBytes))&&(r=r===null?c.dbBytes:Math.max(r,c.dbBytes),(!n||c.day>=n.day)&&(n={day:c.day,dbBytes:c.dbBytes,repoBytes:c.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:B_(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Ya(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 Cm(e,t={}){let n=await P("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Im(n.days,n.limit);return w({...n,summary:r},()=>{if(d(O(`${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=`${it(r.storage.usedBytes)} of ${it(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(`${it(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"}: ${Ya(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(b(`Peak in that window was ${it(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(W([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":it(o.dbBytes),o.repoBytes===null?"-":it(o.repoBytes),Ya(o.computeSeconds),String(o.connections)])])),d(b("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}xe();we();pt();le();import{createInterface as H_}from"node:readline/promises";var K_=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","table.auto","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),Xa={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"},G_="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global|auto, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Dm(e,t,n=!1){let[r,o,...s]=e,a=l=>E(t,l),c=(l,m)=>{if(l===void 0||l==="")throw new g(m);return l},i,u=`${r??""}${o!==void 0&&q_(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:M(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:c(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 l=tt(t,"nodes","nodes");if(!l.nodes)throw new g("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...l};for(let[m,h]of Object.entries(Xa))Object.assign(i,tt(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:c(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:c(a("host"),"nodes add needs --host, the address the router reaches the node at"),...tt(t,"port","port"),...Pt(t,"dbname","dbname"),...Pt(t,"sslmode","sslmode"),...Pt(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:c(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:vo(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:c(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:c(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:c(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...tt(t,"ranges","ranges"),...a("seed")!==void 0?{seed:W_(a("seed"))}:{},...a("nodes")?{nodes:vo(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:c(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:c(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:c(a("column"),"table distribute needs --column, the distribution key column"),keyspace:c(a("keyspace"),"table distribute needs --keyspace")};break;case"table auto":{let l=a("schemas");i={op:"table.auto",keyspace:c(a("keyspace"),"table auto needs --keyspace, the keyspace every table it distributes goes in"),...l!==void 0?{schemas:l.split(",").map(m=>m.trim()).filter(m=>m!=="")}:{},...tt(t,"min-bytes","min_bytes")};break}case"table reference":case"table global":i={op:`table.${o}`,table:c(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let l=_o(s[0],t);i={op:"range.split",keyspace:l.keyspace,range:l.lo,...a("at")!==void 0?{at:Za(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let l=_o(s[0],t);i={op:"range.move",keyspace:l.keyspace,range:l.lo,to:c(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 l=_o(s[0],t),m=_o(s[1],t);if(l.keyspace!==m.keyspace)throw new g(`range merge: ${l.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:l.keyspace,a:l.lo,b:m.lo};break}case"tenant pin":{let l=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:c(l,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:c(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",...Pt(t,"keyspace","keyspace")};break;case"scale":{let l=a("add")?vo(a("add")).map(h=>z_(h,t)):[],m=tt(t,"remove","remove");if(l.length===0&&!m.remove)throw new g("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...l.length>0?{add:l}:{},...m};break}case"verify":i={op:"verify",...Pt(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Pt(t,"node","node")};break;default:throw new g(`unknown operation: cluster ${e.join(" ")}. One of ${G_}`)}for(let[l,m]of Object.entries(Xa)){let h=tt(t,m,l);Object.assign(i,h)}return{body:i,destructive:K_.has(i.op)}}function q_(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function vo(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Pt(e,t,n){let r=E(e,t);return r===void 0?{}:{[n]:r}}function tt(e,t,n){let r=E(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 Za(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 W_(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 _o(e,t){let n=E(t,"keyspace"),r=E(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:Za(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:Za(o,"--range")}}function z_(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])}:{},...Pt(t,"dbname","dbname"),...Pt(t,"sslmode","sslmode")}}function ko(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 Um(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=E(e,"project")??E(e,"ref"),c=a!==void 0,i=()=>t.client??bn({project:a,admin:E(e,"admin"),token:E(e,"token")});switch(r){case"enable":{if(!c)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 l=i();if(!(l instanceof Ye))throw new g("cluster enable needs --project <ref>");if(M(e,"plan")){let h=await l.enable(!0);return w(h,()=>Fe(h,process.stdout)),0}if(!M(e,"yes")){if(!t.confirm&&!be())throw new g("making it a cluster moves the project's services into pods of their own, and nothing was asked: pass --yes, or --plan to see what it does");if(Fe(await l.enable(!0),process.stderr),!await(t.confirm??Ct)(`Make a cluster of ${l.target}?`))throw new T("failed","Not turned on. Nothing was changed.")}let m=await l.enable(!1);return w(m,()=>d(ht(m))),0}case"disable":{if(!c)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 l=i();if(!(l instanceof Ye))throw new g("cluster disable needs --project <ref>");if(M(e,"plan")){let h=await l.disable(!0);return w(h,()=>Fe(h,process.stdout)),0}if(!M(e,"yes")){if(!t.confirm&&!be())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(Fe(await l.disable(!0),process.stderr),!await(t.confirm??Ct)(`Turn the cluster of ${l.target} off?`))throw new T("failed","Not turned off. Nothing was changed.")}let m=await l.disable(!1);return w(m,()=>d(ht(m))),0}case"spread":{if(!c)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 l=i();if(!(l instanceof Ye))throw new g("cluster spread needs --project <ref>");let m={table:o??sn(E(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:sn(E(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:sn(E(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(M(e,"plan")){let x=await l.spread(m,!0);return w(x,()=>Fe(x,process.stdout)),0}if(!M(e,"yes")){if(!t.confirm&&!be())throw new g("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(Fe(await l.spread(m,!0),process.stderr),!await(t.confirm??Ct)(`Spread ${m.table} over ${l.target}?`))throw new T("failed","Not spread. Nothing was changed.")}let h=await l.spread(m,!1);return w(h,()=>d(ht(h))),0}case"retry":{if(!c)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 l=i();if(!(l instanceof Ye))throw new g("cluster retry needs --project <ref>");let m=M(e,"queue")?await l.retry({queue:!0}):await l.retry({node:sn(o??E(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return w(m,()=>d(ht(m))),0}case"replica":{if(!c)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 l=i();if(!(l instanceof Ye))throw new g("cluster replica needs --project <ref>");let m=o==="remove",h=sn(s??E(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(M(e,"plan")){let R=await l.replica(h,m,!0);return w(R,()=>Fe(R,process.stdout)),0}if(!M(e,"yes")){if(!t.confirm&&!be())throw new g(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(Fe(await l.replica(h,m,!0),process.stderr),!await(t.confirm??Ct)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new T("failed","Nothing was changed.")}let x=await l.replica(h,m,!1);return w(x,()=>d(ht(x))),0}case"alerts":{if(!c)throw new g("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let l=i();if(!(l instanceof Ye))throw new g("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await l.alerts();return w({alerts:m},()=>Bm(m,process.stdout,!0,l.ref)),0}if(o==="seen"||o==="acknowledge"){let m=sn(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new g(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await l.acknowledge(m.toLowerCase());return w(h,()=>d(ht(h))),0}throw new g(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let l=await i().status();return w(l,()=>nk(l)),0}case"ops":{let l=await i().ops();return w(l,()=>{process.stdout.write(`${l.ops.join(`
769
+ `)}}An();pe();de();function rv(e){return e>=1?"over":e>=.8?"near":"ok"}function Af(e,t){let n=null,r=null,o=0,s=0;for(let c of e)o+=Number.isFinite(c.computeSeconds)?c.computeSeconds:0,s+=Number.isFinite(c.connections)?c.connections:0,!(c.dbBytes===null||!Number.isFinite(c.dbBytes))&&(r=r===null?c.dbBytes:Math.max(r,c.dbBytes),(!n||c.day>=n.day)&&(n={day:c.day,dbBytes:c.dbBytes,repoBytes:c.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:rv(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Oi(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 Rf(e,t={}){let n=await R("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Af(n.days,n.limit);return b({...n,summary:r},()=>{if(d(M(`${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=`${wt(r.storage.usedBytes)} of ${wt(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(`${wt(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"}: ${Oi(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(v(`Peak in that window was ${wt(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(X([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":wt(o.dbBytes),o.repoBytes===null?"-":wt(o.repoBytes),Oi(o.computeSeconds),String(o.connections)])])),d(v("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}Ae();be();Pt();de();import{createInterface as ov}from"node:readline/promises";var sv=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","table.auto","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),Ni={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"},av="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global|auto, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Tf(e,t,n=!1){let[r,o,...s]=e,a=l=>E(t,l),c=(l,m)=>{if(l===void 0||l==="")throw new h(m);return l},i,u=`${r??""}${o!==void 0&&iv(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new h("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:K(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new h("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:c(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 h("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let l=ut(t,"nodes","nodes");if(!l.nodes)throw new h("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...l};for(let[m,y]of Object.entries(Ni))Object.assign(i,ut(t,y,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:c(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:c(a("host"),"nodes add needs --host, the address the router reaches the node at"),...ut(t,"port","port"),...Mt(t,"dbname","dbname"),...Mt(t,"sslmode","sslmode"),...Mt(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:c(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:Zo(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:c(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:c(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:c(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...ut(t,"ranges","ranges"),...a("seed")!==void 0?{seed:lv(a("seed"))}:{},...a("nodes")?{nodes:Zo(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:c(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 h("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:c(s[0],"table distribute needs a table: cluster table distribute <schema.table> --distribution-key C --keyspace K"),column:c(a("distribution-key")??a("column"),"table distribute needs --distribution-key, the column whose value decides which node holds each row"),keyspace:c(a("keyspace"),"table distribute needs --keyspace")};break;case"table auto":{let l=a("schemas");i={op:"table.auto",keyspace:c(a("keyspace"),"table auto needs --keyspace, the keyspace every table it distributes goes in"),...l!==void 0?{schemas:l.split(",").map(m=>m.trim()).filter(m=>m!=="")}:{},...ut(t,"min-bytes","min_bytes")};break}case"table reference":case"table global":i={op:`table.${o}`,table:c(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let l=Yo(s[0],t);i={op:"range.split",keyspace:l.keyspace,range:l.lo,...a("at")!==void 0?{at:Ii(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let l=Yo(s[0],t);i={op:"range.move",keyspace:l.keyspace,range:l.lo,to:c(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new h("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let l=Yo(s[0],t),m=Yo(s[1],t);if(l.keyspace!==m.keyspace)throw new h(`range merge: ${l.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:l.keyspace,a:l.lo,b:m.lo};break}case"tenant pin":{let l=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:c(l,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:c(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",...Mt(t,"keyspace","keyspace")};break;case"scale":{let l=a("add")?Zo(a("add")).map(y=>cv(y,t)):[],m=ut(t,"remove","remove");if(l.length===0&&!m.remove)throw new h("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...l.length>0?{add:l}:{},...m};break}case"verify":i={op:"verify",...Mt(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Mt(t,"node","node")};break;default:throw new h(`unknown operation: cluster ${e.join(" ")}. One of ${av}`)}for(let[l,m]of Object.entries(Ni)){let y=ut(t,m,l);Object.assign(i,y)}return{body:i,destructive:sv.has(i.op)}}function iv(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function Zo(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Mt(e,t,n){let r=E(e,t);return r===void 0?{}:{[n]:r}}function ut(e,t,n){let r=E(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new h(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Ii(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new h(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function lv(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new h(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Yo(e,t){let n=E(t,"keyspace"),r=E(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new h(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Ii(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new h("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Ii(o,"--range")}}function cv(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new h(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Mt(t,"dbname","dbname"),...Mt(t,"sslmode","sslmode")}}function Xo(e,t){if(e===void 0||!/^\d+$/.test(e))throw new h(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Cf(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=E(e,"project")??E(e,"ref"),c=a!==void 0,i=()=>t.client??Un({project:a,admin:E(e,"admin"),token:E(e,"token")});switch(r){case"enable":{if(!c)throw new h("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let l=i();if(!(l instanceof st))throw new h("cluster enable needs --project <ref>");if(K(e,"plan")){let y=await l.enable(!0);return b(y,()=>Ve(y,process.stdout)),0}if(!K(e,"yes")){if(!t.confirm&&!Se())throw new h("making it a cluster moves the project's services into pods of their own, and nothing was asked: pass --yes, or --plan to see what it does");if(Ve(await l.enable(!0),process.stderr),!await(t.confirm??zt)(`Make a cluster of ${l.target}?`))throw new C("failed","Not turned on. Nothing was changed.")}let m=await l.enable(!1);return b(m,()=>d(At(m))),0}case"disable":{if(!c)throw new h("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 l=i();if(!(l instanceof st))throw new h("cluster disable needs --project <ref>");if(K(e,"plan")){let y=await l.disable(!0);return b(y,()=>Ve(y,process.stdout)),0}if(!K(e,"yes")){if(!t.confirm&&!Se())throw new h("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(Ve(await l.disable(!0),process.stderr),!await(t.confirm??zt)(`Turn the cluster of ${l.target} off?`))throw new C("failed","Not turned off. Nothing was changed.")}let m=await l.disable(!1);return b(m,()=>d(At(m))),0}case"spread":{if(!c)throw new h("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let l=i();if(!(l instanceof st))throw new h("cluster spread needs --project <ref>");let m={table:o??$n(E(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --distribution-key C --key-type T"),column:$n(E(e,"distribution-key")??E(e,"column"),"cluster spread needs --distribution-key, the column to spread the rows by"),key_type:$n(E(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(K(e,"plan")){let T=await l.spread(m,!0);return b(T,()=>Ve(T,process.stdout)),0}if(!K(e,"yes")){if(!t.confirm&&!Se())throw new h("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(Ve(await l.spread(m,!0),process.stderr),!await(t.confirm??zt)(`Spread ${m.table} over ${l.target}?`))throw new C("failed","Not spread. Nothing was changed.")}let y=await l.spread(m,!1);return b(y,()=>d(At(y))),0}case"retry":{if(!c)throw new h("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let l=i();if(!(l instanceof st))throw new h("cluster retry needs --project <ref>");let m=K(e,"queue")?await l.retry({queue:!0}):await l.retry({node:$n(o??E(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return b(m,()=>d(At(m))),0}case"replica":{if(!c)throw new h("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new h("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let l=i();if(!(l instanceof st))throw new h("cluster replica needs --project <ref>");let m=o==="remove",y=$n(s??E(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(K(e,"plan")){let A=await l.replica(y,m,!0);return b(A,()=>Ve(A,process.stdout)),0}if(!K(e,"yes")){if(!t.confirm&&!Se())throw new h(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(Ve(await l.replica(y,m,!0),process.stderr),!await(t.confirm??zt)(m?`Remove the replica of node ${y}?`:`Add a replica to node ${y}?`))throw new C("failed","Nothing was changed.")}let T=await l.replica(y,m,!1);return b(T,()=>d(At(T))),0}case"alerts":{if(!c)throw new h("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let l=i();if(!(l instanceof st))throw new h("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await l.alerts();return b({alerts:m},()=>Lf(m,process.stdout,!0,l.ref)),0}if(o==="seen"||o==="acknowledge"){let m=$n(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new h(`not an alert id: ${m} (as cluster alerts lists it)`);let y=await l.acknowledge(m.toLowerCase());return b(y,()=>d(At(y))),0}throw new h(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let l=await i().status();return b(l,()=>wv(l)),0}case"ops":{let l=await i().ops();return b(l,()=>{process.stdout.write(`${l.ops.join(`
728
770
  `)}
729
- `),d(b(`Default settings: ${Object.entries(l.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),l.refusals&&l.refusals.length>0&&d(b(`Refusal kinds: ${l.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return Y_(e,i);case"autopilot":return Z_(o,e,i,t);case"advise":case"advice":{let l=tt(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(l);return w(m,()=>tk(m,process.stdout)),0}case"jobs":return Q_(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let l=await i().status();return w({nodes:l.nodes??[]},()=>Fm(l)),0}break;case"plan":{let l=Dm(n.slice(1),e,c),m=await i().plan(l.body);return w(m,()=>Fe(m,process.stdout)),0}}let u=Dm(n,e,c);if(M(e,"plan")){let l=await i().plan(u.body);return w(l,()=>Fe(l,process.stdout)),0}return J_(u,e,i,t)}async function J_(e,t,n,r){let o=M(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!be())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(Fe(u,process.stderr),!await(r.confirm??Ct)(`Run ${s} on ${a.target}?`))throw new T("failed","Not run. Nothing was changed.")}let c=await a.submit(e.body);if(c.job===void 0)return w(c.answer,()=>d(c.done)),0;if(M(t,"no-wait"))return w({job:c.job,state:"pending",plan:c.plan},()=>{process.stdout.write(`${c.job}
730
- `),d(`Job ${c.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${c.job}`)}),0;d(`Job ${c.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await Qa(a,c.job,r);return ei(i)}function sn(e,t){if(e===void 0||e==="")throw new g(t);return e}async function Ct(e){let t=H_({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 V_(e){let t=[...vs,...yc],n={};for(let r of vo(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 c=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(c))throw new g(`--set ${s} must be a whole number, not "${a}"`);n[s]=c}return n}async function Y_(e,t){let n={};for(let s of vs)Object.assign(n,tt(e,Xa[s],s));let r=E(e,"set");if(r!==void 0&&Object.assign(n,V_(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return w({settings:a},()=>process.stdout.write(`${W(Object.entries(a).map(([c,i])=>[c,String(i)]))}
731
- `)),0}let o=await t().settings(n);return w(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}function $o(e,t){let n=`every ${Math.round(t/1e3)} s`;switch(e){case"advise":return`Autopilot: advise. ${n[0].toUpperCase()}${n.slice(1)} it records what it would run, and runs nothing.`;case"auto":return`Autopilot: auto. ${n[0].toUpperCase()}${n.slice(1)}, when no job is running, it runs the advisor's recommendation as a job of its own, never in a keyspace you placed by hand.`;default:return"Autopilot: off. It runs nothing on its own. Turn it on with cluster autopilot advise, or auto."}}function Mm(e){return e?.op?` ${e.op}${e.keyspace?` in ${String(e.keyspace)}`:""}`:""}function X_(e){let t=e.cloud;if(t?.mode==="auto"){let o=[`${$o("auto",e.interval_ms)} In SnoutData Cloud each goes through the same checks as a move of yours (the database's size, and room on every node).`];if(t.taken_at){let s=Mm(t.request),a=t.job!==null?`it ran${s} (job ${t.job})`:t.error?`it did not run${s}. ${t.error}`:`it is starting${s}`;o.push(`Last taken ${t.taken_at.slice(0,19).replace("T"," ")}: ${a}`)}return o}if(t?.mode==="off")return[$o("off",e.interval_ms)];let n=[$o(e.mode,e.interval_ms)],r=e.last?.decision;if(e.last&&r&&r.kind!=="off"){let o={propose:"it would run",submit:"it ran",nothing:"nothing to run",busy:"it waited for a job",held:"it is waiting for you"},s=Mm(r.request),a=r.job!==void 0?` (job ${r.job})`:"";n.push(`Last decided ${e.last.at.slice(0,19).replace("T"," ")}: ${o[r.kind]??r.kind}${s}${a}${r.why?`. ${r.why}`:""}${r.error?`. It could not: ${r.error}`:""}`)}return n}async function Z_(e,t,n,r){let o=tt(t,"interval-ms","autopilot_interval_ms");if(e===void 0||e==="status"){if(Object.keys(o).length>0){let i=await n().setAutopilot(o);return w(i,()=>d(`Set autopilot_interval_ms=${o.autopilot_interval_ms}.`)),0}let c=await n().autopilot();return w(c,()=>{for(let i of X_(c))d(i)}),0}if(!wc.includes(e))throw new g(`cluster autopilot [off|advise|auto] [--interval-ms N], not "${e}"`);let s=e;if(s==="auto"&&!M(t,"yes")){if(!r.confirm&&!be())throw new g("autopilot auto lets the router move data on its own, and nothing was asked: pass --yes to turn it on");if(!await(r.confirm??Ct)("Let the router move data on its own (autopilot auto)?"))throw new T("failed","Not changed. The autopilot is as it was.")}let a=await n().setAutopilot({autopilot:s,...o});return w(a,()=>d($o(s,o.autopilot_interval_ms??6e4))),0}async function Q_(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return w({jobs:s},()=>lk(s)),0}case"show":{let s=await r.job(ko(t,"show"));return w(s,()=>ck(s)),0}case"watch":{let s=await Qa(r,ko(t,"watch"),o);return ei(s)}case"cancel":{let s=ko(t,"cancel");if(!M(n,"yes")){if(!o.confirm&&!be())throw new g(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??Ct)(`Cancel job ${s} on ${r.target}?`))throw new T("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return w(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=ko(t,"resume"),a=await r.resume(s);return M(n,"no-wait")?(w(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):ei(await Qa(r,s,o))}default:throw new g(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Qa(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 c of a.steps){let i=typeof c.detail?.phase=="string"?c.detail.phase:"",u=`${c.state}${i?` ${i}`:""}`;s.get(c.n)!==u&&(s.set(c.n,u),c.state!=="pending"&&d(b(` step ${c.n+1}/${a.steps.length} ${c.kind}: ${u}`)))}if(wn(a.state))return a;if(Date.now()>=o)throw new T("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(c=>setTimeout(c,r))}}function ei(e){if(e.state==="done")return w(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
732
- `)),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 T("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function q(e,t=""){e.write(`${t}
733
- `)}function ti(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 Fe(e,t){if(q(t,`${O(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&q(t,W([[" 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")}`,ti(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),q(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${ti(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)`),e.choices&&e.choices.length>0){let n={shard:"shard by",reference:"copy to every node",leave:"leave on the home node"};q(t,W([[" TABLE","CHOICE","WHY"],...e.choices.map(r=>[` ${r.table}`,r.choice==="shard"?`${n.shard} ${r.column}`:n[r.choice],r.why])]))}q(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)q(t,` warning: ${n}`);e.assumptions&&q(t,b(` ${e.assumptions}`))}function Ge(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function ek(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Ge(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Ge(t.at)}`:""}${t.to!==void 0?` --to ${Ge(t.to)}`:""}`;case"range.move":return`${n} range move ${Ge(`${t.keyspace}:${t.range}`)} --to ${Ge(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Ge(t.keyspace)} --value ${Ge(t.value)}${t.node!==void 0?` --node ${Ge(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Ge(t.name)} --host ${t.host?Ge(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Ge(t.keyspace)}`:""}`;default:return null}}function tk(e,t){q(t,e.summary),e.advice.forEach((n,r)=>{q(t),q(t,`${O(`${r+1}. ${n.op}`)} (${n.metric})`),q(t,` ${n.reason}`);let o=ek(n.request);if(q(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;q(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${ti(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])q(t,` warning: ${a}`)}else n.needs.length>0?q(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&q(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(q(t),q(t,b(e.assumptions)))}function jm(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Fm(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}q(process.stdout,W([["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 nk(e){let t=process.stdout;if(e.cloud&&(ik(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){q(t,"The home node has no Lepis catalog yet (schema lepis).");return}q(t,`${O("Cluster")} at epoch ${e.epoch}`),q(t),Fm(e);for(let s of e.keyspaces??[]){q(t),q(t,`${O(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),q(t,W([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,jm(e,a.node)])]));for(let a of s.pins)q(t,` pinned: ${a.value} on ${jm(e,a.node)}`)}let n=e.tables??[];n.length>0&&(q(t),q(t,W([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(q(t),q(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&&q(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(b(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function rk(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 ok(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 Lm(e){return e.severity.toUpperCase()}function Bm(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)q(t,`${O(Lm(a))} ${a.message}${a.acknowledged?b(" (seen)"):""}`);o.length>0&&q(t,b(` 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)q(t,`${O(Lm(a))} ${a.message}`),q(t,b(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&q(t),q(t,"Resolved in the last day:");for(let a of s)q(t,` ${a.message}${a.resolution?` ${b(a.resolution)}`:""}`)}}function sk(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function ak(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 ik(e,t){let n=ak(e);if(!e.sharded){q(t,`${O(e.ref)} is not a cluster (${n}).`),sk(e)>0&&q(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database keeps running).`);return}Bm(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(c=>c.desiredState!=="deleted"),o=1+r.filter(c=>!c.replicaOf).length,s=r.length-(o-1);if(q(t,`${O(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let c=e.pods.some(i=>i.replicaOf!==void 0);q(t,W([[" POD","STATE","NEXT",...c?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,rk(e,i),...c?[ok(i)]:[]])]))}e.router==="starting"?q(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&q(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&q(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?q(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&q(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),q(t)}function lk(e){if(e.length===0){d("No jobs yet.");return}q(process.stdout,W([["ID","OPERATION","STATE","STEPS","BY","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.actor==="lepis"?"autopilot":"you",t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function ck(e){let t=process.stdout;q(t,`${O(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),q(t,W([[" 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)&&(q(t),Fe(e.plan,t))}we();le();dn();import{spawn as dk}from"node:child_process";import{createHash as uk}from"node:crypto";import{chmodSync as pk,renameSync as Po,rmSync as Hm,writeFileSync as mk}from"node:fs";import{dirname as fk,join as gk}from"node:path";var xo=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",hk="https://github.com/snoutdata/studio/releases/download";function yk(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 wk(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 bk(){let e;try{e=await fetch(`${xo}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new T("network",`could not reach ${xo} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new T("server",`${xo} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new T("server",`${xo} did not say which version is newest`);return t.version}async function Km(e){let t;try{t=await fetch(e)}catch(n){throw new T("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new T("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Sk(e,t){let n=gk(fk(e),`.snoutdata-upgrade-${process.pid}`);mk(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Hm(r,{force:!0}),Po(e,r);try{Po(n,e)}catch(o){throw Po(r,e),o}}else pk(n,493),Po(n,e)}catch(r){Hm(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new T("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?ot:cn}`):r}}function _k(){return new Promise((e,t)=>{let n=dk(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new T("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${ot}`))),n.on("exit",r=>e(r??1))})}async function Gm(e={}){let t=e.kind??rr(),n=await bk(),r=ji(Se,n),o={installed:Se,latest:n,upToDate:!r,install:t};if(!r||e.check)return w({...o,upgraded:!1},()=>{d(r?`snoutdata ${Se} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${Se} is the newest version.`)}),0;switch(t){case"npx":return w({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${Se}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new T("usage",`this snoutdata (${Se}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?ot:cn}`);case"npm":{d(`Upgrading snoutdata ${Se} to ${n} with npm\u2026`);let s=await _k();if(s!==0)throw new T("failed",`npm exited ${s}. Run it yourself: ${ot}`);return w({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=yk(process.platform,process.arch);if(!s)throw new T("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${ot}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${hk}/cli-v${n}`,[c,i]=await Promise.all([Km(`${a}/${s}`),Km(`${a}/SHA256SUMS`)]),u=wk(i.toString("utf8"),s);if(!u)throw new T("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let l=uk("sha256").update(c).digest("hex");if(l!==u)throw new T("failed",`checksum mismatch for ${s}. Refusing to install.
771
+ `),d(v(`Default settings: ${Object.entries(l.settings).map(([m,y])=>`${m}=${y}`).join(", ")}`)),l.refusals&&l.refusals.length>0&&d(v(`Refusal kinds: ${l.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return pv(e,i);case"autopilot":return fv(o,e,i,t);case"advise":case"advice":{let l=ut(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(l);return b(m,()=>yv(m,process.stdout)),0}case"check":{let l=await i().check();return b(l,()=>hv(l,process.stdout)),l.summary.errors>0?1:0}case"jobs":return gv(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let l=await i().status();return b({nodes:l.nodes??[]},()=>Df(l)),0}break;case"plan":{let l=Tf(n.slice(1),e,c),m=await i().plan(l.body);return b(m,()=>Ve(m,process.stdout)),0}}let u=Tf(n,e,c);if(K(e,"plan")){let l=await i().plan(u.body);return b(l,()=>Ve(l,process.stdout)),0}return dv(u,e,i,t)}async function dv(e,t,n,r){let o=K(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!Se())throw new h(`${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??zt)(`Run ${s} on ${a.target}?`))throw new C("failed","Not run. Nothing was changed.")}let c=await a.submit(e.body);if(c.job===void 0)return b(c.answer,()=>d(c.done)),0;if(K(t,"no-wait"))return b({job:c.job,state:"pending",plan:c.plan},()=>{process.stdout.write(`${c.job}
772
+ `),d(`Job ${c.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${c.job}`)}),0;d(`Job ${c.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await Ci(a,c.job,r);return Mi(i)}function $n(e,t){if(e===void 0||e==="")throw new h(t);return e}async function zt(e){let t=ov({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 uv(e){let t=[...ta,...rd],n={};for(let r of Zo(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 h(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let c=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(c))throw new h(`--set ${s} must be a whole number, not "${a}"`);n[s]=c}return n}async function pv(e,t){let n={};for(let s of ta)Object.assign(n,ut(e,Ni[s],s));let r=E(e,"set");if(r!==void 0&&Object.assign(n,uv(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return b({settings:a},()=>process.stdout.write(`${X(Object.entries(a).map(([c,i])=>[c,String(i)]))}
773
+ `)),0}let o=await t().settings(n);return b(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}function Qo(e,t){let n=`every ${Math.round(t/1e3)} s`;switch(e){case"advise":return`Autopilot: advise. ${n[0].toUpperCase()}${n.slice(1)} it records what it would run, and runs nothing.`;case"auto":return`Autopilot: auto. ${n[0].toUpperCase()}${n.slice(1)}, when no job is running, it runs the advisor's recommendation as a job of its own, never in a keyspace you placed by hand.`;default:return"Autopilot: off. It runs nothing on its own. Turn it on with cluster autopilot advise, or auto."}}function Of(e){return e?.op?` ${e.op}${e.keyspace?` in ${String(e.keyspace)}`:""}`:""}function mv(e){let t=e.cloud;if(t?.mode==="auto"){let o=[`${Qo("auto",e.interval_ms)} In SnoutData Cloud each goes through the same checks as a move of yours (the database's size, and room on every node).`];if(t.taken_at){let s=Of(t.request),a=t.job!==null?`it ran${s} (job ${t.job})`:t.error?`it did not run${s}. ${t.error}`:`it is starting${s}`;o.push(`Last taken ${t.taken_at.slice(0,19).replace("T"," ")}: ${a}`)}return o}if(t?.mode==="off")return[Qo("off",e.interval_ms)];let n=[Qo(e.mode,e.interval_ms)],r=e.last?.decision;if(e.last&&r&&r.kind!=="off"){let o={propose:"it would run",submit:"it ran",nothing:"nothing to run",busy:"it waited for a job",held:"it is waiting for you"},s=Of(r.request),a=r.job!==void 0?` (job ${r.job})`:"";n.push(`Last decided ${e.last.at.slice(0,19).replace("T"," ")}: ${o[r.kind]??r.kind}${s}${a}${r.why?`. ${r.why}`:""}${r.error?`. It could not: ${r.error}`:""}`)}return n}async function fv(e,t,n,r){let o=ut(t,"interval-ms","autopilot_interval_ms");if(e===void 0||e==="status"){if(Object.keys(o).length>0){let i=await n().setAutopilot(o);return b(i,()=>d(`Set autopilot_interval_ms=${o.autopilot_interval_ms}.`)),0}let c=await n().autopilot();return b(c,()=>{for(let i of mv(c))d(i)}),0}if(!od.includes(e))throw new h(`cluster autopilot [off|advise|auto] [--interval-ms N], not "${e}"`);let s=e;if(s==="auto"&&!K(t,"yes")){if(!r.confirm&&!Se())throw new h("autopilot auto lets the router move data on its own, and nothing was asked: pass --yes to turn it on");if(!await(r.confirm??zt)("Let the router move data on its own (autopilot auto)?"))throw new C("failed","Not changed. The autopilot is as it was.")}let a=await n().setAutopilot({autopilot:s,...o});return b(a,()=>d(Qo(s,o.autopilot_interval_ms??6e4))),0}async function gv(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return b({jobs:s},()=>$v(s)),0}case"show":{let s=await r.job(Xo(t,"show"));return b(s,()=>Pv(s)),0}case"watch":{let s=await Ci(r,Xo(t,"watch"),o);return Mi(s)}case"cancel":{let s=Xo(t,"cancel");if(!K(n,"yes")){if(!o.confirm&&!Se())throw new h(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??zt)(`Cancel job ${s} on ${r.target}?`))throw new C("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return b(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Xo(t,"resume"),a=await r.resume(s);return K(n,"no-wait")?(b(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):Mi(await Ci(r,s,o))}default:throw new h(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Ci(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 c of a.steps){let i=typeof c.detail?.phase=="string"?c.detail.phase:"",u=`${c.state}${i?` ${i}`:""}`;s.get(c.n)!==u&&(s.set(c.n,u),c.state!=="pending"&&d(v(` step ${c.n+1}/${a.steps.length} ${c.kind}: ${u}`)))}if(jn(a.state))return a;if(Date.now()>=o)throw new C("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(c=>setTimeout(c,r))}}function Mi(e){if(e.state==="done")return b(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
774
+ `)),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 C("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function q(e,t=""){e.write(`${t}
775
+ `)}function Di(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){if(q(t,`${M(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&q(t,X([[" 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")}`,Di(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),q(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Di(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)`),e.choices&&e.choices.length>0){let n={shard:"shard by",reference:"copy to every node",leave:"leave on the home node"};q(t,X([[" TABLE","CHOICE","WHY"],...e.choices.map(r=>[` ${r.table}`,r.choice==="shard"?`${n.shard} ${r.column}`:n[r.choice],r.why])]))}q(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)q(t,` warning: ${n}`);e.assumptions&&q(t,v(` ${e.assumptions}`))}function tt(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function Mf(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${tt(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${tt(t.at)}`:""}${t.to!==void 0?` --to ${tt(t.to)}`:""}`;case"range.move":return`${n} range move ${tt(`${t.keyspace}:${t.range}`)} --to ${tt(t.to)}`;case"tenant.pin":return`${n} tenant pin ${tt(t.keyspace)} --value ${tt(t.value)}${t.node!==void 0?` --node ${tt(t.node)}`:""}`;case"node.add":return`${n} nodes add ${tt(t.name)} --host ${t.host?tt(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${tt(t.keyspace)}`:""}`;default:return null}}function hv(e,t){let{errors:n,warnings:r,info:o,tables:s}=e.summary;q(t,`${s} table${s===1?"":"s"} checked: ${n} to fix, ${r} to look at, ${o} suggestion${o===1?"":"s"}.`);let a={error:"fix",warn:"look at",info:"suggestion"};e.findings.forEach((c,i)=>{if(q(t),q(t,`${M(`${i+1}. ${c.table}`)} (${a[c.severity]}, ${c.code})`),q(t,` ${c.message}`),q(t,` ${c.fix}`),c.sql)for(let u of c.sql.split(`
776
+ `))q(t,` sql: ${u}`);c.operation&&q(t,` run: ${Mf(c.operation)??JSON.stringify(c.operation)}`)})}function yv(e,t){q(t,e.summary),e.advice.forEach((n,r)=>{q(t),q(t,`${M(`${r+1}. ${n.op}`)} (${n.metric})`),q(t,` ${n.reason}`);let o=Mf(n.request);if(q(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;q(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Di(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])q(t,` warning: ${a}`)}else n.needs.length>0?q(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&q(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(q(t),q(t,v(e.assumptions)))}function Nf(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Df(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}q(process.stdout,X([["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 wv(e){let t=process.stdout;if(e.cloud&&(vv(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){q(t,"The home node has no Lepis catalog yet (schema lepis).");return}q(t,`${M("Cluster")} at epoch ${e.epoch}`),q(t),Df(e);for(let s of e.keyspaces??[]){q(t),q(t,`${M(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),q(t,X([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Nf(e,a.node)])]));for(let a of s.pins)q(t,` pinned: ${a.value} on ${Nf(e,a.node)}`)}let n=e.tables??[];n.length>0&&(q(t),q(t,X([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(q(t),q(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&&q(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(v(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function bv(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 Sv(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 If(e){return e.severity.toUpperCase()}function Lf(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)q(t,`${M(If(a))} ${a.message}${a.acknowledged?v(" (seen)"):""}`);o.length>0&&q(t,v(` 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)q(t,`${M(If(a))} ${a.message}`),q(t,v(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&q(t),q(t,"Resolved in the last day:");for(let a of s)q(t,` ${a.message}${a.resolution?` ${v(a.resolution)}`:""}`)}}function kv(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function _v(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 vv(e,t){let n=_v(e);if(!e.sharded){q(t,`${M(e.ref)} is not a cluster (${n}).`),kv(e)>0&&q(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database keeps running).`);return}Lf(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(c=>c.desiredState!=="deleted"),o=1+r.filter(c=>!c.replicaOf).length,s=r.length-(o-1);if(q(t,`${M(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let c=e.pods.some(i=>i.replicaOf!==void 0);q(t,X([[" POD","STATE","NEXT",...c?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,bv(e,i),...c?[Sv(i)]:[]])]))}e.router==="starting"?q(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&q(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&q(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?q(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&q(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),q(t)}function $v(e){if(e.length===0){d("No jobs yet.");return}q(process.stdout,X([["ID","OPERATION","STATE","STEPS","BY","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.actor==="lepis"?"autopilot":"you",t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function Pv(e){let t=process.stdout;q(t,`${M(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),q(t,X([[" 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)&&(q(t),Ve(e.plan,t))}be();de();Tn();import{spawn as xv}from"node:child_process";import{createHash as Ev}from"node:crypto";import{chmodSync as Av,renameSync as es,rmSync as jf,writeFileSync as Rv}from"node:fs";import{dirname as Tv,join as Ov}from"node:path";var ts=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Nv="https://github.com/snoutdata/studio/releases/download";function Iv(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 Cv(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 Mv(){let e;try{e=await fetch(`${ts}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new C("network",`could not reach ${ts} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new C("server",`${ts} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new C("server",`${ts} did not say which version is newest`);return t.version}async function Uf(e){let t;try{t=await fetch(e)}catch(n){throw new C("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new C("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Dv(e,t){let n=Ov(Tv(e),`.snoutdata-upgrade-${process.pid}`);Rv(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;jf(r,{force:!0}),es(e,r);try{es(n,e)}catch(o){throw es(r,e),o}}else Av(n,493),es(n,e)}catch(r){jf(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new C("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?mt:Rn}`):r}}function Lv(){return new Promise((e,t)=>{let n=xv(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new C("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${mt}`))),n.on("exit",r=>e(r??1))})}async function Ff(e={}){let t=e.kind??Er(),n=await Mv(),r=vl(ke,n),o={installed:ke,latest:n,upToDate:!r,install:t};if(!r||e.check)return b({...o,upgraded:!1},()=>{d(r?`snoutdata ${ke} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ke} is the newest version.`)}),0;switch(t){case"npx":return b({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ke}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new C("usage",`this snoutdata (${ke}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?mt:Rn}`);case"npm":{d(`Upgrading snoutdata ${ke} to ${n} with npm\u2026`);let s=await Lv();if(s!==0)throw new C("failed",`npm exited ${s}. Run it yourself: ${mt}`);return b({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Iv(process.platform,process.arch);if(!s)throw new C("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${mt}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Nv}/cli-v${n}`,[c,i]=await Promise.all([Uf(`${a}/${s}`),Uf(`${a}/SHA256SUMS`)]),u=Cv(i.toString("utf8"),s);if(!u)throw new C("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let l=Ev("sha256").update(c).digest("hex");if(l!==u)throw new C("failed",`checksum mismatch for ${s}. Refusing to install.
734
777
  expected ${u}
735
- actual ${l}`);return Sk(process.execPath,c),w({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}ur();ct();dn();var qm=`snoutdata ${Se} \u2014 hosted Postgres, from a terminal or an agent
778
+ actual ${l}`);return Dv(process.execPath,c),b({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Mr();_t();Tn();var Bf=`snoutdata ${ke} \u2014 hosted Postgres, from a terminal or an agent
736
779
 
737
780
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
738
781
  --env writes DATABASE_URL into ./.env (and adds
@@ -745,8 +788,10 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Mn="_snoutdata_migrations",D_=`crea
745
788
  for when not given. Only the domain is sent
746
789
  snoutdata login --device sign in by typing a code into a browser anywhere
747
790
  snoutdata login --no-browser print the URL instead of opening one
748
- snoutdata logout
749
- snoutdata whoami
791
+ snoutdata login --profile acme sign in as a second account, kept beside the first;
792
+ any command takes --profile to run as it
793
+ snoutdata logout [--all] forget this profile's session (--all: every profile's)
794
+ snoutdata whoami [--all] whose session this is; --all lists every profile here
750
795
  snoutdata upgrade [--check] install the newest CLI, the way this one was
751
796
 
752
797
  snoutdata tokens create --name ci [--expires DAYS] [--project REF] a credential for CI, shown once
@@ -754,7 +799,7 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Mn="_snoutdata_migrations",D_=`crea
754
799
  snoutdata tokens revoke <id|sdt_prefix>
755
800
 
756
801
  snoutdata projects list
757
- snoutdata projects create --name X [--region R] [--no-wait] [--show-url]
802
+ snoutdata projects create --name X [--region R] [--servers dedicated --size S [--max-size M]] [--no-wait] [--show-url]
758
803
  prints the ref; the connection string (it holds
759
804
  the password) only with --show-url, or from db url
760
805
  snoutdata projects pause|resume|delete [--ref R] [--no-wait]
@@ -888,7 +933,7 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Mn="_snoutdata_migrations",D_=`crea
888
933
  (plus SnoutData Studio's own tools, when it is
889
934
  running here; SNOUTDATA_NO_DESKTOP opts out)
890
935
 
891
- Every command takes --json, --quiet and --help, and anything that waits takes --timeout
936
+ Every command takes --json, --quiet, --help and --profile NAME, and anything that waits takes --timeout
892
937
  SECONDS. A failure in --json mode is {"ok":false,"code","error"} on stdout, and the exit
893
938
  code says the same thing more coarsely: 2 the command was wrong, 3 the credential, 4 not
894
939
  ready yet, 5 forbidden, 6 not found, 7 conflict, 8 quota, 9 network, 10 timed out, 11 this
@@ -902,8 +947,11 @@ SNOUTDATA_NO_INTERACTIVE) nothing is asked and it exits 3 at once.
902
947
 
903
948
  A project comes from --ref, then SNOUTDATA_PROJECT, then
904
949
  .snoutdata/project.json in this folder or a parent. A token comes from
905
- SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes renews
906
- itself as it is used; CI, which has no browser to sign in with, wants "tokens create".
950
+ SNOUTDATA_ACCESS_TOKEN, then the profile's session: ~/.snoutdata/auth.json, or
951
+ ~/.snoutdata/auth.NAME.json for a profile. The profile comes from --profile, then
952
+ SNOUTDATA_PROFILE, then the folder's link (link and init record it), then the default.
953
+ The session that login writes renews itself as it is used, in its own profile; CI, which
954
+ has no browser to sign in with, wants "tokens create".
907
955
 
908
956
  psql, pg_dump and pg_restore run by this CLI verify the database's certificate
909
957
  (sslmode=verify-full). SNOUTDATA_DB_SSLMODE=require turns that check off, for a network
@@ -912,11 +960,12 @@ that re-signs Postgres traffic.
912
960
  Docs: https://docs.snoutdata.com/developers/cli
913
961
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
914
962
  Source: https://github.com/snoutdata/snout-cli
915
- `,kk=["yes","token"];async function vk(e){let[t,n,...r]=e.command;if(M(e,"version")||t==="version")return w({version:Se},()=>process.stdout.write(`${Se}
916
- `)),0;if(M(e,"help")&&t&&t!=="help"){let i=pi(t,n);if(i.length>0)return w({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(l=>[l,Do(u.name,l)]))}))},()=>{for(let u of i){let l="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${l}${m}
963
+ `,jv=["yes","token"];async function Uv(e){let[t,n,...r]=e.command;if(K(e,"version")||t==="version")return b({version:ke},()=>process.stdout.write(`${ke}
964
+ `)),0;if(K(e,"help")&&t&&t!=="help"){let i=Xi(t,n);if(i.length>0)return b({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(l=>[l,cs(u.name,l)]))}))},()=>{for(let u of i){let l="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${l}${m}
917
965
  ${u.summary}
918
- `);let h=Math.max(0,...u.flags.map(x=>x.length));for(let x of u.flags)process.stdout.write(` ${x.padEnd(h)} ${Do(u.name,x)}
966
+ `);let y=Math.max(0,...u.flags.map(T=>T.length));for(let T of u.flags)process.stdout.write(` ${T.padEnd(y)} ${cs(u.name,T)}
919
967
  `)}process.stdout.write(`
920
- Every command also takes --json, --quiet and --help.
921
- `)}),0}let o=M(e,"help")||t==="help";if(o||!t)return w({version:Se,usage:qm.trim(),commands:Co},()=>process.stdout.write(qm)),o?0:nt.usage;let s=()=>{let i=Ie(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=Gn({flag:E(e,"ref")});if(!i)throw new g("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},c=()=>{let i=Gn({flag:E(e,"ref")});return i?at(i):null};for(let i of kk)if(!(i==="yes"&&t==="projects"&&n==="scale")&&e.flags[i]!==void 0&&t!=="cluster")throw new g(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Um(e,{timeoutMs:s()});case"login":{if(M(e,"device")){let m=await or({name:Xn(),platform:process.platform});w({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(b("Waiting for that to be approved\u2026"))});let h=await sr(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});qe({accessToken:h.token});let x=E(e,"email"),R=x?(await Et()).email??void 0:void 0;return x&&Yo(x,{accessToken:h.token,email:R}),w({ok:!0,name:h.name??null,email:R??null},()=>d(`Signed in${R?` as ${R}`:""}.`)),0}let i=E(e,"domain"),u=M(e,"sso")||i!==void 0;if(u&&E(e,"provider"))throw new g("--sso and --provider are two different doors: pass one or the other");let l=await dr({provider:E(e,"provider"),sso:u,domain:i,noBrowser:M(e,"no-browser")||!be(),timeoutMs:s(),email:E(e,"email")});return w({email:l.email??null},()=>d(`Signed in${l.email?` as ${l.email}`:""}.`)),0}case"logout":{wi();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return w({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(b(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Et(),u=Kn()?.expectedEmail,l=!!(u&&i.email&&!Xo(u,i.email));return l&&ie(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),w({...i,...u?{expectedEmail:u,matchesExpected:!l}:{}},()=>{process.stdout.write(`${i.email??i.id}
922
- `),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 xl(),0;case"create":{let i=E(e,"name")??r[0];if(!i)throw new g("tokens create needs --name (what is it for, and where does it live)");return await Pl({name:i,expiresInDays:Ie(e,"expires"),project:E(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 El(i),0}default:throw new g(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await wl(),0;case"create":{let i=E(e,"name")??r[0];if(!i)throw new g("projects create needs --name");let u=E(e,"team");return await os({teamId:u?await kl(u):void 0,name:i,region:E(e,"region"),wait:!M(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:M(e,"show-url")}),0}case"pause":case"resume":case"delete":return await bl(n,E(e,"ref")??r[0]??a(),{wait:!M(e,"no-wait"),timeoutMs:s()}),0;case"scale":{let i=E(e,"instances");return await $l(E(e,"ref")??r[0]??a(),{instances:i===void 0?void 0:vl(i),yes:M(e,"yes")}),0}case"show":{let i=E(e,"ref")??r[0]??a(),u=at(i);u?nl(u):await Nc(i)}return 0;default:throw new g(`unknown command: projects ${n}`)}case"functions":{let i=c();if(i){if(n==="deploy"){if(!r[0])throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await ul(i,r[0],{dir:E(e,"dir"),verifyJwt:!M(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return pl(i),0;if((n==="delete"||n==="remove")&&r[0])return await ml(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 Cl(a(),u,{dir:E(e,"dir"),entrypoint:E(e,"entrypoint"),verifyJwt:!M(e,"no-verify-jwt")}),0}case void 0:case"list":return await jl(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 Ml(a(),u,{memoryMb:Ie(e,"memory"),concurrency:Ie(e,"concurrency"),reset:M(e,"reset")}),0}case"logs":return await Ll(a(),r[0],{errorsOnly:M(e,"errors"),limit:Ie(e,"tail"),follow:M(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new g("functions delete needs a name");return await Ul(a(),u),0}default:throw new g(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],l=E(e,"dockerfile");if(i&&l){if(u)throw new g("containers deploy takes an image or --dockerfile, not both");if(E(e,"registry-user")||M(e,"clear-registry"))throw new g("an image built with --dockerfile is uploaded to the project and needs no registry login");return await tc(a(),i,{path:l,port:Ie(e,"port"),wait:!M(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 m=E(e,"registry-user");if(m&&!M(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 Xl(a(),i,{image:u,port:Ie(e,"port"),registryUser:m,registryPassword:m?await qt():void 0,clearCredential:M(e,"clear-registry"),wait:!M(e,"no-wait")}),0}case void 0:case"list":return await nc(a()),0;case"logs":return await sc(a(),r[0],{errorsOnly:M(e,"errors"),limit:Ie(e,"tail"),follow:M(e,"follow")}),0;case"restart":{if(!r[0])throw new g("containers restart needs a name");return await rc(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new g("containers delete needs a name");return await oc(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await $r(a(),{}),0;if(i==="clear")return await $r(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!M(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&&M(e,"none"))throw new g("containers egress set takes destinations or --none, not both");return await $r(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=c();if(i){if(n==="set")return await gl(i,await lc(r,{stdin:M(e,"stdin")})),0;if(n===void 0||n==="list")return hl(i),0;if((n==="unset"||n==="remove")&&r[0])return await yl(i,r[0]),0}switch(n){case"set":return await cc(a(),r,{stdin:M(e,"stdin")}),0;case void 0:case"list":return await uc(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new g("secrets unset needs a name");return await dc(a(),u),0}default:throw new g(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await Al(a()),0;case"rotate":return await Rl(a(),{force:M(e,"force")}),0;default:throw new g(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Ec(a()),0;if(n==="enable"||n==="disable")return await Ac(a(),xc(r[0]),n==="enable"),0;throw new g(`unknown command: products ${n}. products [enable|disable ${Ar.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 Ud(a()),0;if(i==="set"){let u=Js(r[1]),l=u==="apns"?await jd({p8:E(e,"p8"),keyId:E(e,"key-id"),teamId:E(e,"team-id"),topic:E(e,"topic"),environment:E(e,"environment")}):await Ld(E(e,"file"));return await Fd(a(),u,l),0}if(i==="remove"||i==="unset")return await Bd(a(),Js(r[1])),0;throw new g(`unknown command: push credentials ${i}`)}case"auth":return await Pd(a(),n,r,{clientId:E(e,"client-id"),stdin:M(e,"stdin"),siteUrl:E(e,"site-url"),allow:E(e,"allow"),file:E(e,"file"),subject:E(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Wc(a(),{channel:E(e,"channel"),watch:M(e,"watch")});if(n==="logs")return zc(a(),{channel:E(e,"channel"),since:E(e,"since")});throw new g(`unknown command: realtime ${n}. realtime inspect|logs`)}case"ai":{let i=r[0];switch(n){case void 0:case"status":return await rd(a()),0;case"enable":case"disable":return await od(a(),n==="enable"),0;case"models":if(i===void 0||i==="list")return await ad(a()),0;if(i==="add")return await cd(a(),r[1],{source:E(e,"source"),upstream:E(e,"model"),provider:E(e,"provider"),baseUrl:E(e,"base-url"),region:E(e,"region"),priceIn:E(e,"price-in"),priceOut:E(e,"price-out"),accessKeyId:E(e,"access-key-id"),authHeader:E(e,"auth-header"),stdin:M(e,"stdin")}),0;if(i==="remove"||i==="delete")return await dd(a(),r[1]),0;throw new g(`unknown command: ai models ${i}. ai models [add|remove]`);case"providers":if(i===void 0||i==="list")return await id(a()),0;if(i==="remove"||i==="delete")return await ld(a(),r[1]),0;throw new g(`unknown command: ai providers ${i}. ai providers [remove NAME]`);case"routes":if(i===void 0||i==="list")return await ud(a()),0;if(i==="set")return await pd(a(),r[1],{models:E(e,"models"),sort:E(e,"sort")}),0;if(i==="remove"||i==="delete")return await md(a(),r[1]),0;throw new g(`unknown command: ai routes ${i}. ai routes [set NAME --models a,b | remove NAME]`);case"keys":if(i===void 0||i==="list")return await fd(a()),0;if(i==="create")return await gd(a(),{name:E(e,"name"),models:E(e,"models"),expiresDays:Ie(e,"expires")}),0;if(i==="revoke")return await hd(a(),r[1]),0;throw new g(`unknown command: ai keys ${i}. ai keys [create|revoke]`);case"budgets":if(i===void 0||i==="list")return await yd(a()),0;if(i==="set")return await wd(a(),r[1],{amount:E(e,"amount"),period:E(e,"period"),soft:M(e,"soft")}),0;if(i==="remove"||i==="delete")return await bd(a(),r[1]),0;throw new g(`unknown command: ai budgets ${i}. ai budgets [set|remove]`);case"usage":return await Sd(a(),{days:Ie(e,"days"),by:E(e,"by")}),0;case"test":return _d(a(),{model:E(e,"model"),prompt:E(e,"prompt")});default:throw new g(`unknown command: ai ${n}. ai [enable|disable|models|providers|keys|budgets|usage|test]`)}}case"domains":return await Rc(a(),n,r[0]),0;case"teams":return await _l(),0;case"link":{if(M(e,"local")){let l;try{l=vi(n)}catch(h){throw new T("not-found",h instanceof Error?h.message:String(h))}let m=jt(process.cwd(),{ref:l.ref,...l.name?{name:l.name}:{},local:l.folder});return w({ref:l.ref,local:l.folder,api:l.apiUrl,path:m},()=>{d(`Linked ${l.name??l.ref}, the local project in ${l.folder} (${m}).`),d(`Its API is ${l.apiUrl}; its database is on 127.0.0.1:${l.dbPort}.`)}),0}let i=E(e,"ref")??n;if(!i)throw new g("link needs --ref");let u=jt(process.cwd(),{ref:i});return w({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await js(a()),0;case"psql":return Uc(a(),r);case"reset-password":return await Sl(a()),0;case"access":return await Kc(a(),r,{level:E(e,"level")}),0;case"push":return Dd(a(),{dir:E(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return M(e,"window")?(await Tc(a()),0):E(e,"at")?(await Oc(a(),E(e,"at"),E(e,"name")),0):Hc(a(),{file:E(e,"file"),force:!!e.flags.force});case"export":return Bc(a(),{timeoutMs:s(),out:E(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=M(e,"local")?await Rm(await Ln()):null,u=i?i.dbUrl:E(e,"db-url");return qd(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(E(e,"schema")??"public").split(",").map(l=>l.trim()).filter(Boolean),out:E(e,"out"),sql:i?.sql})}case"start":{let i=c();if(i)return el(i)}return xm(await Ln(),{port:Ie(e,"port"),dir:E(e,"dir"),noMigrations:M(e,"no-migrations"),outOfOrder:M(e,"out-of-order")});case"stop":{let i=c();return i?tl(i):Em(await Ln())}case"status":{let i=c();return i?Qi(i):Am(await Ln())}case"usage":return Cm(a(),{days:Ie(e,"days"),history:!!e.flags.history});case"mcp":return Nm({version:Se,allowDelete:!!e.flags["allow-delete"]});case"init":return $k(e);case"upgrade":return Gm({check:M(e,"check")});default:throw new g(`unknown command: ${t}`)}}async function $k(e){let t=_i({flag:E(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.`),M(e,"env")?await Wm(o):await js(o),t.kind==="link"){let s=at(o),a=jt(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(e,"name")??Pk(process.cwd());d(`Creating a project called "${n}"\u2026`),await os({name:n,region:E(e,"region"),wait:!0,link:process.cwd(),showUrl:!M(e,"env")});let r=Gn({cwd:process.cwd()});return r&&M(e,"env")&&await Wm(r),0}async function Wm(e){let{connection:t}=await Promise.resolve().then(()=>(pe(),Ke)),n=await t(e),r=Fc(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 Pk(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function xk(e){if(e instanceof T)return ft(e.code,e.message,e.details),e.exitCode;if(e instanceof g)return ft("usage",e.message),nt.usage;if(e instanceof gt)return ft("not-signed-in",e.message),nt["not-signed-in"];if(e instanceof $e){let r=rt(e.status,e.serverCode);return ft(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),nt[r]}let t=xi(e);if(t)return ft("usage",t),nt.usage;let n=zn(e);return ft(n,e instanceof Error?e.message:String(e)),nt[n]}function ni(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Ek(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Ak(){pn(Ek(process.argv.slice(2)));let e;try{e=di(process.argv.slice(2))}catch(t){ft("usage",String(t instanceof Error?t.message:t)),ni(nt.usage);return}pn(e.json),Fi(e.flags.quiet===!0),Ei(Jn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{ni(await vk(e))}catch(t){ni(xk(t))}}Ak();
968
+ Every command also takes --json, --quiet, --help and --profile.
969
+ `)}),0}let o=K(e,"help")||t==="help";if(o||!t)return b({version:ke,usage:Bf.trim(),commands:ls},()=>process.stdout.write(Bf)),o?0:rt.usage;let s=()=>{let i=Fe(e,"timeout");if(i!==void 0){if(!(i>0))throw new h("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=fr({flag:E(e,"ref")});if(!i)throw new h("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},c=()=>{let i=fr({flag:E(e,"ref")});return i?ht(i):null};for(let i of jv)if(!(i==="yes"&&t==="projects"&&n==="scale")&&e.flags[i]!==void 0&&t!=="cluster")throw new h(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Cf(e,{timeoutMs:s()});case"login":{if(K(e,"device")){let m=await Ar({name:kr(),platform:process.platform});b({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(v("Waiting for that to be approved\u2026"))});let y=await Rr(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});nt({accessToken:y.token});let T=E(e,"email"),A=T?(await Lt()).email??void 0:void 0;return T&&As(T,{accessToken:y.token,email:A}),b({ok:!0,name:y.name??null,email:A??null,profile:De()},()=>d(`Signed in${A?` as ${A}`:""}${Li()}.`)),0}let i=E(e,"domain"),u=K(e,"sso")||i!==void 0;if(u&&E(e,"provider"))throw new h("--sso and --provider are two different doors: pass one or the other");let l=await Cr({provider:E(e,"provider"),sso:u,domain:i,noBrowser:K(e,"no-browser")||!Se(),timeoutMs:s(),email:E(e,"email")});return b({email:l.email??null,profile:De()},()=>d(`Signed in${l.email?` as ${l.email}`:""}${Li()}.`)),0}case"logout":{if(K(e,"all")){let u=ms();for(let l of u)gs(void 0,l);return b({ok:!0,profiles:u},()=>d(u.length>0?`Signed out of ${u.join(", ")}.`:"No profile was signed in.")),0}gs();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return b({ok:!0,profile:De(),environmentTokenStillSet:i},()=>{d(`Signed out${Li()}.`),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(v(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{if(K(e,"all")){let y=ms().map(T=>{let A=fs(void 0,T);return{profile:T,email:A?.email??A?.expectedEmail??null,active:T===De()}});return b({profiles:y,environmentToken:!!process.env.SNOUTDATA_ACCESS_TOKEN},()=>{y.length===0&&d('No profile is signed in on this machine. Run "snoutdata login [--profile NAME]".');let T=Math.max(0,...y.map(A=>A.profile.length));for(let A of y)process.stdout.write(`${A.active?"*":" "} ${A.profile.padEnd(T)} ${A.email??v("(email not stored)")}
970
+ `);process.env.SNOUTDATA_ACCESS_TOKEN&&d("SNOUTDATA_ACCESS_TOKEN is set in this shell, so commands use it, not these profiles.")}),0}let i=await Lt(),u=mr()?.expectedEmail,l=!!(u&&i.email&&!Rs(u,i.email));l&&ce(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`);let m=!!process.env.SNOUTDATA_ACCESS_TOKEN;return b({...i,profile:m?null:De(),...u?{expectedEmail:u,matchesExpected:!l}:{}},()=>{process.stdout.write(`${i.email??i.id}
971
+ `),!m&&De()!==Me&&d(`profile ${De()}.`),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 pc(),0;case"create":{let i=E(e,"name")??r[0];if(!i)throw new h("tokens create needs --name (what is it for, and where does it live)");return await uc({name:i,expiresInDays:Fe(e,"expires"),project:E(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new h("tokens revoke needs the token's id or its sdt_ prefix");return await mc(i),0}default:throw new h(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await oc(),0;case"create":{let i=E(e,"name")??r[0];if(!i)throw new h("projects create needs --name");let u=E(e,"team");return await Ds({teamId:u?await lc(u):void 0,name:i,region:E(e,"region"),servers:E(e,"servers"),size:E(e,"size"),maxSize:E(e,"max-size"),wait:!K(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:K(e,"show-url")}),0}case"pause":case"resume":case"delete":return await sc(n,E(e,"ref")??r[0]??a(),{wait:!K(e,"no-wait"),timeoutMs:s()}),0;case"scale":{let i=E(e,"instances");return await dc(E(e,"ref")??r[0]??a(),{instances:i===void 0?void 0:cc(i),yes:K(e,"yes")}),0}case"show":{let i=E(e,"ref")??r[0]??a(),u=ht(i);u?Hl(u):await wd(i)}return 0;default:throw new h(`unknown command: projects ${n}`)}case"functions":{let i=c();if(i){if(n==="deploy"){if(!r[0])throw new h("functions deploy needs a name: snoutdata functions deploy <name>");return await Xl(i,r[0],{dir:E(e,"dir"),verifyJwt:!K(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Zl(i),0;if((n==="delete"||n==="remove")&&r[0])return await Ql(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new h("functions deploy needs a name: snoutdata functions deploy <name>");return await Sc(a(),u,{dir:E(e,"dir"),entrypoint:E(e,"entrypoint"),verifyJwt:!K(e,"no-verify-jwt")}),0}case void 0:case"list":return await vc(a()),0;case"size":{let u=r[0];if(!u)throw new h("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await _c(a(),u,{memoryMb:Fe(e,"memory"),concurrency:Fe(e,"concurrency"),reset:K(e,"reset")}),0}case"logs":return await $c(a(),r[0],{errorsOnly:K(e,"errors"),limit:Fe(e,"tail"),follow:K(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new h("functions delete needs a name");return await Pc(a(),u),0}default:throw new h(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],l=E(e,"dockerfile");if(i&&l){if(u)throw new h("containers deploy takes an image or --dockerfile, not both");if(E(e,"registry-user")||K(e,"clear-registry"))throw new h("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Bc(a(),i,{path:l,port:Fe(e,"port"),wait:!K(e,"no-wait")}),0}if(!i||!u)throw new h("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let m=E(e,"registry-user");if(m&&!K(e,"stdin"))throw new h('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await Lc(a(),i,{image:u,port:Fe(e,"port"),registryUser:m,registryPassword:m?await sn():void 0,clearCredential:K(e,"clear-registry"),wait:!K(e,"no-wait")}),0}case void 0:case"list":return await Hc(a()),0;case"logs":return await qc(a(),r[0],{errorsOnly:K(e,"errors"),limit:Fe(e,"tail"),follow:K(e,"follow")}),0;case"restart":{if(!r[0])throw new h("containers restart needs a name");return await Kc(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new h("containers delete needs a name");return await Gc(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await Jr(a(),{}),0;if(i==="clear")return await Jr(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!K(e,"none"))throw new h("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&K(e,"none"))throw new h("containers egress set takes destinations or --none, not both");return await Jr(a(),{set:u}),0}throw new h(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new h(`unknown command: containers ${n}`)}case"secrets":{let i=c();if(i){if(n==="set")return await tc(i,await Jc(r,{stdin:K(e,"stdin")})),0;if(n===void 0||n==="list")return nc(i),0;if((n==="unset"||n==="remove")&&r[0])return await rc(i,r[0]),0}switch(n){case"set":return await Vc(a(),r,{stdin:K(e,"stdin")}),0;case void 0:case"list":return await Xc(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new h("secrets unset needs a name");return await Yc(a(),u),0}default:throw new h(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await fc(a()),0;case"rotate":return await gc(a(),{force:K(e,"force")}),0;default:throw new h(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await md(a()),0;if(n==="enable"||n==="disable")return await fd(a(),pd(r[0]),n==="enable"),0;throw new h(`unknown command: products ${n}. products [enable|disable ${Zr.join("|")}]`)}case"push":{if(n!=="credentials")throw new h("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await Pu(a()),0;if(i==="set"){let u=xa(r[1]),l=u==="apns"?await vu({p8:E(e,"p8"),keyId:E(e,"key-id"),teamId:E(e,"team-id"),topic:E(e,"topic"),environment:E(e,"environment")}):await $u(E(e,"file"));return await xu(a(),u,l),0}if(i==="remove"||i==="unset")return await Eu(a(),xa(r[1])),0;throw new h(`unknown command: push credentials ${i}`)}case"auth":return await uu(a(),n,r,{clientId:E(e,"client-id"),stdin:K(e,"stdin"),siteUrl:E(e,"site-url"),allow:E(e,"allow"),file:E(e,"file"),subject:E(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Nd(a(),{channel:E(e,"channel"),watch:K(e,"watch")});if(n==="logs")return Id(a(),{channel:E(e,"channel"),since:E(e,"since")});throw new h(`unknown command: realtime ${n}. realtime inspect|logs`)}case"ai":{let i=r[0];switch(n){case void 0:case"status":return await Kd(a()),0;case"enable":case"disable":return await Gd(a(),n==="enable"),0;case"models":if(i===void 0||i==="list")return await zd(a()),0;if(i==="add")return await Vd(a(),r[1],{source:E(e,"source"),upstream:E(e,"model"),provider:E(e,"provider"),baseUrl:E(e,"base-url"),region:E(e,"region"),priceIn:E(e,"price-in"),priceOut:E(e,"price-out"),accessKeyId:E(e,"access-key-id"),authHeader:E(e,"auth-header"),stdin:K(e,"stdin")}),0;if(i==="remove"||i==="delete")return await Yd(a(),r[1]),0;throw new h(`unknown command: ai models ${i}. ai models [add|remove]`);case"providers":if(i===void 0||i==="list")return await Wd(a()),0;if(i==="remove"||i==="delete")return await Jd(a(),r[1]),0;throw new h(`unknown command: ai providers ${i}. ai providers [remove NAME]`);case"routes":if(i===void 0||i==="list")return await Xd(a()),0;if(i==="set")return await Zd(a(),r[1],{models:E(e,"models"),sort:E(e,"sort")}),0;if(i==="remove"||i==="delete")return await Qd(a(),r[1]),0;throw new h(`unknown command: ai routes ${i}. ai routes [set NAME --models a,b | remove NAME]`);case"keys":if(i===void 0||i==="list")return await eu(a()),0;if(i==="create")return await tu(a(),{name:E(e,"name"),models:E(e,"models"),expiresDays:Fe(e,"expires")}),0;if(i==="revoke")return await nu(a(),r[1]),0;throw new h(`unknown command: ai keys ${i}. ai keys [create|revoke]`);case"budgets":if(i===void 0||i==="list")return await ru(a()),0;if(i==="set")return await ou(a(),r[1],{amount:E(e,"amount"),period:E(e,"period"),soft:K(e,"soft")}),0;if(i==="remove"||i==="delete")return await su(a(),r[1]),0;throw new h(`unknown command: ai budgets ${i}. ai budgets [set|remove]`);case"usage":return await au(a(),{days:Fe(e,"days"),by:E(e,"by")}),0;case"test":return iu(a(),{model:E(e,"model"),prompt:E(e,"prompt")});default:throw new h(`unknown command: ai ${n}. ai [enable|disable|models|providers|keys|budgets|usage|test]`)}}case"domains":return await gd(a(),n,r[0]),0;case"teams":return await ic(),0;case"link":{if(K(e,"local")){let l;try{l=ll(n)}catch(y){throw new C("not-found",y instanceof Error?y.message:String(y))}let m=Xt(process.cwd(),{ref:l.ref,...l.name?{name:l.name}:{},local:l.folder});return b({ref:l.ref,local:l.folder,api:l.apiUrl,path:m},()=>{d(`Linked ${l.name??l.ref}, the local project in ${l.folder} (${m}).`),d(`Its API is ${l.apiUrl}; its database is on 127.0.0.1:${l.dbPort}.`)}),0}let i=E(e,"ref")??n;if(!i)throw new h("link needs --ref");let u=Xt(process.cwd(),{ref:i});return b({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await ga(a()),0;case"psql":return Pd(a(),r);case"reset-password":return await ac(a()),0;case"access":return await Rd(a(),r,{level:E(e,"level")}),0;case"push":return ku(a(),{dir:E(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return K(e,"window")?(await hd(a()),0):E(e,"at")?(await yd(a(),E(e,"at"),E(e,"name")),0):Ad(a(),{file:E(e,"file"),force:!!e.flags.force});case"export":return Ed(a(),{timeoutMs:s(),out:E(e,"out"),statusOnly:!!e.flags.status});default:throw new h(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new h(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=K(e,"local")?await $f(await ar()):null,u=i?i.dbUrl:E(e,"db-url");return Ou(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(E(e,"schema")??"public").split(",").map(l=>l.trim()).filter(Boolean),out:E(e,"out"),sql:i?.sql})}case"start":{let i=c();if(i)return Fl(i)}return kf(await ar(),{port:Fe(e,"port"),dir:E(e,"dir"),noMigrations:K(e,"no-migrations"),outOfOrder:K(e,"out-of-order")});case"stop":{let i=c();return i?Bl(i):_f(await ar())}case"status":{let i=c();return i?Ul(i):vf(await ar())}case"usage":return Rf(a(),{days:Fe(e,"days"),history:!!e.flags.history});case"mcp":return Ef({version:ke,allowDelete:!!e.flags["allow-delete"]});case"init":return Fv(e);case"upgrade":return Ff({check:K(e,"check")});default:throw new h(`unknown command: ${t}`)}}async function Fv(e){let t=al({flag:E(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.`),K(e,"env")?await Hf(o):await ga(o),t.kind==="link"){let s=ht(o),a=Xt(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(e,"name")??Bv(process.cwd());d(`Creating a project called "${n}"\u2026`),await Ds({name:n,region:E(e,"region"),wait:!0,link:process.cwd(),showUrl:!K(e,"env")});let r=fr({cwd:process.cwd()});return r&&K(e,"env")&&await Hf(r),0}async function Hf(e){let{connection:t}=await Promise.resolve().then(()=>(pe(),et)),n=await t(e),r=xd(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 Bv(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Hv(e){if(e instanceof C)return ft(e.code,e.message,e.details),e.exitCode;if(e instanceof h)return ft("usage",e.message),rt.usage;if(e instanceof Et)return ft("not-signed-in",e.message),rt["not-signed-in"];if(e instanceof Re){let r=pt(e.status,e.serverCode);return ft(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),rt[r]}let t=ul(e);if(t)return ft("usage",t),rt.usage;let n=yr(e);return ft(n,e instanceof Error?e.message:String(e)),rt[n]}function ns(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Kv(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}function Li(){return De()===Me?"":` (profile ${De()})`}async function Gv(){Nn(Kv(process.argv.slice(2)));let e;try{e=Vi(process.argv.slice(2))}catch(t){ft("usage",String(t instanceof Error?t.message:t)),ns(rt.usage);return}Nn(e.json),xl(e.flags.quiet===!0),pl(wr({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{nl(rl({flag:E(e,"profile")}).name)}catch(t){ft("usage",String(t instanceof Error?t.message:t)),ns(rt.usage);return}try{ns(await Uv(e))}catch(t){ns(Hv(t))}}Gv();