snoutdata 0.20.0 → 0.21.1

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,32 +1,200 @@
1
1
  #!/usr/bin/env node
2
- var ig=Object.defineProperty;var W=(e,t)=>()=>(e&&(t=e(e=0)),t);var Li=(e,t)=>{for(var n in t)ig(e,n,{get:t[n],enumerable:!0})};function ji(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]=dg(o);if(lg.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(!cg.has(s))throw new h(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function dg(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function A(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function z(e,t){return e.flags[t]===!0}function je(e,t){let n=A(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new h(`--${t} must be a whole number, not "${n}"`);return r}var h,lg,cg,Te=W(()=>{"use strict";h=class extends Error{},lg=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","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"]);cg=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 ug,existsSync as Bi,mkdirSync as Hi,readFileSync as Gi,rmSync as pg,writeFileSync as Ki}from"node:fs";import{homedir as ar}from"node:os";import{dirname as rs,join as qi,parse as mg,resolve as fg}from"node:path";function os(e=ar()){return qi(e,".snoutdata","auth.json")}function ir(e=ar()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=os(e);if(!Bi(n))return null;try{let r=JSON.parse(Gi(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function et(e,t=ar()){let n=os(t);Hi(rs(n),{recursive:!0}),Ki(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),ug(n,384)}function Wi(e=ar()){pg(os(e),{force:!0})}function zi(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Ji(e){return qi(e,".snoutdata","project.json")}function Wt(e){let t=fg(e),n=mg(t).root;for(;;){let r=Ji(t);if(Bi(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=rs(t)}}function zt(e,t){let n=Ji(e);return Hi(rs(n),{recursive:!0}),Ki(n,`${JSON.stringify(t,null,2)}
4
- `),n}function lr(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Wt(e.cwd??process.cwd())?.ref??null}function Vi(e){let t=e.environment??process.env,n=Wt(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 wt=W(()=>{"use strict"});import{existsSync as ss,readFileSync as Yi}from"node:fs";import{homedir as gg}from"node:os";import{isAbsolute as hg,join as bt,resolve as Jt}from"node:path";function yg(e=process.env,t=process.platform,n=gg()){let r;return t==="win32"?r=e.APPDATA??bt(n,"AppData","Roaming"):t==="darwin"?r=bt(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??bt(n,".config"),["snoutdata","snoutdata-dev"].map(o=>bt(r,o,"localProjects.json"))}function cr(e=yg()){let t=[];for(let n of e)if(ss(n))try{let r=JSON.parse(Yi(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 dr(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 _n(e){return ss(bt(e,"compose.yaml"))&&ss(bt(e,".env"))}function St(e,t=null){let n=Jt(e);if(!_n(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=dr(Yi(bt(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${bt(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 Xi(e,t=process.cwd(),n=cr()){if(!e){if(_n(t))return St(t);let[i]=n;if(n.length===1&&i)return St(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=hg(e)?e:Jt(t,e);if(_n(r)){let i=n.find(u=>Jt(u.folder)===Jt(r));return St(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 St(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 Zi(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 Qi(e,t,n=cr()){if(t&&_n(t))try{let r=n.find(s=>Jt(s.folder)===Jt(t)),o=St(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&_n(r.folder))return St(r.folder,r.name);return null}var $n=W(()=>{"use strict"});function te(e,t,n){throw new C(e,t,n)}function dt(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&&wg.has(t)?"server":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function el(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 ur(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(bg.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 ct,C,wg,bg,be=W(()=>{"use strict";ct={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 ct[this.code]}};wg=new Set(["provider_auth","upstream_error","transport","egress_refused"]);bg=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function pr(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 Sg){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 Vt(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 tl(e){as=e}function Se(){return(as??pr({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Yt(){return(as??pr({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Sg,as,kt=W(()=>{"use strict";Sg=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];as=null});import{createHmac as kg,randomBytes as _g,timingSafeEqual as $g}from"node:crypto";import{existsSync as vg,readFileSync as Pg}from"node:fs";import{homedir as is,hostname as xg}from"node:os";import{join as Xt}from"node:path";function Eg(e,t,n){if(t==="win32"){let r=n.APPDATA??Xt(is(),"AppData","Roaming");return Xt(r,e)}return t==="darwin"?Xt(is(),"Library","Application Support",e):Xt(n.XDG_CONFIG_HOME??Xt(is(),".config"),e)}function rl(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>Xt(Eg(n,e,t),"mcp.json"))}function Ag(e=process.platform,t=process.env){return rl(e,t)[0]}function Rg(e=Ag()){if(!vg(e))return null;try{let t=JSON.parse(Pg(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Tg(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Ng(e,t,n){return kg("sha256",e).update(`${Og}
2
+ var jg=Object.defineProperty;var J=(e,t)=>()=>(e&&(t=e(e=0)),t);var rl=(e,t)=>{for(var n in t)jg(e,n,{get:t[n],enumerable:!0})};function ol(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]=Bg(o);if(Ug.has(s)){let c=a??e[++r];if(c===void 0)throw new y(`--${s} needs a value`);n[s]=c;continue}if(a!==void 0)throw new y(`--${s} does not take a value`);if(!Fg.has(s))throw new y(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Bg(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function R(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function W(e,t){return e.flags[t]===!0}function Fe(e,t){let n=R(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new y(`--${t} must be a whole number, not "${n}"`);return r}var y,Ug,Fg,Te=J(()=>{"use strict";y=class extends Error{},Ug=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"]);Fg=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 Hg,existsSync as gr,mkdirSync as il,readdirSync as Gg,readFileSync as ll,rmSync as Kg,writeFileSync as cl}from"node:fs";import{homedir as Qt}from"node:os";import{dirname as ws,join as hr,parse as qg,resolve as zg}from"node:path";function Tn(e){if(!dl.test(e))throw new ys(`"${e}" is not a profile name: use letters, digits, - and _ (up to 40), starting with a letter or digit`);return e}function ul(e){xt=Tn(e)}function Se(){return xt}function pl(e){let t=e.environment??process.env;if(e.flag!==void 0)return{name:Tn(e.flag),from:"flag"};if(t.SNOUTDATA_PROFILE)return{name:Tn(t.SNOUTDATA_PROFILE),from:"environment"};let n=Ft(e.cwd??process.cwd())?.profile;return n?{name:Tn(n),from:"link"}:{name:xe,from:"default"}}function bs(e=Qt(),t=xt){return hr(e,".snoutdata",t===xe?"auth.json":`auth.${Tn(t)}.json`)}function Ss(e=Qt()){let t=hr(e,".snoutdata");if(!gr(t))return[];let n=Gg(t).map(r=>/^auth\.(.+)\.json$/.exec(r)?.[1]).filter(r=>r!==void 0&&dl.test(r)&&r!==xe).sort();return[...gr(hr(t,"auth.json"))?[xe]:[],...n]}function ks(e=Qt(),t=xt){let n=bs(e,t);if(!gr(n))return null;try{let r=JSON.parse(ll(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function yr(e=Qt(),t=xt){let n=process.env.SNOUTDATA_ACCESS_TOKEN;return n?{accessToken:n}:ks(e,t)}function ot(e,t=Qt(),n=xt){let r=bs(t,n);il(ws(r),{recursive:!0}),cl(r,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),Hg(r,384)}function _s(e=Qt(),t=xt){Kg(bs(e,t),{force:!0})}function ml(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function fl(e){return hr(e,".snoutdata","project.json")}function Ft(e){let t=zg(e),n=qg(t).root;for(;;){let r=fl(t);if(gr(r))try{let o=JSON.parse(ll(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=ws(t)}}function en(e,t,n=xt){let r=fl(e);il(ws(r),{recursive:!0});let o={ref:t.ref,...t.name!==void 0?{name:t.name}:{},...t.local!==void 0?{local:t.local}:{},...n!==xe?{profile:n}:{}};return cl(r,`${JSON.stringify(o,null,2)}
4
+ `),r}function wr(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Ft(e.cwd??process.cwd())?.ref??null}function gl(e){let t=e.environment??process.env,n=Ft(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 xe,dl,ys,xt,Et=J(()=>{"use strict";xe="default",dl=/^[A-Za-z0-9][A-Za-z0-9_-]{0,39}$/,ys=class extends Error{};xt=xe});import{existsSync as vs,readFileSync as hl}from"node:fs";import{homedir as Wg}from"node:os";import{isAbsolute as Jg,join as At,resolve as tn}from"node:path";function Vg(e=process.env,t=process.platform,n=Wg()){let r;return t==="win32"?r=e.APPDATA??At(n,"AppData","Roaming"):t==="darwin"?r=At(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??At(n,".config"),["snoutdata","snoutdata-dev"].map(o=>At(r,o,"localProjects.json"))}function br(e=Vg()){let t=[];for(let n of e)if(vs(n))try{let r=JSON.parse(hl(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 Sr(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 On(e){return vs(At(e,"compose.yaml"))&&vs(At(e,".env"))}function Rt(e,t=null){let n=tn(e);if(!On(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=Sr(hl(At(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${At(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 yl(e,t=process.cwd(),n=br()){if(!e){if(On(t))return Rt(t);let[i]=n;if(n.length===1&&i)return Rt(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=Jg(e)?e:tn(t,e);if(On(r)){let i=n.find(u=>tn(u.folder)===tn(r));return Rt(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 Rt(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 wl(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 bl(e,t,n=br()){if(t&&On(t))try{let r=n.find(s=>tn(s.folder)===tn(t)),o=Rt(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&On(r.folder))return Rt(r.folder,r.name);return null}var Nn=J(()=>{"use strict"});function re(e,t,n){throw new C(e,t,n)}function yt(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&&Yg.has(t)?"server":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function Sl(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 kr(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(Xg.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 st,C,Yg,Xg,ke=J(()=>{"use strict";st={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 st[this.code]}};Yg=new Set(["provider_auth","upstream_error","transport","egress_refused"]);Xg=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function _r(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 Zg){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 nn(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 kl(e){$s=e}function _e(){return($s??_r({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function rn(){return($s??_r({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Zg,$s,Tt=J(()=>{"use strict";Zg=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];$s=null});import{createHmac as Qg,randomBytes as eh,timingSafeEqual as th}from"node:crypto";import{existsSync as nh,readFileSync as rh}from"node:fs";import{homedir as Ps,hostname as oh}from"node:os";import{join as on}from"node:path";function sh(e,t,n){if(t==="win32"){let r=n.APPDATA??on(Ps(),"AppData","Roaming");return on(r,e)}return t==="darwin"?on(Ps(),"Library","Application Support",e):on(n.XDG_CONFIG_HOME??on(Ps(),".config"),e)}function vl(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>on(sh(n,e,t),"mcp.json"))}function ah(e=process.platform,t=process.env){return vl(e,t)[0]}function ih(e=ah()){if(!nh(e))return null;try{let t=JSON.parse(rh(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function lh(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function dh(e,t,n){return Qg("sha256",e).update(`${ch}
5
5
  ${t}
6
- ${n}`).digest("base64url")}function Ig(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&$g(n,r)}async function sl(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=_g(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(ol)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&Ig(o.proof,Ng(e.token,t,n))}catch{return!1}}async function cs(e,t,n,r=sl){if(!await r(e).catch(()=>!1))throw new mr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function fr(e,t=sl){if(e!==void 0)return nl(e,t);let n="no-config";for(let r of rl()){let o=Rg(r);if(!o)continue;let s=await nl(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 nl(e,t){let n=Tg(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await cs(r,"/cli/whoami",{signal:AbortSignal.timeout(ol)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof mr?{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 al(e,t){let n=await cs(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 gr(){return xg()}async function ls(e,t,n,r){let o=await cs(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 il(e){return await ls(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await ls(e,"tools/list",{},3e3)).tools??[]}async function ll(e,t,n){return ls(e,"tools/call",{name:t,arguments:n},12e4)}var ol,Og,mr,hr=W(()=>{"use strict";ol=1500,Og="snoutdata-studio-proof-v1";mr=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 ds(e){try{return new URL(e).host}catch{return e}}function wr(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function dl(e){return{path:`/v1/data${e}`,oldPath:`/rest/v1${e}`}}function _t(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function Dg(e){return yr.has(ds(e))}function Lg(e,t,n){return t===void 0?e:{...e,[n?Mg:Cg]:t}}async function Ve(e,t,n={},r=fetch){let o=ds(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:Lg(s,n.key,u),body:n.body})}if(yr.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:(yr.add(o),i)}async function ul(e,t,n,r=fetch){if(!yr.has(ds(e)))try{await Ve(e,_t("/health","/health"),{key:n},r)}catch{}return`${e}${Dg(e)?t.oldPath:t.path}`}var Cg,Mg,yr,br=W(()=>{"use strict";Cg="Snout-Key",Mg="apikey",yr=new Set});import{basename as jg}from"node:path";function kr(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=jg(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 Ug(e=kr(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?ut:vn}}function us(e={}){let t=e.kind??kr(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?ut:`${vn} (or ${ut})`;return`snoutdata ${ke} is installed and ${r}. Run \`${Ug(t,n)}\`, or install again: ${o}`}function pl(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,Sr,vn,ut,Pn=W(()=>{"use strict";ke="0.20.0",Sr=`snoutdata-cli/${ke}`,vn="curl -fsSL https://snoutdata.com/install.sh | sh",ut="npm install -g snoutdata@latest"});async function ml(e,t){let n=await Ve(Ye,wr(e),{method:"POST",key:Xe,headers:{"Content-Type":"application/json","x-client-info":Sr},body:JSON.stringify(t)});vr(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw Pr(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function _r(e={}){let{status:t,body:n}=await ml("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 $r(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 ml("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 ps=W(()=>{"use strict";pe();br();be();Pn()});function En(e){xn=e}function gl(e){fl=e}function xr(){return xn}function $(e){return hl?`\x1B[2m${e}\x1B[0m`:e}function M(e){return hl?`\x1B[1m${e}\x1B[0m`:e}function b(e,t){if(xn){process.stdout.write(`${JSON.stringify(e,null,2)}
7
- `);return}t()}function d(e){!xn&&!fl&&process.stderr.write(`${e}
8
- `)}function $t(e,t,n){process.stderr.write(`${t}
9
- `),xn&&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 pt(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 xn,fl,hl,de=W(()=>{"use strict";xn=!1,fl=!1;hl=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as bl,randomBytes as ms}from"node:crypto";import{createServer as Bg}from"node:http";import{createInterface as Hg}from"node:readline/promises";import{spawn as Gg}from"node:child_process";function Er(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function yl(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.":ch(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
13
- <meta name="viewport" content="width=device-width,initial-scale=1"></head>
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
- <div style="text-align:center;max-width:460px;padding:40px 24px">
16
- <div style="width:44px;height:44px;margin:0 auto 30px;border-radius:12px;background:#191714;border:1px solid #262320;display:grid;place-items:center">
17
- <div style="width:14px;height:14px;border-radius:50%;background:${n}"></div>
6
+ ${n}`).digest("base64url")}function uh(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&th(n,r)}async function Pl(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=eh(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout($l)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&uh(o.proof,dh(e.token,t,n))}catch{return!1}}async function Es(e,t,n,r=Pl){if(!await r(e).catch(()=>!1))throw new vr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function $r(e,t=Pl){if(e!==void 0)return _l(e,t);let n="no-config";for(let r of vl()){let o=ih(r);if(!o)continue;let s=await _l(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 _l(e,t){let n=lh(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Es(r,"/cli/whoami",{signal:AbortSignal.timeout($l)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof vr?{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 xl(e,t){let n=await Es(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 Pr(){return oh()}async function xs(e,t,n,r){let o=await Es(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 El(e){return await xs(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await xs(e,"tools/list",{},3e3)).tools??[]}async function Al(e,t,n){return xs(e,"tools/call",{name:t,arguments:n},12e4)}var $l,ch,vr,xr=J(()=>{"use strict";$l=1500,ch="snoutdata-studio-proof-v1";vr=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 As(e){try{return new URL(e).host}catch{return e}}function Ar(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function Tl(e){return{path:`/v1/data${e}`,oldPath:`/rest/v1${e}`}}function at(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function fh(e){return Er.has(As(e))}function gh(e,t,n){return t===void 0?e:{...e,[n?mh:ph]:t}}async function He(e,t,n={},r=fetch){let o=As(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:gh(s,n.key,u),body:n.body})}if(Er.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:(Er.add(o),i)}async function Ol(e,t,n,r=fetch){if(!Er.has(As(e)))try{await He(e,at("/health","/health"),{key:n},r)}catch{}return`${e}${fh(e)?t.oldPath:t.path}`}var ph,mh,Er,Rr=J(()=>{"use strict";ph="Snout-Key",mh="apikey",Er=new Set});import{basename as hh}from"node:path";function Or(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=hh(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 yh(e=Or(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?wt:In}}function Rs(e={}){let t=e.kind??Or(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?wt:`${In} (or ${wt})`;return`snoutdata ${ve} is installed and ${r}. Run \`${yh(t,n)}\`, or install again: ${o}`}function Nl(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 ve,Tr,In,wt,Cn=J(()=>{"use strict";ve="0.21.1",Tr=`snoutdata-cli/${ve}`,In="curl -fsSL https://snoutdata.com/install.sh | sh",wt="npm install -g snoutdata@latest"});async function Il(e,t){let n=await He(Ge,Ar(e),{method:"POST",key:Ke,headers:{"Content-Type":"application/json","x-client-info":Tr},body:JSON.stringify(t)});Cr(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw Mr(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function Nr(e={}){let{status:t,body:n}=await Il("cloud-device-start",{client:e});return t===429&&re("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&re("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)&&re("server",n.error??`could not start a pairing (${t})`),n}async function Ir(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&re("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 Il("cloud-device-poll",{secret:e});switch(o===404&&re("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&re("usage",s.error??"that is not a pairing secret"),o!==200&&re("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":re("forbidden",s.error??"that pairing was refused in the browser");case"used":re("conflict",s.error??"that pairing has already been completed");case"expired":re("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 Ts=J(()=>{"use strict";fe();Rr();ke();Cn()});function it(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Lr(e,t=!1){return`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>SnoutData</title>
7
+ <meta name="viewport" content="width=device-width,initial-scale=1">
8
+ <meta name="referrer" content="no-referrer">
9
+ <style>${Sh}</style></head>
10
+ <body><main class="gate"><div class="gate-card${t?" wide":""}">
11
+ <img class="brand-mark" src="${bh}" width="40" height="40" alt="">
12
+ ${e}
13
+ </div></main></body></html>`}function Cl(e){return`<p class="small muted where">Signing in the <code>snoutdata</code> CLI on this computer${e?` as profile <strong>${it(e)}</strong>`:""}. Your terminal carries on once you are in.</p>`}function Ml(e){return e?`<p class="error" role="alert">${it(e)}</p>`:""}function Ll(e){let t=encodeURIComponent(e.token),n=e.google?`<a class="btn btn-provider" href="/google?t=${t}">${vh}Continue with Google</a>`:"";return Lr(`<h1>SnoutData</h1>
14
+ <p class="small muted">Sign in with the same account as the app and the dashboard.</p>
15
+ <div class="stack">
16
+ ${n}
17
+ <a class="btn btn-provider" href="/github?t=${t}">${$h}Continue with GitHub</a>
18
+ <div class="or">or</div>
19
+ <form method="post" action="/email">
20
+ <input type="hidden" name="t" value="${it(e.token)}">
21
+ <label for="email">Work email</label>
22
+ <input id="email" name="email" type="email" autocomplete="email" placeholder="you@company.com" required value="${it(e.email??"")}"${e.email?"":" autofocus"}>
23
+ ${Ml(e.error)}
24
+ <button class="btn-primary" type="submit">Continue</button>
25
+ </form>
26
+ <p class="small muted">Your company's identity provider if it has one, or a code sent to that address.</p>
18
27
  </div>
19
- <div style="font-size:12px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#8f887e;margin-bottom:22px">SnoutData</div>
20
- <h1 style="margin:0 0 12px;font-size:30px;font-weight:600;letter-spacing:-.01em">${r}</h1>
21
- <p style="color:#8f887e;margin:0;font-size:15px;line-height:1.5">${o}</p>
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
- </div>
24
- </body></html>`}function fs(){let e=Er(ms(32)),t=Er(bl("sha256").update(e).digest());return{verifier:e,challenge:t}}function Kg(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 qg(e){let{command:t,args:n,verbatim:r}=Kg(process.platform,e),o=Gg(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function gs(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Jg(){return"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"}function Vg(e){let t=e??Er(ms(32));return{raw:t,hashed:bl("sha256").update(t).digest("hex")}}function Yg(e){return`${Wg}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:gs(),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 Xg(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 hs(e){return new Promise((t,n)=>{let r="",o=Bg((a,c)=>{let i=Xg(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"?yl("ok"):yl("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."),qg(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 Sl(e,t){let n=await Ve(Ye,_t("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Xe,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 kl(await n.json())}async function Zg(e,t,n){let r=await fetch(zg,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:gs(),client_secret:Jg(),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 Qg(e){if(!gs())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}=fs(),{raw:r,hashed:o}=Vg(),s=Er(ms(16)),{code:a,redirectUri:c}=await hs({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:l=>Yg({redirectUri:l,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Zg(a,t,c),u=await Ve(Ye,_t("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Xe,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 kl(await u.json())}function kl(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return et(n),n}function wl(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 eh(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 th(e,t,n){let r=eh(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(dt(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function nh(e){let t=await Ve(Ye,_t("/sso/start","/sso"),{method:"POST",key:Xe,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 th(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 rh(e){if(e!==void 0){let r=wl(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=Yt();throw new h(`login --sso needs --domain <your work email or company domain>${r?`, because ${Vt(r)}`:""}`)}let t=Hg({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=wl(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 oh(e){let t=await rh(e.domain),{verifier:n,challenge:r}=fs();d(`Signing in with the identity provider for ${M(t)}.`);let{code:o}=await hs({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>nh({domain:t,redirectTo:s,challenge:r})});return Sl(o,n)}function sh(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}async function ih(e,t=sh(),n=fetch){if(!t||!e)return null;let r=Ve(Ye,dl("/rpc/set_my_timezone"),{method:"POST",key:Xe,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"),ah),o.unref()});try{return await Promise.race([r,s])}finally{clearTimeout(o)}}async function Ar(e={}){let t=ys(e.email,await lh(e)),n=await ih(t.accessToken);return n&&process.env.SNOUTDATA_DEBUG&&ce(`could not record the timezone: ${n}`),t}function ys(e,t){if(!e)return t;let n={...t,expectedEmail:e};return et(n),t.email&&!ws(t.email,e)&&ce(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function ws(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function lh(e){if(e.sso)return oh({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return Qg(e);let{verifier:n,challenge:r}=fs(),{code:o}=await hs({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await ul(Ye,_t("/provider/start","/authorize"),Xe));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 Sl(o,n)}function ch(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Wg,zg,ah,Rr=W(()=>{"use strict";pe();br();Te();wt();be();kt();de();Wg="https://accounts.google.com/o/oauth2/v2/auth",zg="https://oauth2.googleapis.com/token";ah=3e3});import{createInterface as dh}from"node:readline/promises";async function bs(e){let t=dh({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 uh(){let e=await fr();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d($("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 ${M(t)}.`),d($("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d($("and you can revoke the token later with `snoutdata tokens revoke`.")),!await bs("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await al(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 et(r),d(`Signed in with "${n.name}".`),r}async function ph(){if(!await bs("Sign in with a browser?"))return null;try{let e=await Ar({});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 mh(){if(!await bs("Sign in by typing a code into a browser on another machine?"))return null;let e=await _r({name:gr(),platform:process.platform});d(""),d(` Open ${M(e.verifyUrl)}`),d(` Type ${M(e.code)}`),d(""),d($("The code is typed rather than in the link on purpose: it is what proves the person")),d($("at the browser is the person at this terminal. Waiting\u2026"));let t=await $r(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return et(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function _l(){if(!Se())return null;let e=await uh();if(e)return e;let t=await ph();return t||mh()}var $l=W(()=>{"use strict";wt();hr();ps();kt();Rr();de()});var Ze={};Li(Ze,{ACCOUNTS_URL:()=>Ye,APP_KEY:()=>Xe,ApiError:()=>Ae,NotSignedIn:()=>vt,call:()=>E,connection:()=>Fe,errorSentence:()=>El,exportStatus:()=>Zt,isSettled:()=>xl,listProjects:()=>qe,listTeams:()=>Or,localStack:()=>mt,noticeFrom:()=>vr,outdated:()=>Pr,requestExport:()=>An,serverCodeOf:()=>Pl,unreadableAnswer:()=>Nr,waitForReady:()=>Tn,waitForSettled:()=>Rn,whoami:()=>It,withKeyNames:()=>Tr});function fh(){let e=Yt();return e?`not signed in, and ${Vt(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 gh(){let e=ir();if(e||(e=await _l()),!e)throw new vt;if(!zi(e)||!e.refreshToken)return e.accessToken;let t=await Ve(Ye,_t("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Xe,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new vt;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return et(r),r.accessToken}async function E(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=mt(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 gh(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await Ve(Ye,wr(e,s),{method:n,key:Xe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":Sr},body:n==="GET"?void 0:JSON.stringify(t)});vr(a);let c=await a.text(),i;try{i=c?JSON.parse(c):{}}catch{throw new Ae(a.status,Nr(a.status,a.statusText,c))}if(!a.ok){let u=El(i)??`${a.status} ${a.statusText}`;throw a.status===401?new vt(u):a.status===410?Pr(a,u):new Ae(a.status,u,Pl(i),i)}return i}function Pl(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 vr(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||vl||(vl=!0,ce(`${t} ${us({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function Pr(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new C("outdated",`${t.replace(/\s+$/,"")} ${us({minimum:n})}`,{status:410,installed:ke,minimum:n??null})}async function It(){return E("cloud-whoami")}async function qe(){return E("cloud-project-list")}async function Fe(e){let t=mt(e);return t?Zi(t):Tr(await E("cloud-project-connection",{ref:e}))}function Tr(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function mt(e){return Qi(e,Wt(process.cwd())?.local??null)}async function Or(){return E("cloud-team-list")}async function An(e){return E("cloud-project-export",{ref:e})}async function Zt(e){return E("cloud-project-export",{ref:e},"GET")}async function Rn(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await qe(),s=o.find(c=>c.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),xl(t,a))return s;if(Date.now()>=r)throw new Ae(504,`${e} is still ${a} after waiting`);await new Promise(c=>setTimeout(c,2e3))}}function xl(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Tn(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await qe(),o=r.find(s=>s.ref===e);if(!o)throw new Ae(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new Ae(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}function El(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 Nr(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 Ye,Xe,Ae,vt,vl,pe=W(()=>{"use strict";wt();$n();be();kt();$l();de();Pn();br();Ye=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Xe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",Ae=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},vt=class extends Error{constructor(t){super(t??fh())}};vl=!1});var ku,SS,_u=W(()=>{"use strict";ku="snoutpod_isolation",SS=`table bridge ${ku}
26
- delete table bridge ${ku}
27
- `});function ba(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function vu(e,t,n){let r="";for(;r.length<n;)r+=ba(e,t);return r}var wa,$u,Pu=W(()=>{"use strict";wa="0123456789abcdefghjkmnpqrstvwxyz",$u="abcdefghjkmnpqrstvwxyz"});function Au(e){return ba(e,xu)+vu(e,wa,Eu-1)}function _S(e){return kS.test(e)}function se(e){if(!_S(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function H(e,t=$S){return se(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var xu,Eu,kS,$S,Me=W(()=>{"use strict";Pu();xu=$u,Eu=13,kS=new RegExp(`^[${xu}][${wa}]{${Eu-1}}$`);$S="db.snoutdata.com"});var Ru=W(()=>{"use strict";Me()});function Ou(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 Sa=W(()=>{"use strict"});function ka(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var _a=W(()=>{"use strict"});var Nu=W(()=>{"use strict"});function Cu(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 ES(e){if(!vS.test(e))throw new Error(`not a keeper name: ${e}`);return`keeper_${e.replace(/-/g,"_")}`}function Mu(e){let t=e.map(r=>`'${ES(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 Du(e){if(e!==""&&!xS.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 RS(e){return typeof e=="string"&&AS.test(e)}function TS(e){return e===ln||RS(e)}function va(e){if(!TS(e))throw new Error(`not an IPv4 address or ${ln}: ${JSON.stringify(e)}`);return`hostssl replication ${an} ${e===ln?ln:`${e}/32`} scram-sha-256`}function Lu(e,t=!0){if(!$a.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 = '${an}') then create role ${an} with login replication; end if; end $$`,`alter role ${an} with login replication noinherit password '${e}'`];return t?[...n,`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${co}' and wal_status = 'lost' and not active`,`select pg_create_physical_replication_slot('${co}', true) where not exists (select 1 from pg_replication_slots where slot_name = '${co}')`]:[...n,Pa]}var vS,PS,At,$a,Iu,uo,xS,an,co,AS,ln,Pa,po=W(()=>{"use strict";Me();vS=/^[a-z0-9][a-z0-9-]*$/,PS=20,At="snoutpod_keeper",$a=/^SCRAM-SHA-256\$[0-9]{4,6}:[A-Za-z0-9+/]+=*\$[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/,Iu=`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 '${PS} seconds')), ''), coalesce((select last_archived_wal from pg_stat_archiver), ''), exists (select 1 from pg_roles where rolname = '${At}' and rolreplication) and exists (select 1 from pg_hba_file_rules where database = '{replication}' and '${At}' = 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`;uo=/^-----BEGIN CERTIFICATE-----\n[A-Za-z0-9+/=\n]{64,12000}-----END CERTIFICATE-----\n$/,xS=/^(?:"[a-z0-9-]+"|(?:ANY|FIRST) [1-9] \("[a-z0-9-]+"(?:, "[a-z0-9-]+")+\))$/;an="snoutpod_replica",co="snoutpod_replica",AS=/^(?:(?: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)$/;ln="samehost";Pa=`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${co}' and not active`});function Uu(e,t){return Bu(e,t,"swap")}function Fu(e,t){return Bu(e,t,"adopt")}function Bu(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 ju,xa,Ea,Aa,Ra=W(()=>{"use strict";ju='snoutpod-pgbackrest archive-get %f "%p"',xa=String.raw`set -u
28
+ ${Cl(e.profile)}`)}function Dr(e){let t=it(e.token);return Lr(`<h1>Check your email</h1>
29
+ <p class="small muted">We sent a code to <strong>${it(e.email)}</strong>.</p>
30
+ <form method="post" action="/code">
31
+ <input type="hidden" name="t" value="${t}">
32
+ <label for="code">Code</label>
33
+ <input id="code" class="code" name="code" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9 ]{6,8}" maxlength="8" required autofocus>
34
+ ${Ml(e.error)}
35
+ ${e.notice?`<p class="small muted" role="status">${it(e.notice)}</p>`:""}
36
+ <button class="btn-primary" type="submit">Sign in</button>
37
+ </form>
38
+ <form method="post" action="/resend">
39
+ <input type="hidden" name="t" value="${t}">
40
+ <button class="link" type="submit">Send another code</button>
41
+ </form>
42
+ <form method="get" action="/">
43
+ <input type="hidden" name="t" value="${t}">
44
+ <button class="link" type="submit">Use another way to sign in</button>
45
+ </form>
46
+ ${Cl(e.profile)}`)}function Dl(e={}){let t=e.email?`<p class="muted">as <strong style="color:var(--fg)">${it(e.email)}</strong></p>`:"",n=e.profile?` --profile ${it(e.profile)}`:"";return Lr(`<div class="badge" style="--badge:var(--live)">${kh}</div>
47
+ <h1>Signed in</h1>
48
+ ${t}
49
+ <p class="small muted where">Your terminal is signed in. Next, try:</p>
50
+ <span class="terminal"><span class="prompt">$ </span>snoutdata projects list${n}</span>
51
+ <p class="small muted">You can close this tab.</p>`,!0)}function jr(e){return Lr(`<div class="badge" style="--badge:var(--error)">${_h}</div>
52
+ <h1>Sign-in failed</h1>
53
+ <p class="muted">${it(e??"Something went wrong.")}</p>
54
+ <span class="terminal"><span class="prompt">$ </span>snoutdata login</span>
55
+ <p class="small muted">Run that in your terminal to try again.</p>`)}var bh,Sh,kh,_h,vh,$h,jl=J(()=>{"use strict";bh="https://snoutdata.com/logo-mark.png",Sh=`
56
+ :root {
57
+ color-scheme: light dark;
58
+ --bg-app: light-dark(#f7f7f4, #0a0c10);
59
+ --bg-card: light-dark(#ffffff, #12161f);
60
+ --bg-input: light-dark(#ffffff, #151a24);
61
+ --bg-hover: light-dark(#f0efe9, #1b212e);
62
+ --border: light-dark(#e5e3da, #232a38);
63
+ --border-soft: light-dark(#edece5, #1a212c);
64
+ --fg: light-dark(#26251e, #f1f4f9);
65
+ --fg-dim: light-dark(#76756d, #8a94a8);
66
+ --btn-bg: light-dark(#26251e, #f1f4f9);
67
+ --btn-fg: light-dark(#f7f7f4, #0a0c10);
68
+ --btn-bg-hover: light-dark(#38362d, #ffffff);
69
+ --btn-secondary: light-dark(#ecebe4, #1b212e);
70
+ --btn-secondary-hover: light-dark(#e3e1d8, #232a38);
71
+ --live: light-dark(#2f9a78, #4fc29c);
72
+ --error: light-dark(#c0392b, #f48771);
73
+ --ring: color-mix(in srgb, var(--fg) 45%, transparent);
74
+ --radius: 12px;
75
+ --radius-xl: 22px;
76
+ --shadow-pop: 0 6px 22px light-dark(rgba(20, 18, 12, 0.1), rgba(0, 0, 0, 0.55)), 0 1px 3px light-dark(rgba(20, 18, 12, 0.06), rgba(0, 0, 0, 0.4));
77
+ --ease: cubic-bezier(0.2, 0, 0, 1);
78
+ --dur: 140ms;
79
+ --font-ui: -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Ubuntu, sans-serif;
80
+ --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
81
+ }
82
+ * { box-sizing: border-box; }
83
+ body {
84
+ margin: 0;
85
+ background: var(--bg-app);
86
+ color: var(--fg);
87
+ font-family: var(--font-ui);
88
+ font-size: 15px;
89
+ line-height: 1.55;
90
+ }
91
+ .gate { display: grid; place-items: center; min-height: 100vh; padding: 20px; }
92
+ .gate-card {
93
+ width: min(400px, 100%);
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 14px;
97
+ background: var(--bg-card);
98
+ border: 1px solid var(--border);
99
+ border-radius: var(--radius-xl);
100
+ box-shadow: var(--shadow-pop);
101
+ padding: 28px;
102
+ text-align: center;
103
+ animation: rise 320ms var(--ease) both;
104
+ }
105
+ .brand-mark { display: block; width: 40px; height: 40px; margin: 0 auto; }
106
+ h1 { margin: 0; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; }
107
+ p { margin: 0; }
108
+ .muted { color: var(--fg-dim); }
109
+ .small { font-size: 13px; }
110
+ .stack { display: flex; flex-direction: column; gap: 10px; }
111
+ button, a.btn {
112
+ font: inherit;
113
+ font-size: 14px;
114
+ font-weight: 500;
115
+ color: var(--fg);
116
+ background: var(--btn-secondary);
117
+ border: 1px solid var(--border-soft);
118
+ border-radius: var(--radius);
119
+ padding: 9px 12px;
120
+ cursor: pointer;
121
+ text-decoration: none;
122
+ transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
123
+ }
124
+ button:where(:hover), a.btn:where(:hover) { background: var(--btn-secondary-hover); }
125
+ button:active, a.btn:active { transform: scale(0.97); }
126
+ button:focus-visible, a:focus-visible, input:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
127
+ .btn-provider { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; }
128
+ .btn-primary { background: var(--btn-bg); border-color: transparent; color: var(--btn-fg); width: 100%; }
129
+ .btn-primary:hover { background: var(--btn-bg-hover); }
130
+ .or { display: flex; align-items: center; gap: 10px; color: var(--fg-dim); font-size: 12px; }
131
+ .or::before, .or::after { content: ''; flex: 1; height: 1px; background: var(--border); }
132
+ form { display: flex; flex-direction: column; gap: 9px; text-align: left; margin: 0; }
133
+ label { font-size: 13px; font-weight: 500; }
134
+ input {
135
+ font-family: var(--font-ui);
136
+ font-size: 14px;
137
+ line-height: 1.5;
138
+ color: var(--fg);
139
+ background: var(--bg-input);
140
+ border: 1px solid var(--border);
141
+ border-radius: var(--radius);
142
+ padding: 10px 12px;
143
+ width: 100%;
144
+ }
145
+ input.code { font-family: var(--font-mono); font-size: 20px; letter-spacing: 0.3em; text-align: center; }
146
+ .error { font-size: 13px; color: var(--error); text-align: left; }
147
+ .link { align-self: flex-start; padding: 0; border: 0; background: none; font-size: 13px; color: var(--fg-dim); text-decoration: underline; }
148
+ .link:hover { background: none; color: var(--fg); }
149
+ .where { border-top: 1px solid var(--border); padding-top: 14px; }
150
+ code, .terminal { font-family: var(--font-mono); font-size: 13px; }
151
+ .terminal {
152
+ display: block;
153
+ text-align: left;
154
+ background: var(--bg-app);
155
+ border: 1px solid var(--border);
156
+ border-radius: var(--radius);
157
+ padding: 12px 14px;
158
+ color: var(--fg);
159
+ font-size: 12.5px;
160
+ overflow-wrap: anywhere;
161
+ }
162
+ .gate-card.wide { width: min(440px, 100%); }
163
+ .terminal .prompt { color: var(--fg-dim); user-select: none; }
164
+ .terminal .ok { color: var(--live); }
165
+ .badge {
166
+ width: 56px;
167
+ height: 56px;
168
+ margin: 2px auto 0;
169
+ border-radius: 50%;
170
+ display: grid;
171
+ place-items: center;
172
+ background: color-mix(in srgb, var(--badge) 14%, transparent);
173
+ color: var(--badge);
174
+ animation: pop 420ms var(--ease) both 80ms;
175
+ }
176
+ .badge svg path { stroke-dasharray: 30; stroke-dashoffset: 30; animation: draw 360ms var(--ease) forwards 300ms; }
177
+ @keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
178
+ @keyframes pop { from { transform: scale(0.6); opacity: 0; } to { transform: none; opacity: 1; } }
179
+ @keyframes draw { to { stroke-dashoffset: 0; } }
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .gate-card, .badge { animation: none; }
182
+ .badge svg path { animation: none; stroke-dashoffset: 0; }
183
+ button, a.btn { transition: none; }
184
+ }
185
+ `;kh='<svg width="26" height="26" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12.5l4.2 4.2L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>',_h='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M7 7l10 10M17 7L7 17" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>';vh='<svg width="17" height="17" viewBox="0 0 18 18" aria-hidden="true"><path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z"/><path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18Z"/><path fill="#FBBC05" d="M3.97 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.01-2.33Z"/><path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.9 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58Z"/></svg>',$h='<svg width="17" height="17" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>'});function Ln(e){Mn=e}function Fl(e){Ul=e}function Ur(){return Mn}function v(e){return Bl?`\x1B[2m${e}\x1B[0m`:e}function L(e){return Bl?`\x1B[1m${e}\x1B[0m`:e}function b(e,t){if(Mn){process.stdout.write(`${JSON.stringify(e,null,2)}
186
+ `);return}t()}function d(e){!Mn&&!Ul&&process.stderr.write(`${e}
187
+ `)}function bt(e,t,n){process.stderr.write(`${t}
188
+ `),Mn&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
189
+ `)}function ue(e){process.stderr.write(`${e}
190
+ `)}function ee(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(`
191
+ `)}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 Mn,Ul,Bl,pe=J(()=>{"use strict";Mn=!1,Ul=!1;Bl=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Gl,randomBytes as Dn}from"node:crypto";import{createServer as Kl}from"node:http";import{createInterface as Ph}from"node:readline/promises";import{spawn as xh}from"node:child_process";function an(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Os(e,t,n){let r=Se()===xe?void 0:Se();return e==="ok"?Dl({email:n,profile:r}):jr(t)}function sn(){let e=an(Dn(32)),t=an(Gl("sha256").update(e).digest());return{verifier:e,challenge:t}}function Eh(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 ql(e){let{command:t,args:n,verbatim:r}=Eh(process.platform,e),o=xh(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function Fr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Th(){return"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"}function zl(e){let t=e??an(Dn(32));return{raw:t,hashed:Gl("sha256").update(t).digest("hex")}}function Wl(e){return`${Ah}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:Fr(),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 Jl(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 Is(e){return new Promise((t,n)=>{let r="",o=Kl((a,c)=>{let i=Jl(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"?Os("ok"):Os("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}
192
+ `),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."),ql(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 Cs(e,t){let n=await He(Ge,at("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Ke,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 Ms(await n.json())}async function Vl(e,t,n){let r=await fetch(Rh,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Fr(),client_secret:Th(),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 Oh(e){if(!Fr())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}=sn(),{raw:r,hashed:o}=zl(),s=an(Dn(16)),{code:a,redirectUri:c}=await Is({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:u=>Wl({redirectUri:u,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Vl(a,t,c);return Yl(i,r)}async function Yl(e,t){let n=await He(Ge,at("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Ke,headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:e,nonce:t})});if(!n.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await n.text()).slice(0,200)}`);return Ms(await n.json())}function Ms(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ot(n),n}function Ns(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 Ls(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 Nh(e,t,n){let r=Ls(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(yt(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Xl(e){let t=await He(Ge,at("/sso/start","/sso"),{method:"POST",key:Ke,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 Nh(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 Ih(e){if(e!==void 0){let r=Ns(e);if(!r)throw new y(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!_e()){let r=rn();throw new y(`login --sso needs --domain <your work email or company domain>${r?`, because ${nn(r)}`:""}`)}let t=Ph({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new y("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=Ns(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 y("no domain to sign in with: pass --domain <your work email or company domain>")}async function Ch(e){let t=await Ih(e.domain),{verifier:n,challenge:r}=sn();d(`Signing in with the identity provider for ${L(t)}.`);let{code:o}=await Is({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Xl({domain:t,redirectTo:s,challenge:r})});return Cs(o,n)}function Mh(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}async function Dh(e,t=Mh(),n=fetch){if(!t||!e)return null;let r=He(Ge,Tl("/rpc/set_my_timezone"),{method:"POST",key:Ke,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"),Lh),o.unref()});try{return await Promise.race([r,s])}finally{clearTimeout(o)}}async function jh(e){let t="";for await(let n of e)if(t+=String(n),t.length>4096)break;return new URLSearchParams(t)}async function Hl(e){let t=await He(Ge,at("/code","/otp"),{method:"POST",key:Ke,headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,create_user:!0})});return t.ok?null:Ls(await t.text())||"Could not send a code just now. Try again in a moment."}async function Uh(e,t){let n="";for(let o of["email","signup"]){let s=await He(Ge,at("/confirm","/verify"),{method:"POST",key:Ke,headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,token:t,type:o})}),a=await s.text();if(s.ok)return Ms(JSON.parse(a));if(n=a,s.status!==403&&!/otp_expired|expired|invalid/i.test(a))break}let r=Ls(n);return/expired|invalid/i.test(r)||!r?"That code did not work. It may be from an older email, or already used. Send another.":r}async function Fh(e){let t=an(Dn(18)),n=Se()===xe?void 0:Se(),r=!!Fr(),o=null,s=null;return new Promise((a,c)=>{let i="",u=!1,l=(F,H,M=200)=>{F.writeHead(M,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),F.end(H)},m=(F,H)=>{u||(u=!0,l(F,H instanceof Error?jr(H.message):Os("ok",void 0,H.email)),E.close(),H instanceof Error?c(H):a(H))},h=(F,H={})=>l(F,Ll({token:t,google:r,profile:n,email:H.email??e.email,error:H.error})),A=async(F,H)=>{let M=new URL(F.url??"/","http://127.0.0.1"),de=M.searchParams.get("t")===t;if((M.pathname==="/"||M.pathname==="/callback")&&(M.searchParams.has("code")||M.searchParams.has("error"))){let G=o?.kind==="google"&&M.pathname==="/"?o.state:void 0,oe=Jl(F.url??"/",G);if(oe.kind==="ignore"){H.writeHead(404).end();return}if(oe.kind==="error"||!o){m(H,new Error(oe.kind==="error"?oe.message:"the browser came back to a sign-in that was not started"));return}try{if(o.kind==="google"){let Z=await Vl(oe.code,o.verifier,i);m(H,await Yl(Z,o.rawNonce))}else m(H,await Cs(oe.code,o.verifier))}catch(Z){m(H,Z instanceof Error?Z:new Error(String(Z)))}return}if(F.method==="GET"&&M.pathname==="/"&&de){h(H);return}if(F.method==="GET"&&M.pathname==="/google"&&de&&r){let{verifier:G,challenge:oe}=sn(),{raw:Z,hashed:me}=zl(),rt=an(Dn(16));o={kind:"google",verifier:G,rawNonce:Z,state:rt},H.writeHead(302,{Location:Wl({redirectUri:i,challenge:oe,hashedNonce:me,state:rt,loginHint:e.email})}).end();return}if(F.method==="GET"&&M.pathname==="/github"&&de){let{verifier:G,challenge:oe}=sn();o={kind:"pkce",verifier:G},H.writeHead(302,{Location:await Zl("github",`${i}/callback`,oe)}).end();return}if(F.method==="POST"){let G=await jh(F);if(G.get("t")!==t){H.writeHead(403).end();return}if(M.pathname==="/email"){let oe=(G.get("email")??"").trim().toLowerCase(),Z=Ns(oe);if(!Z||!oe.includes("@")){h(H,{email:oe,error:"Enter your work email, for example you@company.com."});return}let{verifier:me,challenge:rt}=sn();try{let Je=await Xl({domain:Z,redirectTo:`${i}/callback`,challenge:rt});o={kind:"pkce",verifier:me},H.writeHead(302,{Location:Je}).end();return}catch(Je){if(!(Je instanceof C&&Je.code==="not-found")){h(H,{email:oe,error:Je instanceof Error?Je.message:String(Je)});return}}let ht=await Hl(oe);if(ht){h(H,{email:oe,error:ht});return}s=oe,l(H,Dr({token:t,email:oe,profile:n}));return}if(M.pathname==="/code"&&s){let oe=(G.get("code")??"").replace(/\s+/g,""),Z=await Uh(s,oe);if(typeof Z=="string"){l(H,Dr({token:t,email:s,profile:n,error:Z}));return}m(H,Z);return}if(M.pathname==="/resend"&&s){let oe=await Hl(s);l(H,Dr({token:t,email:s,profile:n,...oe?{error:oe}:{notice:"Another code is on its way."}}));return}}H.writeHead(404).end()},E=Kl((F,H)=>{A(F,H).catch(M=>{H.headersSent||l(H,jr(M instanceof Error?M.message:String(M)),500)})});E.listen(0,"127.0.0.1",()=>{let F=E.address();i=`http://127.0.0.1:${typeof F=="object"&&F?F.port:0}`;let M=`${i}/?t=${t}`;process.stdout.write(`${M}
193
+ `),e.noBrowser?d("Open that in a browser on this machine and sign in."):(d("Opening the sign-in page in your browser. If nothing opens, paste that into one."),ql(M))}),E.on("error",c),setTimeout(()=>{u||(u=!0,E.close(),c(new Error("timed out waiting for the browser")))},e.timeoutMs??3e5).unref()})}async function Br(e={}){let t=Ds(e.email,await Bh(e)),n=await Dh(t.accessToken);return n&&process.env.SNOUTDATA_DEBUG&&ue(`could not record the timezone: ${n}`),t}function Ds(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ot(n),t.email&&!js(t.email,e)&&ue(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function js(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Bh(e){if(e.sso)return Ch({...e,domain:e.domain??e.email});if(!e.provider)return Fh(e);let t=e.provider;if(t==="google")return Oh(e);let{verifier:n,challenge:r}=sn(),{code:o}=await Is({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Zl(t,s,r)});return Cs(o,n)}async function Zl(e,t,n){let r=new URL(await Ol(Ge,at("/provider/start","/authorize"),Ke));return r.searchParams.set("provider",e),r.searchParams.set("redirect_to",t),r.searchParams.set("code_challenge",n),r.searchParams.set("code_challenge_method","s256"),r.searchParams.set("prompt","select_account"),r.toString()}var Ah,Rh,Lh,Hr=J(()=>{"use strict";fe();Rr();Te();Et();jl();ke();Tt();pe();Ah="https://accounts.google.com/o/oauth2/v2/auth",Rh="https://oauth2.googleapis.com/token";Lh=3e3});import{createInterface as Hh}from"node:readline/promises";async function Us(e){let t=Hh({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 Gh(){let e=await $r();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=Se();if(d(`SnoutData Studio is open here, signed in as ${L(t)}.`),n!==xe&&d(`Its token would be kept as profile ${L(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 Us("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let r=await xl(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 ot(o),d(`Signed in with "${r.name}".`),o}async function Kh(){if(!await Us("Sign in with a browser?"))return null;try{let e=await Br({});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 qh(){if(!await Us("Sign in by typing a code into a browser on another machine?"))return null;let e=await Nr({name:Pr(),platform:process.platform});d(""),d(` Open ${L(e.verifyUrl)}`),d(` Type ${L(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 Ir(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ot(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Ql(){if(!_e())return null;let e=await Gh();if(e)return e;let t=await Kh();return t||qh()}var ec=J(()=>{"use strict";Et();xr();Ts();Tt();Hr();pe()});var tt={};rl(tt,{ACCOUNTS_URL:()=>Ge,APP_KEY:()=>Ke,ApiError:()=>Oe,NotSignedIn:()=>Ot,call:()=>T,connection:()=>qe,errorSentence:()=>oc,exportStatus:()=>ln,isSettled:()=>rc,listProjects:()=>Xe,listTeams:()=>Kr,localStack:()=>kt,noticeFrom:()=>Cr,outdated:()=>Mr,requestExport:()=>jn,serverCodeOf:()=>nc,unreadableAnswer:()=>qr,waitForReady:()=>Fn,waitForSettled:()=>Un,whoami:()=>Bt,withKeyNames:()=>Gr});function zh(){let e=rn(),t=Se(),n=t===xe?"":` as profile ${t}`;return e?`not signed in${n}, and ${nn(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===xe?"":` --profile ${t}`}\`, or set SNOUTDATA_ACCESS_TOKEN`}async function Wh(){let e=yr();if(e||(e=await Ql()),!e)throw new Ot;if(!ml(e)||!e.refreshToken)return e.accessToken;let t=await He(Ge,at("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Ke,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Ot;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ot(r),r.accessToken}async function T(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=kt(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 Wh(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await He(Ge,Ar(e,s),{method:n,key:Ke,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":Tr},body:n==="GET"?void 0:JSON.stringify(t)});Cr(a);let c=await a.text(),i;try{i=c?JSON.parse(c):{}}catch{throw new Oe(a.status,qr(a.status,a.statusText,c))}if(!a.ok){let u=oc(i)??`${a.status} ${a.statusText}`;throw a.status===401?new Ot(u):a.status===410?Mr(a,u):new Oe(a.status,u,nc(i),i)}return i}function nc(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 Cr(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||tc||(tc=!0,ue(`${t} ${Rs({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function Mr(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new C("outdated",`${t.replace(/\s+$/,"")} ${Rs({minimum:n})}`,{status:410,installed:ve,minimum:n??null})}async function Bt(){return T("cloud-whoami")}async function Xe(){return T("cloud-project-list")}async function qe(e){let t=kt(e);return t?wl(t):Gr(await T("cloud-project-connection",{ref:e}))}function Gr(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function kt(e){return bl(e,Ft(process.cwd())?.local??null)}async function Kr(){return T("cloud-team-list")}async function jn(e){return T("cloud-project-export",{ref:e})}async function ln(e){return T("cloud-project-export",{ref:e},"GET")}async function Un(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await Xe(),s=o.find(c=>c.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),rc(t,a))return s;if(Date.now()>=r)throw new Oe(504,`${e} is still ${a} after waiting`);await new Promise(c=>setTimeout(c,2e3))}}function rc(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Fn(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await Xe(),o=r.find(s=>s.ref===e);if(!o)throw new Oe(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new Oe(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}function oc(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 qr(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 Ge,Ke,Oe,Ot,tc,fe=J(()=>{"use strict";Et();Nn();ke();Tt();ec();pe();Cn();Rr();Ge=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Ke=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",Oe=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Ot=class extends Error{constructor(t){super(t??zh())}};tc=!1});var Zu,ZS,Qu=J(()=>{"use strict";Zu="snoutpod_isolation",ZS=`table bridge ${Zu}
194
+ delete table bridge ${Zu}
195
+ `});function Ua(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function tp(e,t,n){let r="";for(;r.length<n;)r+=Ua(e,t);return r}var ja,ep,np=J(()=>{"use strict";ja="0123456789abcdefghjkmnpqrstvwxyz",ep="abcdefghjkmnpqrstvwxyz"});function sp(e){return Ua(e,rp)+tp(e,ja,op-1)}function ek(e){return QS.test(e)}function ie(e){if(!ek(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function z(e,t=tk){return ie(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var rp,op,QS,tk,De=J(()=>{"use strict";np();rp=ep,op=13,QS=new RegExp(`^[${rp}][${ja}]{${op-1}}$`);tk="db.snoutdata.com"});var ap=J(()=>{"use strict";De()});function lp(e,t){let n=ip(e,7),r=ip(t,24);return Math.max(2,Math.ceil(n*24/r))}function ip(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Fa=J(()=>{"use strict"});function Ba(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Ha=J(()=>{"use strict"});var cp=J(()=>{"use strict"});function up(e){let n=(e.trim().split(`
196
+ `)[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 sk(e){if(!nk.test(e))throw new Error(`not a keeper name: ${e}`);return`keeper_${e.replace(/-/g,"_")}`}function pp(e){let t=e.map(r=>`'${sk(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 mp(e){if(e!==""&&!ok.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 ik(e){return typeof e=="string"&&ak.test(e)}function lk(e){return e===yn||ik(e)}function Ka(e){if(!lk(e))throw new Error(`not an IPv4 address or ${yn}: ${JSON.stringify(e)}`);return`hostssl replication ${hn} ${e===yn?yn:`${e}/32`} scram-sha-256`}function fp(e,t=!0){if(!Ga.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 = '${hn}') then create role ${hn} with login replication; end if; end $$`,`alter role ${hn} with login replication noinherit password '${e}'`];return t?[...n,`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${vo}' and wal_status = 'lost' and not active`,`select pg_create_physical_replication_slot('${vo}', true) where not exists (select 1 from pg_replication_slots where slot_name = '${vo}')`]:[...n,qa]}var nk,rk,Mt,Ga,dp,$o,ok,hn,vo,ak,yn,qa,Po=J(()=>{"use strict";De();nk=/^[a-z0-9][a-z0-9-]*$/,rk=20,Mt="snoutpod_keeper",Ga=/^SCRAM-SHA-256\$[0-9]{4,6}:[A-Za-z0-9+/]+=*\$[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/,dp=`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 '${rk} seconds')), ''), coalesce((select last_archived_wal from pg_stat_archiver), ''), exists (select 1 from pg_roles where rolname = '${Mt}' and rolreplication) and exists (select 1 from pg_hba_file_rules where database = '{replication}' and '${Mt}' = 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`;$o=/^-----BEGIN CERTIFICATE-----\n[A-Za-z0-9+/=\n]{64,12000}-----END CERTIFICATE-----\n$/,ok=/^(?:"[a-z0-9-]+"|(?:ANY|FIRST) [1-9] \("[a-z0-9-]+"(?:, "[a-z0-9-]+")+\))$/;hn="snoutpod_replica",vo="snoutpod_replica",ak=/^(?:(?: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)$/;yn="samehost";qa=`select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = '${vo}' and not active`});function hp(e,t){return wp(e,t,"swap")}function yp(e,t){return wp(e,t,"adopt")}function wp(e,t,n){let r=null,o=!1;for(let s of e.split(`
197
+ `)){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 gp,za,Wa,Ja,Va=J(()=>{"use strict";gp='snoutpod-pgbackrest archive-get %f "%p"',za=String.raw`set -u
30
198
  A=$1
31
199
  B=$2
32
200
  fail() { echo "swap-error $*"; exit 3; }
@@ -45,9 +213,9 @@ kept=""
45
213
  if [ -f "$C" ]; then
46
214
  kept=$(grep -v -E '^[[:space:]]*(primary_conninfo|primary_slot_name|restore_command|synchronous_standby_names)[[:space:]]*=' "$C")
47
215
  fi
48
- { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+ju.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
216
+ { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+gp.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
49
217
  echo "swap-done"
50
- `;Ea=String.raw`set -u
218
+ `;Wa=String.raw`set -u
51
219
  A=$1
52
220
  B=$2
53
221
  fail() { echo "adopt-error $*"; exit 3; }
@@ -74,13 +242,13 @@ kept=""
74
242
  if [ -f "$C" ]; then
75
243
  kept=$(grep -v -E '^[[:space:]]*(primary_conninfo|primary_slot_name|restore_command|synchronous_standby_names)[[:space:]]*=' "$C")
76
244
  fi
77
- { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+ju.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
245
+ { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+gp.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
78
246
  echo "adopt-done"
79
- `;Aa=["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 Ku(e){return`*:*:*:snoutpod_replica:${e.replace(/[\\:]/g,"\\$&")}
80
- `}var Hu,Gu,yx,Ta=W(()=>{"use strict";Ra();po();Hu="/tmp/snoutpod/replica.pgpass",Gu="/tmp/snoutpod/primary.crt";yx=Buffer.from([0,0,0,8,4,210,22,47])});import{createHash as OS,createHmac as qu,pbkdf2Sync as NS,randomBytes as Sx}from"node:crypto";function Oa(e,t){let n=IS.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=NS(t.normalize("NFKC"),Buffer.from(o,"base64"),c,32,"sha256"),u=qu("sha256",i).update("Client Key").digest(),l=OS("sha256").update(u).digest(),m=qu("sha256",i).update("Server Key").digest();return l.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var IS,Na=W(()=>{"use strict";IS=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function Wu(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 Ju(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 zu,Ia=W(()=>{"use strict";zu='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 MS(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function Yu(e){let[t,n]=(e.trim().split(`
82
- `)[0]??"").split("|");return t==="t"&&n!==void 0&&MS(n)?n:null}var CS,$x,Vu,Ca=W(()=>{"use strict";CS="/var/lib/postgresql/data",$x=`set -u
83
- D=${CS}
247
+ `;Ja=["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 kp(e){return`*:*:*:snoutpod_replica:${e.replace(/[\\:]/g,"\\$&")}
248
+ `}var bp,Sp,Zx,Ya=J(()=>{"use strict";Va();Po();bp="/tmp/snoutpod/replica.pgpass",Sp="/tmp/snoutpod/primary.crt";Zx=Buffer.from([0,0,0,8,4,210,22,47])});import{createHash as ck,createHmac as _p,pbkdf2Sync as dk,randomBytes as tE}from"node:crypto";function Xa(e,t){let n=uk.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=dk(t.normalize("NFKC"),Buffer.from(o,"base64"),c,32,"sha256"),u=_p("sha256",i).update("Client Key").digest(),l=ck("sha256").update(u).digest(),m=_p("sha256",i).update("Server Key").digest();return l.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var uk,Za=J(()=>{"use strict";uk=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function vp(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 Pp(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(`
249
+ `)){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 $p,Qa=J(()=>{"use strict";$p='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 mk(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function Ep(e){let[t,n]=(e.trim().split(`
250
+ `)[0]??"").split("|");return t==="t"&&n!==void 0&&mk(n)?n:null}var pk,oE,xp,ei=J(()=>{"use strict";pk="/var/lib/postgresql/data",oE=`set -u
251
+ D=${pk}
84
252
  W="$D/pg_wal"
85
253
  fail() { echo "salvage-error $*"; exit 3; }
86
254
  [ -d "$W" ] || fail "the volume has no pg_wal"
@@ -119,27 +287,27 @@ for s in $segs; do
119
287
  echo "salvage-pushed $s"
120
288
  done
121
289
  echo "salvage-done"
122
- `;Vu="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var xx,Xu=W(()=>{"use strict";Me();xx=7*24});var Ax,Zu=W(()=>{"use strict";Ax=7*24*60*60*1e3});var DS,Qu=W(()=>{"use strict";Zu();DS=60*60*1e3});var ep=W(()=>{"use strict"});var Ma,Mx,tp=W(()=>{"use strict";Ma="snoutdata_realtime",Mx=`do $snoutpod$
290
+ `;xp="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var iE,Ap=J(()=>{"use strict";De();iE=7*24});var cE,Rp=J(()=>{"use strict";cE=7*24*60*60*1e3});var fk,Tp=J(()=>{"use strict";Rp();fk=60*60*1e3});var Op=J(()=>{"use strict"});var ti,hE,Np=J(()=>{"use strict";ti="snoutdata_realtime",hE=`do $snoutpod$
123
291
  begin
124
- if exists (select 1 from pg_publication where pubname = '${Ma}') then
292
+ if exists (select 1 from pg_publication where pubname = '${ti}') then
125
293
  return;
126
294
  end if;
127
- create publication ${Ma};
128
- execute format('alter publication ${Ma} owner to %I',
295
+ create publication ${ti};
296
+ execute format('alter publication ${ti} owner to %I',
129
297
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
130
298
  end
131
299
  $snoutpod$;
132
- `});var Lx,jx,np=W(()=>{"use strict";Lx=2*1024*1024,jx=64*1024});var rp=W(()=>{"use strict"});function ho(e,t={name:xe,subnet:fo,gateway:ip}){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(mo(Lt("a peer",r,qn),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 lp(e){if(!La.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);return`${go}${e}`}function yo(e){let t=e.startsWith(go)?e.slice(go.length):"";return La.test(t)?t:null}function jt(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 ja(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 cp(e,t){if(!La.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);let n=Lt("a cluster peer",t,qn).split(".").map(Number);return`${go}${e}-${n.map(r=>r.toString(16).padStart(2,"0")).join("")}`}function cn(e){let t=LS.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 Ua(e){return yo(e)!==null||cn(e)!==null}function jS(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(!jt(r.subnet))throw new Error(`not a peer network's subnet: ${r.subnet}`);if(Lt("a peer",r.peer,qn),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:Da,to:"0.0.0.0/0",match:"all",verdict:"drop"},{from:"0.0.0.0/0",to:Da,match:"all",verdict:"drop"}),t}function dp(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===Da?`ip saddr != ${fo}`:`ip daddr != ${fo}`:"",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 ${Kn}`,`delete table inet ${Kn}`,`table inet ${Kn} {`," chain forward {"," type filter hook forward priority 0; policy accept;",...jS(e).map(t)," }","}",""].join(`
133
- `)}function wo(e,t,n={subnet:fo,gateway:ip}){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 bo(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 Lt(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function So(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 US(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 US(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(Lt("the subnet",n,op),Lt("the sink",r,qn),!mo(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!mo(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",`${Lt("a denied prefix",s,op)},${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(mo(Lt("a peer",s,qn),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 sp(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function mo(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(sp(e)&s)>>>0===(sp(n??"")&s)>>>0}function pp(e){return e?.internal===!0}var ap,xe,fo,ip,go,La,LS,Da,Kn,up,op,qn,ko=W(()=>{"use strict";ap=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],xe="snoutpeer",fo="10.96.0.0/24",ip="10.96.0.1";go=`${xe}-`,La=/^[a-z][0-9a-z]{12}$/;LS=/^snoutpeer-([a-z][0-9a-z]{12})-([0-9a-f]{8})$/;Da="10.96.0.0/16";Kn="snoutpod_peers";up=`table inet ${Kn}
134
- delete table inet ${Kn}
135
- `;op=/^((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])$/,qn=/^((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 FS,_o=W(()=>{"use strict";FS=7*24*60*60});function KS(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 HS,GS,tE,mp=W(()=>{"use strict";Me();np();rp();ko();_o();HS="10.93.0.0/24",GS="10.93.0.1",tE=[...ap,"100.64.0.0/10",...KS(HS,GS)]});var fp=W(()=>{"use strict"});import{createHash as qS}from"node:crypto";function ht(e){return`${H(e).pod}-${ge}`}function st(e){return e.layout==="separated"}function Ba(e){return st(e)?[]:e.services??[]}function Ha(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+gp*1e3,admin:e+gp*1e3+xo,passthrough:e+WS*1e3};if(t.passthrough>65535)throw new Error(`routerPodPort ${e} puts the router's ports past 65535`);return t}function hp(e,t){let n={...t};return delete n.LEPIS_HOMES,n.LEPIS_HOME=`${H(se(e)).pod}:5432`,n.LEPIS_PASSTHROUGH_PORT=String(dn),n}function yp(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 Wn(e){return`${H(e).pod}-lepis-tls`}function JS(e){return qS("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Ga(e,t){let n={...e},r=n[$o],o=n[vo];delete n[$o],delete n[vo];for(let[s,a,c]of[[$o,r,t],[vo,o,JS(t)]])if(a!==void 0){if(!zS.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=c}return n}var ge,$o,vo,Po,xo,Fa,gp,lE,dn,WS,Rt,Ne,Eo,wp,zS,Ao=W(()=>{"use strict";Me();fp();ge="lepis",$o="SNOUTPOD_ADMIN_PASSWORD_ENV",vo="SNOUTPOD_ADMIN_TOKEN_ENV",Po=7433,xo=1e3,Fa=7432,gp=4,lE={LEPIS_HOST:"0.0.0.0",LEPIS_PORT:String(Fa),LEPIS_HOME:"127.0.0.1:5432",LEPIS_HOME_SSLMODE:"disable",LEPIS_SERVICE_USER:"snoutpod_admin",[$o]:"LEPIS_SERVICE_PASSWORD",LEPIS_ADMIN_ADDR:`0.0.0.0:${Po}`,LEPIS_TLS_CERT:"/tls/cert.pem",LEPIS_TLS_KEY:"/tls/key.pem",[vo]:"LEPIS_ADMIN_TOKEN",LEPIS_LOG:"info"},dn=7434,WS=7,Rt="snoutpod.lepis";Ne="snoutpod_router";Eo="/tls",wp=["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
- `),zS=/^[A-Z][A-Z0-9_]{0,63}$/});function Ka(e){return bp.includes(e)}async function qa(e,t){if(!Ka(t))throw new Error(`no password is derived for role ${JSON.stringify(t)}: only ${bp.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 bp,Wa=W(()=>{"use strict";bp=["snout_gate","snout_auth_admin","snout_storage_admin","snout_realtime_admin","snout_psyche_admin","snout_push_admin","snoutpod_router"]});var gE,hE,Sp=W(()=>{"use strict";Me();_o();gE=6*60*60,hE=5*60});var kp=W(()=>{"use strict"});var _p=W(()=>{"use strict"});var SE,$p=W(()=>{"use strict";SE=15*6e4});var PE,vp=W(()=>{"use strict";$p();PE=2*1024**3});var Pp=W(()=>{"use strict";_p();vp()});function Ep(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"||!YS.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!VS.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 Ap(e,t){return`${e}.nspname !~ '^pg_'
139
- and ${e}.nspname <> all (array[${xp.map(n=>`'${n}'`).join(", ")}]::text[])
300
+ `});var wE,bE,Ip=J(()=>{"use strict";wE=2*1024*1024,bE=64*1024});var Cp=J(()=>{"use strict"});function Ro(e,t={name:Ae,subnet:Eo,gateway:jp}){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(xo(qt("a peer",r,tr),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 Up(e){if(!ri.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);return`${Ao}${e}`}function To(e){let t=e.startsWith(Ao)?e.slice(Ao.length):"";return ri.test(t)?t:null}function zt(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 oi(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 Fp(e,t){if(!ri.test(e))throw new Error(`not a project's group: ${JSON.stringify(e)}`);let n=qt("a cluster peer",t,tr).split(".").map(Number);return`${Ao}${e}-${n.map(r=>r.toString(16).padStart(2,"0")).join("")}`}function wn(e){let t=gk.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 si(e){return To(e)!==null||wn(e)!==null}function hk(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(!zt(r.subnet))throw new Error(`not a peer network's subnet: ${r.subnet}`);if(qt("a peer",r.peer,tr),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:ni,to:"0.0.0.0/0",match:"all",verdict:"drop"},{from:"0.0.0.0/0",to:ni,match:"all",verdict:"drop"}),t}function Bp(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===ni?`ip saddr != ${Eo}`:`ip daddr != ${Eo}`:"",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 ${er}`,`delete table inet ${er}`,`table inet ${er} {`," chain forward {"," type filter hook forward priority 0; policy accept;",...hk(e).map(t)," }","}",""].join(`
301
+ `)}function Oo(e,t,n={subnet:Eo,gateway:jp}){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 No(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 qt(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Io(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 yk(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 yk(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(qt("the subnet",n,Mp),qt("the sink",r,tr),!xo(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!xo(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",`${qt("a denied prefix",s,Mp)},${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(xo(qt("a peer",s,tr),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 Lp(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function xo(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Lp(e)&s)>>>0===(Lp(n??"")&s)>>>0}function Gp(e){return e?.internal===!0}var Dp,Ae,Eo,jp,Ao,ri,gk,ni,er,Hp,Mp,tr,Co=J(()=>{"use strict";Dp=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],Ae="snoutpeer",Eo="10.96.0.0/24",jp="10.96.0.1";Ao=`${Ae}-`,ri=/^[a-z][0-9a-z]{12}$/;gk=/^snoutpeer-([a-z][0-9a-z]{12})-([0-9a-f]{8})$/;ni="10.96.0.0/16";er="snoutpod_peers";Hp=`table inet ${er}
302
+ delete table inet ${er}
303
+ `;Mp=/^((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])$/,tr=/^((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 wk,Mo=J(()=>{"use strict";wk=7*24*60*60});function _k(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 Sk,kk,LE,Kp=J(()=>{"use strict";De();Ip();Cp();Co();Mo();Sk="10.93.0.0/24",kk="10.93.0.1",LE=[...Dp,"100.64.0.0/10",..._k(Sk,kk)]});var qp=J(()=>{"use strict"});import{createHash as vk}from"node:crypto";function $t(e){return`${z(e).pod}-${he}`}function pt(e){return e.layout==="separated"}function ii(e){return pt(e)?[]:e.services??[]}function li(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+zp*1e3,admin:e+zp*1e3+Uo,passthrough:e+$k*1e3};if(t.passthrough>65535)throw new Error(`routerPodPort ${e} puts the router's ports past 65535`);return t}function Wp(e,t){let n={...t};return delete n.LEPIS_HOMES,n.LEPIS_HOME=`${z(ie(e)).pod}:5432`,n.LEPIS_PASSTHROUGH_PORT=String(bn),n}function Jp(e,t){let n=ie(e);return t?["\\getenv router_password SNOUTPOD_ROUTER_PASSWORD",`do $$ begin if not exists (select from pg_roles where rolname = '${Ce}') then create role ${Ce}; end if; end $$;`,`alter role ${Ce} with login noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls connection limit 64 password :'router_password';`,`create schema if not exists ${Ce} authorization snoutpod_admin;`,`create or replace function ${Ce}.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 ${Ce} from public;`,`revoke all on function ${Ce}.verifier(text) from public;`,`grant usage on schema ${Ce} to ${Ce};`,`grant execute on function ${Ce}.verifier(text) to ${Ce};`,`grant connect on database "${n}" to ${Ce};`,""].join(`
304
+ `):[`do $$ begin if exists (select from pg_roles where rolname = '${Ce}') then alter role ${Ce} nologin; end if; end $$;`,""].join(`
305
+ `)}function nr(e){return`${z(e).pod}-lepis-tls`}function xk(e){return vk("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function ci(e,t){let n={...e},r=n[Lo],o=n[Do];delete n[Lo],delete n[Do];for(let[s,a,c]of[[Lo,r,t],[Do,o,xk(t)]])if(a!==void 0){if(!Pk.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=c}return n}var he,Lo,Do,jo,Uo,ai,zp,GE,bn,$k,Lt,Ce,Fo,Vp,Pk,Bo=J(()=>{"use strict";De();qp();he="lepis",Lo="SNOUTPOD_ADMIN_PASSWORD_ENV",Do="SNOUTPOD_ADMIN_TOKEN_ENV",jo=7433,Uo=1e3,ai=7432,zp=4,GE={LEPIS_HOST:"0.0.0.0",LEPIS_PORT:String(ai),LEPIS_HOME:"127.0.0.1:5432",LEPIS_HOME_SSLMODE:"disable",LEPIS_SERVICE_USER:"snoutpod_admin",[Lo]:"LEPIS_SERVICE_PASSWORD",LEPIS_ADMIN_ADDR:`0.0.0.0:${jo}`,LEPIS_TLS_CERT:"/tls/cert.pem",LEPIS_TLS_KEY:"/tls/key.pem",[Do]:"LEPIS_ADMIN_TOKEN",LEPIS_LOG:"info"},bn=7434,$k=7,Lt="snoutpod.lepis";Ce="snoutpod_router";Fo="/tls",Vp=["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(`
306
+ `),Pk=/^[A-Z][A-Z0-9_]{0,63}$/});function di(e){return Yp.includes(e)}async function ui(e,t){if(!di(t))throw new Error(`no password is derived for role ${JSON.stringify(t)}: only ${Yp.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 Yp,pi=J(()=>{"use strict";Yp=["snout_gate","snout_auth_admin","snout_storage_admin","snout_realtime_admin","snout_psyche_admin","snout_push_admin","snoutpod_router"]});var YE,XE,Xp=J(()=>{"use strict";De();Mo();YE=6*60*60,XE=5*60});var Zp=J(()=>{"use strict"});var Qp=J(()=>{"use strict"});var tA,em=J(()=>{"use strict";tA=15*6e4});var aA,tm=J(()=>{"use strict";em();aA=2*1024**3});var nm=J(()=>{"use strict";Qp();tm()});function om(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"||!Ak.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Ek.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 sm(e,t){return`${e}.nspname !~ '^pg_'
307
+ and ${e}.nspname <> all (array[${rm.map(n=>`'${n}'`).join(", ")}]::text[])
140
308
  and not ${t}.rolsuper
141
- and ${t}.rolname !~ '${XS.source}'`}function Tp(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 Op(e,t){return se(e),JSON.stringify(Ep(t).map(n=>({role:n.role,level:n.level})))}var VS,un,YS,ae,xp,XS,To,ZS,Ro,QS,Rp,za=W(()=>{"use strict";Me();VS=["full","read"],un="snout_oauth",YS=/^oauth_[0-9a-f]{32}$/,ae="snout_oauth_read",xp=["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"],XS=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;To="SNOUTPOD_DB_ACCESS",ZS=/^[a-z_][a-z0-9_]*$/;for(let e of xp)if(!ZS.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Ro="snoutpod_oauth_read_new_schema",QS=`do $home$
309
+ and ${t}.rolname !~ '${Rk.source}'`}function im(e){let t=[];for(let n of e.split(`
310
+ `)){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 lm(e,t){return ie(e),JSON.stringify(om(t).map(n=>({role:n.role,level:n.level})))}var Ek,Sn,Ak,le,rm,Rk,Go,Tk,Ho,Ok,am,mi=J(()=>{"use strict";De();Ek=["full","read"],Sn="snout_oauth",Ak=/^oauth_[0-9a-f]{32}$/,le="snout_oauth_read",rm=["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"],Rk=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;Go="SNOUTPOD_DB_ACCESS",Tk=/^[a-z_][a-z0-9_]*$/;for(let e of rm)if(!Tk.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Ho="snoutpod_oauth_read_new_schema",Ok=`do $home$
143
311
  declare
144
312
  -- snout_ext in the own layout, extensions in a database not yet renamed (docs/cloud/OWN-LAYOUT.md).
145
313
  home text := case
@@ -148,7 +316,7 @@ declare
148
316
  end;
149
317
  begin
150
318
  execute pg_catalog.format('create schema if not exists %I', home);
151
- execute pg_catalog.replace($fn$create or replace function @home@.${Ro}() returns event_trigger
319
+ execute pg_catalog.replace($fn$create or replace function @home@.${Ho}() returns event_trigger
152
320
  language plpgsql
153
321
  security invoker
154
322
  set search_path = pg_catalog, pg_temp
@@ -157,7 +325,7 @@ declare
157
325
  created record;
158
326
  owner_role text := pg_catalog.current_database() || '_owner';
159
327
  begin
160
- if not exists (select from pg_catalog.pg_roles where rolname = '${ae}') then
328
+ if not exists (select from pg_catalog.pg_roles where rolname = '${le}') then
161
329
  return;
162
330
  end if;
163
331
  for created in
@@ -168,29 +336,29 @@ begin
168
336
  where c.command_tag = 'CREATE SCHEMA'
169
337
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
170
338
  and not c.in_extension
171
- and ${Ap("n","r")}
339
+ and ${sm("n","r")}
172
340
  loop
173
341
  begin
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);
342
+ execute pg_catalog.format('grant usage on schema %I to ${le}', created.nspname);
343
+ execute pg_catalog.format('grant select on all tables in schema %I to ${le}', created.nspname);
344
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${le}', created.nspname);
345
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${le}', owner_role, created.nspname);
346
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${le}', owner_role, created.nspname);
179
347
  exception when others then
180
348
  raise warning 'snoutpod: read access reaches schema % at the next apply, not now: %', created.nspname, sqlerrm;
181
349
  end;
182
350
  end loop;
183
351
  end
184
352
  $snoutpod$$fn$, '@home@', pg_catalog.quote_ident(home));
185
- if not exists (select from pg_catalog.pg_event_trigger where evtname = '${Ro}') then
186
- execute pg_catalog.format('create event trigger ${Ro} on ddl_command_end
353
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${Ho}') then
354
+ execute pg_catalog.format('create event trigger ${Ho} on ddl_command_end
187
355
  when tag in (''CREATE SCHEMA'')
188
- execute function %I.${Ro}()', home);
356
+ execute function %I.${Ho}()', home);
189
357
  end if;
190
358
  end
191
359
  $home$;
192
- `,Rp=`\\getenv db_access ${To}
193
- ${QS}create function pg_temp.snoutpod_member(member_name text, group_name text)
360
+ `,am=`\\getenv db_access ${Go}
361
+ ${Ok}create function pg_temp.snoutpod_member(member_name text, group_name text)
194
362
  returns boolean
195
363
  language sql
196
364
  stable
@@ -211,7 +379,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
211
379
  set search_path = pg_catalog, pg_temp
212
380
  as $snoutpod$
213
381
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
214
- select ${Ap("n","r")}
382
+ select ${sm("n","r")}
215
383
  from pg_namespace n
216
384
  join pg_roles r on r.oid = n.nspowner
217
385
  where n.oid = schema_oid
@@ -246,9 +414,9 @@ begin
246
414
  end if;
247
415
  any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
248
416
 
249
- if not exists (select from pg_roles where rolname = '${un}') then
250
- create role ${un} nologin;
251
- change := 'created-group'; role_name := '${un}'; return next;
417
+ if not exists (select from pg_roles where rolname = '${Sn}') then
418
+ create role ${Sn} nologin;
419
+ change := 'created-group'; role_name := '${Sn}'; return next;
252
420
  end if;
253
421
 
254
422
  -- Taken away: every member of the group the list does not name.
@@ -257,7 +425,7 @@ begin
257
425
  from pg_auth_members am
258
426
  join pg_roles g on g.oid = am.roleid
259
427
  join pg_roles m on m.oid = am.member
260
- where g.rolname = '${un}'
428
+ where g.rolname = '${Sn}'
261
429
  order by 1
262
430
  loop
263
431
  continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
@@ -276,21 +444,21 @@ begin
276
444
 
277
445
  -- What a read person may read: the customer's schemas, and nothing of ours (dbAccess.ts).
278
446
  if any_read then
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;
447
+ if not exists (select from pg_roles where rolname = '${le}') then
448
+ create role ${le} nologin;
449
+ change := 'created-group'; role_name := '${le}'; return next;
282
450
  end if;
283
451
  -- Stated whole, as a person's role is, and a member of nothing: whatever it held would reach
284
452
  -- every read person.
285
- alter role ${ae} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
453
+ alter role ${le} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
286
454
  for target in
287
455
  select g.rolname as of_role, pg_get_userbyid(am.grantor) as grantor
288
456
  from pg_auth_members am
289
457
  join pg_roles g on g.oid = am.roleid
290
458
  join pg_roles m on m.oid = am.member
291
- where m.rolname = '${ae}'
459
+ where m.rolname = '${le}'
292
460
  loop
293
- execute pg_catalog.format('revoke %I from ${ae} granted by %I', target.of_role, target.grantor);
461
+ execute pg_catalog.format('revoke %I from ${le} granted by %I', target.of_role, target.grantor);
294
462
  end loop;
295
463
  for target in
296
464
  select n.nspname, pg_temp.snoutpod_customer_schema(n.oid) as customer
@@ -299,16 +467,16 @@ begin
299
467
  order by 1
300
468
  loop
301
469
  if target.customer then
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);
470
+ execute pg_catalog.format('grant usage on schema %I to ${le}', target.nspname);
471
+ execute pg_catalog.format('grant select on all tables in schema %I to ${le}', target.nspname);
472
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${le}', target.nspname);
473
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${le}', owner_role, target.nspname);
474
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${le}', owner_role, target.nspname);
307
475
  else
308
476
  -- Ours. Nothing was ever granted here unless the list or an owner changed since.
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);
477
+ execute pg_catalog.format('revoke all on schema %I from ${le}', target.nspname);
478
+ execute pg_catalog.format('revoke all on all tables in schema %I from ${le}', target.nspname);
479
+ execute pg_catalog.format('revoke all on all sequences in schema %I from ${le}', target.nspname);
312
480
  end if;
313
481
  end loop;
314
482
  for target in
@@ -317,10 +485,10 @@ begin
317
485
  from pg_default_acl d
318
486
  join pg_namespace n on n.oid = d.defaclnamespace
319
487
  where not pg_temp.snoutpod_customer_schema(n.oid)
320
- and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${ae}'::regrole)
488
+ and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${le}'::regrole)
321
489
  loop
322
490
  continue when target.kind is null;
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);
491
+ execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${le}', target.for_role, target.nspname, target.kind);
324
492
  end loop;
325
493
  end if;
326
494
 
@@ -329,7 +497,7 @@ begin
329
497
  wanted_role := entry->>'role';
330
498
  wanted_level := entry->>'level';
331
499
  if exists (select from pg_roles where rolname = wanted_role) then
332
- if not pg_temp.snoutpod_member(wanted_role, '${un}') then
500
+ if not pg_temp.snoutpod_member(wanted_role, '${Sn}') then
333
501
  -- The name is right and the role is not ours: made by somebody else, who holds
334
502
  -- ADMIN on it. Never adopted (see dbAccess.ts).
335
503
  change := 'refused'; role_name := wanted_role; return next;
@@ -337,7 +505,7 @@ begin
337
505
  end if;
338
506
  change := 'applied';
339
507
  else
340
- execute pg_catalog.format('create role %I login in role ${un}', wanted_role);
508
+ execute pg_catalog.format('create role %I login in role ${Sn}', wanted_role);
341
509
  change := 'created';
342
510
  end if;
343
511
  -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
@@ -350,8 +518,8 @@ begin
350
518
  execute pg_catalog.format('revoke pg_read_all_data from %I', wanted_role);
351
519
  end if;
352
520
  if wanted_level = 'full' then
353
- if pg_temp.snoutpod_member(wanted_role, '${ae}') then
354
- execute pg_catalog.format('revoke ${ae} from %I', wanted_role);
521
+ if pg_temp.snoutpod_member(wanted_role, '${le}') then
522
+ execute pg_catalog.format('revoke ${le} from %I', wanted_role);
355
523
  end if;
356
524
  execute pg_catalog.format('alter role %I nobypassrls', wanted_role);
357
525
  if not pg_temp.snoutpod_member(wanted_role, owner_role) then
@@ -366,8 +534,8 @@ begin
366
534
  execute pg_catalog.format('revoke %I from %I', owner_role, wanted_role);
367
535
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = wanted_role;
368
536
  end if;
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);
537
+ if not pg_temp.snoutpod_member(wanted_role, '${le}') then
538
+ execute pg_catalog.format('grant ${le} to %I with inherit true, set false', wanted_role);
371
539
  end if;
372
540
  execute pg_catalog.format('alter role %I bypassrls', wanted_role);
373
541
  execute pg_catalog.format('alter role %I in database %I reset role', wanted_role, db);
@@ -378,30 +546,30 @@ begin
378
546
 
379
547
  -- Nobody reads any more: the group goes, and with it every grant and default it held, so a
380
548
  -- project that stops using the level carries nothing of it.
381
- if not any_read and exists (select from pg_roles where rolname = '${ae}') then
549
+ if not any_read and exists (select from pg_roles where rolname = '${le}') then
382
550
  begin
383
551
  for target in
384
552
  select m.rolname as member_role, pg_get_userbyid(am.grantor) as grantor
385
553
  from pg_auth_members am
386
554
  join pg_roles g on g.oid = am.roleid
387
555
  join pg_roles m on m.oid = am.member
388
- where g.rolname = '${ae}'
556
+ where g.rolname = '${le}'
389
557
  loop
390
- execute pg_catalog.format('revoke ${ae} from %I granted by %I', target.member_role, target.grantor);
558
+ execute pg_catalog.format('revoke ${le} from %I granted by %I', target.member_role, target.grantor);
391
559
  end loop;
392
- drop owned by ${ae};
393
- drop role ${ae};
394
- change := 'dropped-group'; role_name := '${ae}'; return next;
560
+ drop owned by ${le};
561
+ drop role ${le};
562
+ change := 'dropped-group'; role_name := '${le}'; return next;
395
563
  exception when others then
396
564
  -- Holding something in another database, which this never grants. Kept, and said.
397
- raise warning 'snoutpod: could not drop ${ae}: %', sqlerrm;
565
+ raise warning 'snoutpod: could not drop ${le}: %', sqlerrm;
398
566
  end;
399
567
  end if;
400
568
  end
401
569
  $snoutpod$;
402
570
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
403
- `});var Np,jA,UA,FA,BA,HA,GA,Ip=W(()=>{"use strict";_u();Ru();Sa();_a();Nu();po();Ta();Na();Ia();Ca();Xu();Qu();ep();tp();mp();Ao();Wa();Sp();kp();Pp();_o();za();Np=120,jA=10*6e4,UA=2*6e4,FA=15*60*1e3,BA=60*60*1e3,HA=5*60*1e3,GA=10*60*1e3});import{execFile as ek}from"node:child_process";function Cp(){return(e,t,n={})=>new Promise((r,o)=>{let s=ek(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 O(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 tk,readFile as JA}from"node:fs/promises";async function Mp(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??nk)(i)!=="empty":!1}async function nk(e){try{return(await tk(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Dp=W(()=>{"use strict";Me();zn()});function Lp(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 Fp(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Bp(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=${Lp("the access key",e.key,ok)}`),n.push("--env",jp),e.token&&n.push("--env",Up)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${Lp("the endpoint",e.endpoint,rk)}`),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 Hp(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{[jp]:e.secret,...e.token?{[Up]:e.token}:{}}}var jp,Up,rk,ok,Gp=W(()=>{"use strict";jp="SNOUTPOD_S3_SECRET",Up="SNOUTPOD_S3_TOKEN",rk=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,ok=/^[A-Za-z0-9/+=_.-]{1,256}$/});function Wp(e){if(!Va.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 Ja,Kp,qp,Va,zp=W(()=>{"use strict";Ja=["snout_gate","authenticator"],Kp=`do $snoutpod$
571
+ `});var cm,bR,SR,kR,_R,vR,$R,dm=J(()=>{"use strict";Qu();ap();Fa();Ha();cp();Po();Ya();Za();Qa();ei();Ap();Tp();Op();Np();Kp();Bo();pi();Xp();Zp();nm();Mo();mi();cm=120,bR=10*6e4,SR=2*6e4,kR=15*60*1e3,_R=60*60*1e3,vR=5*60*1e3,$R=10*60*1e3});import{execFile as Nk}from"node:child_process";function um(){return(e,t,n={})=>new Promise((r,o)=>{let s=Nk(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(`
572
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var rr=J(()=>{"use strict"});import{readdir as Ik,readFile as RR}from"node:fs/promises";async function pm(e,t,n={}){let r=n.binary??"podman",o=z(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??Ck)(i)!=="empty":!1}async function Ck(e){try{return(await Ik(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var mm=J(()=>{"use strict";De();rr()});function fm(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 ym(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function wm(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=${fm("the access key",e.key,Lk)}`),n.push("--env",gm),e.token&&n.push("--env",hm)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${fm("the endpoint",e.endpoint,Mk)}`),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 bm(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{[gm]:e.secret,...e.token?{[hm]:e.token}:{}}}var gm,hm,Mk,Lk,Sm=J(()=>{"use strict";gm="SNOUTPOD_S3_SECRET",hm="SNOUTPOD_S3_TOKEN",Mk=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Lk=/^[A-Za-z0-9/+=_.-]{1,256}$/});function vm(e){if(!gi.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 fi,km,_m,gi,$m=J(()=>{"use strict";fi=["snout_gate","authenticator"],km=`do $snoutpod$
405
573
  declare
406
574
  wanted text[];
407
575
  begin
@@ -419,7 +587,7 @@ begin
419
587
  end loop;
420
588
  end
421
589
  $snoutpod$;
422
- `,qp=`do $snoutpod$
590
+ `,_m=`do $snoutpod$
423
591
  begin
424
592
  if not exists (
425
593
  select from pg_catalog.pg_stat_sys_tables
@@ -430,8 +598,8 @@ begin
430
598
  end if;
431
599
  end
432
600
  $snoutpod$;
433
- `,Va=["snout_psyche_admin"]});function Vp(e){let t=[];for(let n of e.split(`
434
- `)){let r=n.split(Xa);if(r.length!==4||!Ya.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 Ya,Xa,Jp,Yp=W(()=>{"use strict";Ya=["snouttime"];for(let e of Ya)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Xa="",Jp=`
601
+ `,gi=["snout_psyche_admin"]});function xm(e){let t=[];for(let n of e.split(`
602
+ `)){let r=n.split(yi);if(r.length!==4||!hi.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 hi,yi,Pm,Em=J(()=>{"use strict";hi=["snouttime"];for(let e of hi)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);yi="",Pm=`
435
603
  SET search_path = pg_catalog, pg_temp;
436
604
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
437
605
  DO $snoutpod$
@@ -445,7 +613,7 @@ BEGIN
445
613
  SELECT e.extname, e.extversion
446
614
  FROM pg_extension e
447
615
  JOIN pg_available_extensions a ON a.name = e.extname
448
- WHERE e.extname = ANY (ARRAY[${Ya.map(e=>`'${e}'`).join(", ")}])
616
+ WHERE e.extname = ANY (ARRAY[${hi.map(e=>`'${e}'`).join(", ")}])
449
617
  AND a.default_version IS DISTINCT FROM e.extversion
450
618
  LOOP
451
619
  BEGIN
@@ -476,131 +644,131 @@ BEGIN
476
644
  END
477
645
  $snoutpod$;
478
646
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
479
- `});function Zp(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 Xp,rR,oR,Qp=W(()=>{"use strict";zn();Oo();Xp="SNOUTPOD_OAUTH_ISSUER",rR=256*1024,oR=5*6e4});function pn(e){return`${H(e).pod}-guard`}function em(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:`${No}/${Za}`}:{},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:`${No}/epoch`,LEPIS_GUARD_STATUS:`${No}/status.json`,LEPIS_GUARD_LEASE_MS:String(r),LEPIS_GUARD_TICK_MS:String(Math.max(1,Math.min(1e3,Math.floor(r/5))))}}function tm(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 nm(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",pn(e),"--label",`${Io}=${e}`,"--label",`${Qa}=${n.fingerprint}`,"--network","host","--userns","auto","--volume",`${n.hostDir}:${No}:U`,"--memory","64m","--pids-limit","64","--restart","no",...n.envNames.flatMap(r=>["--env",r]),t.image,"guard"]}function rm(e,t,n,r){let o=2*(r.leaseMs??1e4)/1e3;return n-e<=o+60?!1:t===null||n-t>o}var Za,No,Io,Qa,om=W(()=>{"use strict";Me();Za="witness-ca.pem";No="/guard",Io="snoutpod.guard",Qa="snoutpod.guard-spec"});function im(e){return se(e),{anchor:`snoutpod-${e}-copy`,router:`snoutpod-${e}-copy-lepis`,service:t=>`snoutpod-${e}-copy-${mn(t)}`}}var sm,am,lm=W(()=>{"use strict";Me();Oo();sm="snoutpod.copy",am="snoutpod.copy-service"});import{createHash as sk}from"node:crypto";function at(e){return`${H(se(e)).pod}-${Co}`}function um(e){return st(e)?(e.services??[]).filter(t=>t.name!==ge):[]}function Ut(e,t){return st(e)&&t.name!==ge}function pm(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 ti(e,t){if(t.hostPort===null||t.hostPort===void 0)return null;let n=pm(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 ni(e,t){let n=ti(e,t),r=mm(e,t);return n===null?t:{...t,hostPort:n,...r===null?{}:{adminHostPort:r}}}function mm(e,t){return!t.adminPort||!t.adminHostPort?null:ti(e,{name:`${t.name} admin`,hostPort:t.adminHostPort})}function ri(e){return um(e).flatMap(t=>{let n=mm(e,t);return n===null||!t.adminPort?[]:[[n,t.adminPort]]}).sort((t,n)=>t[0]-n[0])}function oi(e){let t=um(e);return t.length>0&&pm(e),t.flatMap(n=>{let r=ti(e,n);return r===null?[]:[[r,n.port]]}).sort((n,r)=>n[0]-r[0])}function fm(e,t){let n=ri(e);return sk("sha256").update(`stack:${t}:`).update(JSON.stringify(oi(e))).update(n.length===0?"":`admin:${JSON.stringify(n)}`).digest("hex").slice(0,32)}var yt,ei,Co,cm,dm,gm=W(()=>{"use strict";Me();Ao();yt="snoutpod.stack",ei="snoutpod.stack-spec",Co="stack",cm=16,dm=16});import{createHash as Vn,randomBytes as ik}from"node:crypto";import{access as lk,constants as ck,mkdir as hm,readFile as dk,readdir as uk,rename as pk,rm as mk,writeFile as fk}from"node:fs/promises";import{delimiter as gk,join as Bt}from"node:path";function Om(e,t,n,r){return Vn("sha256").update(li(e)).update(e.smtpSecretEnv&&t?Vn("sha256").update(t).digest("hex"):"").update(n===void 0?"":`router:${n}`).update(r===void 0?"":`@${r}`).digest("hex").slice(0,32)}function hk(e,t){return Ha(t),Vn("sha256").update(`separated:${t}:${dn}:`).update(Om(e)).digest("hex").slice(0,32)}function li(e){if(Array.isArray(e))return`[${e.map(li).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)}:${li(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function mn(e){if(!yk.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Tt(e){if(!wk.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function bk(e,t,n,r,o){if(r===void 0)return`postgres://${Tt(t)}:${n}@127.0.0.1:5432/${se(e)}`;if(o!==void 0&&o!==ht(e))throw new Error(`${o} is not ${e}'s router`);return`postgres://${`${Tt(t)}:${n}@${o??"127.0.0.1"}`}:${Le(r,"router port")}/${se(e)}?sslmode=disable`}function Sk(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 Lo(e,t){if(!t.dbUriEnv||!t.dbUriRole||t.name===ge)return;let n=(e.services??[]).find(r=>r.name===ge);if(n)return Ja.includes(t.dbUriRole)?n.port:st(e)?dn:void 0}function ci(e,t){return st(e)&&Lo(e,t)!==void 0?ht(e.ref):void 0}function bm(e,t,n,r){let o=Ga(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]=bk(e.ref,s,a,Lo(e,t),ci(e,t))}if(t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),t.adminPortEnv&&t.adminPort){if(!Im.test(t.adminPortEnv))throw new Error(`${t.name}: not an environment variable name: ${JSON.stringify(t.adminPortEnv)}`);o[t.adminPortEnv]=String(Le(t.adminPort,`${t.name}: adminPort`))}return o}function Sm(e,t){return`${H(e).pod}-${mn(t)}`}function kk(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 Mo(e){return`${e}${Nm}`}function _m(e){return typeof e=="string"&&e.endsWith(Nm)}function ai(e){let t=e.Names?.[0];return t===void 0?void 0:t.replace(/^\//,"")}function Ft(e){return`snoutpod-host-${mn(e)}`}function _k(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 Vn("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 $k(e){return[...e.filter(t=>!t.joins),...e.filter(t=>t.joins)]}function vk(e){return Object.keys(e.secretEnv??{}).length>0||Object.keys(e.files??{}).length>0||!!e.envFrom||!!e.databaseUrl}function Pk(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 ui(e){return!e.adminPort||!e.adminHostPort?null:[Le(e.adminHostPort,`${e.name}: adminHostPort`),Le(e.adminPort,`${e.name}: adminPort`)]}function xk(e,t){let n=[],r=yn(t);for(let o of e??[]){if(mn(o.name),Le(o.port,`${o.name}: port`),o.hostPort===null||o.hostPort===void 0)continue;Le(o.hostPort,`${o.name}: hostPort`);for(let a of r)n.push("--publish",`${a}:${o.hostPort}:${o.port}`);let s=ui(o);if(s&&n.push("--publish",`127.0.0.1:${s[0]}:${s[1]}`),o.name===ge){let a=o.hostPort+xo;Le(a,`${o.name}: admin hostPort`);for(let c of r)n.push("--publish",`${c}:${a}:${Po}`)}}return n}function Ek(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`);Le(t.port,`${t.name}: port`);let r=Ha(e.routerPodPort),o=([s,a])=>yn(n.bind).flatMap(c=>["--publish",`${c}:${Le(s,"router hostPort")}:${a}`]);return["run","--detach","--name",ht(e.ref),"--label",`${Rt}=${e.ref}`,"--label",`${hn}=${e.servicesGeneration??0}`,"--label",`${Jn}=${n.fingerprint}`,"--network",n.network,"--userns=auto","--memory",`${t.memoryMb}m`,"--pids-limit",String(Lm),"--restart","no",...o([r.postgres,t.port]),...o([r.admin,Po]),...o([r.passthrough,dn]),...Cm(t),"--volume",`${Wn(e.ref)}:${Eo}:ro`,...n.envNames.flatMap(s=>["--env",s]),t.image]}function Ak(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",at(e.ref),"--label",`${yt}=${e.ref}`,"--label",`${ei}=${t.fingerprint}`,"--network",t.network,"--userns=auto","--memory",`${cm}m`,"--pids-limit",String(dm),"--restart","no","--stop-signal","SIGKILL",...oi(e).flatMap(([n,r])=>yn(t.bind).flatMap(o=>["--publish",`${o}:${Le(n,"stack hostPort")}:${Le(r,"service port")}`])),...ri(e).flatMap(([n,r])=>["--publish",`127.0.0.1:${Le(n,"stack adminHostPort")}:${Le(r,"admin port")}`]),"--entrypoint","sleep",e.image,"infinity"]}function Rk(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 yn(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 Le(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Do(e){let t=[];for(let n of Object.keys(e)){if(!Im.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Cm(e,t={}){if(t.adminPublished&&e.adminHealthPath&&ui(e)){if(!di.test(e.adminHealthPath))throw new Error(`${e.name}: not a health path: ${JSON.stringify(e.adminHealthPath)}`);return["--label",`${jo}=${e.adminHostPort}:${e.adminHealthPath}`]}let n=e.healthPath;if(n==null||n==="")return[];if(!di.test(n))throw new Error(`${e.name}: not a health path: ${JSON.stringify(n)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Le(e.hostPort,`${e.name}: hostPort`),["--label",`${jo}=${e.hostPort}:${n}`])}function Tk(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||!di.test(r)?null:{port:n,path:r}}async function Nk(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>lk(o,ck.X_OK));for(let o of n.split(gk))if(o)try{let s=Bt(o,e);return await r(s),s}catch{}return null}function Ik(){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 Mk(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Dk(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 pi(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Lk(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 jk(e,t,n,r){return{image:await Uk(e,t,r),data:await Fk(e,t,n)}}async function Uk(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 Dk(r,n)}async function Fk(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?pi(s.stdout):null}catch{return null}}function Dm(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 Bk(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 Hk(e,t){let n=new Map,r=new Set(t.length>0?Dm(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=cp(s,a),i=n.get(c)??{group:s,peer:a,containers:[]};i.containers.push(H(o.ref).pod),o.ref===s&&i.containers.push(ht(o.ref)),n.set(c,i)}}return n}function Gk(e,t){let n=[];for(let[r,o]of[...t].sort(([s],[a])=>s<a?-1:s>a?1:0)){if(!jt(o))continue;let s=yo(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=cn(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 xm(e,t){let n=yn(t);return(e.Ports??[]).some(r=>{let o=r.host_ip??"";return o!==""&&o!=="0.0.0.0"&&o!=="::"&&!n.includes(o)})}function Jk(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:Em;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Em)}`),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 Vk(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Ok;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Nk(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function jm(e){let t=e.binary??"podman",n=e.exec,r=Vk(e),o=e.probe??Mm;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:Ck,stdin:Ik()})}catch{}}async function a(){let[p,f,g]=await Promise.all([O(n,t,["ps","--all","--filter",`label=${De}`,"--format","json"]),O(n,t,["ps","--all","--filter",`label=${Rt}`,"--format","json"]),O(n,t,["ps","--all","--filter",`label=${yt}`,"--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?.[He]?F.Labels?.[yt]: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?.[Rt];return B?[{...F,Labels:{...F.Labels,[De]:B,[He]:ge}}]:[]})],x=new Map,_=new Map,I=new Map,j=new Map,q=new Set([...S.keys(),...(f.trim()?JSON.parse(f):[]).flatMap(F=>F.Labels?.[Rt]??[])]);for(let F of k){let B=F.Labels?.[De],D=F.Labels?.[He];if(!B||!D)continue;if(_m(ai(F))){j.set(B,[...j.get(B)??[],D]);continue}let Y=x.get(B)??[];Y.push(D),x.set(B,Y),ii(F.State)==="stopped"&&_.set(B,[..._.get(B)??[],D]);let me=Number(F.Labels?.[hn]??0),Re=Number.isInteger(me)?me:0;I.set(B,Math.max(I.get(B)??Re,Re))}let L=k.filter(F=>F.Labels?.[De]&&!F.Labels?.[He]),v=L.map(F=>Qk(F,Date.now())?"stopped":ii(F.State)),U=await Xk(L,Yk,(F,B)=>v[B]==="running"?Promise.resolve(!0):e.hasData(F.Labels?.[De]??""));return L.map((F,B)=>{let D=F.Labels?.[De]??"",Y=Number(F.Labels?.[si]);return{ref:D,status:v[B]??"unknown",generation:Number.isInteger(Y)?Y:null,hasData:U[B]??!1,services:x.get(D)??[],servicesStopped:_.get(D)??[],...q.has(D)?{separated:!0}:{},...j.has(D)?{setAside:j.get(D)}:{},...xm(F,e.podBind)?{boundElsewhere:!0}:{},...I.has(D)?{servicesGeneration:I.get(D)}:{}}})}async function c(){let p=await O(n,t,["ps","--all","--filter",`label=${De}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(g=>g.Labels?.[De]&&!g.Labels?.[He]).length}async function i(p){let f=await(e.majors??((g,w)=>jk(n,t,g,w)))(p.ref,p.image);return Lk(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 Fp(e.repository,w?()=>w.forProject(p.ref,p.bucket):null),k={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...Hp(S)},x=[...await r(),"run","--detach","--name",f.pod,"--label",`${De}=${p.ref}`,"--label",`${si}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,"--pids-limit",String(Kk),...n_(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?yn(e.podBind).flatMap(_=>["--publish",`${_}:${p.port}:5432`]):[],...xk(Ba(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"]:[],...t_(p.oauth),...Jk(p.tuning).flatMap(_=>["--env",_]),"--env",`SNOUTPOD_RETENTION_FULL=${Ou(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...Bp(S),p.image,"snoutpod-postgres"];await O(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?.[He]?void 0:p.Labels?.[yt]}async function T(p,f){let g=e.inPlaceRoot;if(g===void 0)return p;let w=await uk(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(!km.test(_))continue;if(!S.has(_)){f&&await mk(Bt(g,x),{force:!0});continue}let I=kk(await dk(Bt(g,x),"utf8").catch(()=>""));I!==null&&k.set(_,I)}return p.map(x=>{let _=x.Id&&x.Labels?.[He]?k.get(x.Id):void 0;return _===void 0?x:{...x,Labels:{...x.Labels,[Jn]:_.spec,[hn]:String(_.generation)}}})}async function N(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)/fn)}m`]:[];if((await n(t,["update","--memory",`${f}m`,...w,p])).code!==0)return!1;let k=await N(p);return k!==null&&k.bytes===f*fn}async function Z(p,f){let g=e.inPlaceRoot;if(g===void 0||!km.test(p))return;await hm(g,{recursive:!0});let w=Bt(g,`${p}.json`);await fk(`${w}.tmp`,JSON.stringify(f)),await pk(`${w}.tmp`,w)}async function le(p){let[f,g,w]=await Promise.all([O(n,t,["ps","--all","--filter",`label=${De}=${se(p)}`,"--filter",`label=${He}`,"--format","json"]),O(n,t,["ps","--all","--filter",`label=${Rt}=${p}`,"--format","json"]),O(n,t,["ps","--all","--filter",`label=${yt}=${p}`,"--format","json"])]),S=await T(f.trim()?JSON.parse(f):[],!1),k=[];for(let _ of S){let I=_.Labels?.[He];if(!I)continue;let j=Number(_.Labels?.[hn]??0),q=_m(ai(_));k.push({name:I,container:q?ai(_)??"":Sm(p,I),id:_.Id??"",spec:_.Labels?.[Jn]??"",pod:_.Labels?.[ym]??"",running:_.State==="running",generation:Number.isInteger(j)?j:0,separate:!1,stack:_.Labels?.[yt]===p,setAside:q})}for(let _ of g.trim()?JSON.parse(g):[]){if(_.Labels?.[Rt]!==p)continue;let I=Number(_.Labels?.[hn]??0);k.push({name:ge,container:ht(p),id:_.Id??"",spec:_.Labels?.[Jn]??"",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?.[ei]??"",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 q of f){let L=q.dbUriRole??null;q.dbUriEnv&&L&&!w.includes(L)&&w.push(Tt(L))}let S=new Map;if(w.length===0)return S;let k=await e.fixedRolePasswords?.()??new Map,x={},_=[],I=[];w.some(q=>Ja.includes(q))&&I.push(Kp,qp);for(let q of w.filter(L=>Va.includes(L)))I.push(Wp(q));let j=new Map;if(p.derivedServicePasswords===!0)for(let q of w.filter(Ka))j.set(q,await qa(p.adminPassword,q));return w.forEach((q,L)=>{let v=k.get(q)??(j.has(q)?void 0:g.get(q));if(v!==void 0){S.set(q,v);return}let U=j.get(q)??ik(24).toString("base64url"),F=`SNOUTPOD_SERVICE_PASSWORD_${L}`;S.set(q,U),x[F]=U,_.push("--env",F),I.push(`\\getenv service_password_${L} ${F}`),I.push(`\\set service_role_${L} ${q}`),I.push(`alter role :"service_role_${L}" with password :'service_password_${L}';`)}),I.push(""),await O(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 $e(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]),q=j.code===0?Sk(j.stdout,x.dbUriEnv,Tt(_)):null;q!==null&&w.set(_,q)}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)Oa(k.get(x)??"",_)||w.delete(x);return w}async function ne(p){let f=Wn(p.ref);await O(n,t,["volume","create","--ignore",f]),await O(n,t,[...await r(),"run","--rm","--network","none","--volume",`${f}:${Eo}`,"--entrypoint","bash",p.image,"-c",wp])}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=Mk(p);if(!w||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(g.stderr||g.stdout).trim()}`);await e.registryLogin(w),await O(n,t,[...f,"pull","--quiet",p])}async function kn(p){let f=await n(t,["container","inspect","--format","{{json .HostConfig.PortBindings}}",H(p).pod]);return f.code===0?Rk(f.stdout):null}async function Gt(p,f={}){let g=p.services??[],w=st(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=>st(p)&&P.name===ge,q=w.filter(P=>Ut(p,P)),L="";if(q.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(q.some(P=>P.name===Co))throw new Error(`${p.ref}: a service may not be called ${Co}, which is the services-only pod's own name`);L=fm(p,e.network)}let v=q.length>0&&k!==null&&k.running&&k.id!==""&&k.spec===L,U=w.some(P=>!j(P)&&!Ut(p,P))?await G(H(p.ref).pod):"",F=w.some(P=>!j(P)&&!Ut(p,P)&&ui(P))?new Set(await kn(p.ref)??[]):new Set,B=v?k?.id??"":"",D=P=>Ut(p,P)?B:U,Y=P=>j(P)?hk(P,p.routerPodPort):Om(Ut(p,P)?ni(p,P):P,_,Lo(p,P),ci(p,P)),me=new Map(w.map(P=>[P.name,Y(P)])),Re=p.servicesGeneration??0,Ke=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 Je=await N(he.container);Je===null||Je.bytes%fn!==0||Je.bytes/fn>=P.memoryMb||Y({...P,memoryMb:Je.bytes/fn})===he.spec&&await oe(he.container,P.memoryMb,Je)&&(await Z(he.id,{spec:me.get(P.name)??"",generation:Re}),Ke.add(P.name),e.log?.(`${p.ref}: ${P.name} took ${P.memoryMb} MB in place (was ${Je.bytes/fn} MB), not stopped`))}let Ee=new Set(w.filter(P=>{let he=I.get(P.name);if(Ke.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&&Ke.size===0&&w.length>0&&!x.some(P=>P.generation>=Re))for(let P of w)Ee.add(P.name);let Ii=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(Ii));for(let Ie of w){let Je=Ii(Ie);!Ee.has(Ie.name)&&Je!==null&&he.has(Je)&&(Ee.add(Ie.name),P=!0)}}let Ci=new Set(w.filter(P=>Ee.has(P.name)).map(P=>P.image));(q.length>0&&!v||Ee.has(ge))&&Ci.add(p.image);for(let P of Ci)await fe(P);let og=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))&&!og(P)&&await O(n,t,["rm","--force","--ignore",P.container]);if(k!==null&&!v&&!(f.keepSeparated===!0&&q.length===0)&&await O(n,t,["rm","--force","--ignore",at(p.ref)]),q.length>0&&!v){if(await O(n,t,[...await r(),...Ak(p,{network:e.network,bind:e.podBind,fingerprint:L})]),B=await G(at(p.ref)),B==="")throw new Error(`${p.ref}: the services-only pod was made and its id could not be read`);await f.afterStart?.(at(p.ref))}let sr=w.filter(P=>Ee.has(P.name)),Mi=S.filter(P=>P.setAside),Qo=await V(p,sr,Mi.length>0&&!f.renewPasswords?await $e(p,Mi,sr):new Map);sr.some(P=>P.name===ge)&&await ne(p);for(let P of sr){if(j(P)){let Di=Ga(hp(p.ref,P.env),p.adminPassword);await O(n,t,[...await r(),...Ek(p,P,{network:e.network,bind:e.podBind,fingerprint:me.get(P.name)??"",envNames:Do(Di).filter(ag=>ag!=="--env")})],{env:Di}),await f.afterStart?.(ht(p.ref));continue}let he=Ut(p,P),Ie=he?at(p.ref):H(p.ref).pod,Je=Sm(p.ref,P.name),sg=[...await r(),"run","--detach","--name",Je,"--label",`${De}=${p.ref}`,"--label",`${si}=${p.generation}`,"--label",`${He}=${P.name}`,"--label",`${hn}=${p.servicesGeneration??0}`,"--label",`${Jn}=${me.get(P.name)??""}`,"--label",`${ym}=${D(P)}`,...he?["--label",`${yt}=${p.ref}`]:[],"--network",`container:${Ie}`,"--userns",`container:${Ie}`,"--memory",`${P.memoryMb}m`,"--pids-limit",String(Lm),"--restart","no",...Cm(he?ni(p,P):P,{adminPublished:he||P.adminHostPort!==null&&P.adminHostPort!==void 0&&F.has(P.adminHostPort)}),...P.name===ge?["--volume",`${Wn(p.ref)}:${Eo}:ro`]:[],...Do(bm(p,P,Qo,_)),P.image];Ee.has(ge)||await nr(p,P,Qo),await O(n,t,sg,{env:bm(p,P,Qo,_)})}}async function nr(p,f,g){let w=Lo(p,f),S=f.dbUriRole,k=S?g.get(S):void 0;if(w===void 0||w===dn||!S||k===void 0)return;let x=H(p.ref),_=ci(p,f)??"127.0.0.1",{waitMs:I,retryMs:j}=e.routerLogin??{waitMs:qk,retryMs:Wk},q=Date.now()+I;for(let L=1;;L++){let v=await n(t,["exec","--env","PGPASSWORD","--env","PGSSLMODE=disable","--env","PGCONNECT_TIMEOUT=3",x.pod,"psql","--host",_,"--port",String(w),"--username",Tt(S),"--dbname",x.database,"--no-password","--tuples-only","--no-align","--command","select 1"],{env:{PGPASSWORD:k}});if(v.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()>=q){let U=`${v.stderr}
484
- ${v.stdout}`.trim().split(`
485
- `).filter(Boolean).pop()??`exit ${v.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 Ei(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 ve(p){return["exec",..._e,H(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres"]}async function Nf(p){let f=await n(t,[...ve(p),"-tAc",Vu]);return f.code===0?Yu(f.stdout):null}async function If(p){let f=await n(t,[...ve(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 Ai(p){let f=await n(t,[...ve(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 Cf(p,f){let g=x=>n(t,[...ve(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 Mf(p){let f=await n(t,[...ve(p),"-tAc",Iu],{timeoutMs:it});return f.code===0?Cu(f.stdout):null}async function Df(p,f){let g=new Map;for(let S of f){if(typeof S!="string"){se(S.ref);let k=Wu(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",zu,"sh",...g.values()]);return w.code===0?Ju(w.stdout,g):null}async function Lf(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}}",_]),q=j.code===0?j.stdout.trim().replace(/\/+$/,""):"";if(!q)throw new Error(`the volume ${_} could not be found, so nothing was moved`);g.push(q)}let S=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",xa,"sh",g[0],g[1]]):await n("sh",["-c",xa,"sh",g[0],g[1]]),k=Uu(S.stdout,S.code);if(k)throw new Error(k)}async function jf(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 O(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",Ea,"sh",w[0],w[1]]):await n("sh",["-c",Ea,"sh",w[0],w[1]]),x=Fu(k.stdout,k.code);if(x)throw new Error(x)}async function Uf(p){return(await n(t,[...ve(p),"-v","ON_ERROR_STOP=1",...Aa.flatMap(g=>["-c",g])])).code===0}async function Ff(p){let f=e.guardRoot;if(!f)return;let g=await O(n,t,["ps","--all","--filter",`label=${Io}`,"--format","json"]),w=new Map((g.trim()?JSON.parse(g):[]).flatMap(k=>{let x=k.Labels?.[Io];return x?[[x,{fingerprint:k.Labels?.[Qa]??"",status:ii(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 O(n,t,["rm","--force","--ignore",pn(k)]);for(let{spec:k,guard:x}of p){if(k.port===null)continue;let _=tm(x,k.port,k.generation,L=>Vn("sha256").update(L).digest("hex")),I=w.get(k.ref);if(I?.fingerprint===_){if(I.status!=="running")await O(n,t,["start",pn(k.ref)]);else{let L=Bt(f,k.ref,"status.json"),v=await n(t,["unshare","stat","-c","%Y",L]),U=v.code===0?Number(v.stdout.trim()):null;rm(I.startedAt,Number.isFinite(U)?U:null,Math.floor(Date.now()/1e3),x)&&await O(n,t,["restart",pn(k.ref)])}continue}await O(n,t,["rm","--force","--ignore",pn(k.ref)]);let j=Bt(f,k.ref);await hm(j,{recursive:!0,mode:448}),x.witnessCa&&await O(n,t,["unshare","sh","-c",'umask 022 && printf %s "$1" > "$2.tmp" && mv "$2.tmp" "$2"',"witness-ca",x.witnessCa,Bt(j,Za)]),await fe(x.image);let q=em(x,k.port,k.adminPassword);await O(n,t,[...await r(),...nm(k.ref,x,{hostDir:j,fingerprint:_,envNames:Do(q).filter(L=>L!=="--env")})],{env:q})}}async function Bf(p){return(await n(t,[...ve(p),"-v","ON_ERROR_STOP=1","-c",Pa],{timeoutMs:it})).code===0}async function Hf(p,f){return(await n(t,[...ve(p),"-v","ON_ERROR_STOP=1",...Du(f).flatMap(w=>["-c",w])],{timeoutMs:it})).code===0}async function Gf(){let p=e.network;if(!p)return null;let f=(await O(n,t,["network","inspect",p,"--format","{{range .Subnets}}{{.Subnet}} {{end}}"])).trim().split(" ")[0]??"";if(f==="")return null;let g=await O(n,t,["ps","--format","json"]),S=(g.trim()?JSON.parse(g):[]).map(_=>{let I=_.Labels?.[De],j=_.Labels?.[gn],q=_.Labels?.[Rt];if(q)return{name:ht(q),ref:q,service:!1};let L=_.Labels?.[yt];if(L&&!_.Labels?.[He])return{name:at(L),ref:L,service:!1};let v=_.Labels?.[sm],U=_.Labels?.[am];if(v&&(!U||U===ge)){let F=im(v);return{name:U?F.router:F.anchor,ref:v,service:!1}}return I&&!_.Labels?.[He]?{name:H(I).pod,ref:I,service:!1}:j?{name:Ft(j),ref:j,service:!0}:null}).filter(_=>_!==null);if(S.length===0)return{subnet:f,containers:[]};let k=await O(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 Kf(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 Ri(p,f,g){if(!Number.isInteger(g)||g<1)return!1;let w=await n(t,[...ve(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,[...ve(p),"-v","ON_ERROR_STOP=1","-c",`alter system set ${f} = '${g}MB'`,"-c","select pg_reload_conf()"],{timeoutMs:it})).code===0}function qf(p,f){return Ri(p,"temp_file_limit",f)}function Wf(p,f){return Ri(p,"max_wal_size",f)}async function zf(p){return(await n(t,["unshare","--rootless-netns",Pm,"-f","-"],{stdin:p,timeoutMs:it})).code===0}let Yo=null,Xo=null,Ge=new Map,rr=0;async function Jf(p,f){let g=p.join(",");if(Yo===g)return!0;let w=await n(t,["network","inspect",xe]);if(w.code===0&&wo(w.stdout,p))return Yo=g,f("network",null),!0;if(w.code===0){let k=(await O(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 O(n,t,["network","rm",xe])}let S=await n(t,ho(p));return S.code!==0?(f("network",`WARNING: the ${xe} network could not be made: ${(S.stderr||S.stdout).trim().split(`
489
- `)[0]}`),!1):(Yo=g,f("network",null),f("made",null),f("made",`made the ${xe} network, routing only to ${p.join(", ")}`),!0)}async function Vf(p,f={}){let g=await Ti(p),w=[...await Yf(p),...await Xf(p),...await Zf(p,f.groupPeers===!0)],S=await Ti(p);return[...g,...w,...S]}let Kt=null;async function Ti(p){if(rr===0&&or===0&&Kt===null&&!p.some(x=>Zo(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&&Ua(x.name)&&g.set(x.name,x.subnets?.[0]?.subnet??"");if(g.size===0&&Kt===null)return[];let w=g.size===0?up:dp(Gk(p,g)),S=Date.now();if(Kt&&Kt.script===w&&S-Kt.at<15e3)return[];let k=await n(t,["unshare","--rootless-netns",Pm,"-f","-"],{stdin:w,timeoutMs:it});if(k.code!==0){let x=`WARNING: the peer networks' filter could not be loaded: ${(k.stderr||k.stdout).trim().split(`
490
- `)[0]}`;return Ge.get("filter")!==x?(Ge.set("filter",x),[x]):[]}return Ge.delete("filter"),Kt=g.size===0?null:{script:w,at:S},[]}function Zo(p){return p.guard!==void 0&&p.state!=="deleted"&&!!p.peerAddress}async function Yf(p){let f=[...new Set(e.peers??[])].sort(),g=Dm(p.filter(D=>!Zo(D)));if(f.length===0||g.length===0)return[];let w=[],S=(D,Y)=>{Y===null?Ge.delete(D):Ge.get(D)!==Y&&(Ge.set(D,Y),w.push(Y))},k=await O(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(Xo?.key!==I){let D=e.network?await n(t,["network","inspect",e.network]):null,Y=D?.code===0?bo(D.stdout,f):[];Xo={key:I,peers:f.filter(me=>!Y.includes(me))}}let j=Xo.peers;if(j.length===0||!await Jf(j,S))return w;let q=await O(n,t,["inspect","--format","{{.Name}} {{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",..._.map(D=>D.container)]),L=new Set(q.split(`
491
- `).map(D=>D.trim().split(/\s+/)).filter(D=>D.slice(1).includes(xe)).map(D=>D[0])),v=_.filter(D=>!L.has(D.container));if(v.length===0)return w;let U=new Map(p.map(D=>[D.ref,D.group??D.ref])),F=await O(n,t,["ps","--all","--filter",`network=${xe}`,"--format","json"]),B=new Set((F.trim()?JSON.parse(F):[]).map(D=>D.Labels?.[De]).filter(D=>!!D).map(D=>U.get(D)??D));for(let D of v){let Y=Bk(D,B,v[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 Xf(p){let f=p.filter(Zo),g=[],w=(L,v)=>{v===null?Ge.delete(L):Ge.get(L)!==v&&(Ge.set(L,v),g.push(v))};if(f.length===0&&rr===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&&yo(L.name)!==null?k.set(L.name,L.subnets?.[0]?.subnet??""):L.name&&cn(L.name)!==null&&x.push(L.subnets?.[0]?.subnet??"");rr=k.size;let _=await O(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,q=new Set;for(let L of f){let v=L.group??L.ref,U=lp(v),F=H(L.ref).pod;q.add(U);let B=L.peerAddress;if(j?.code===0&&bo(j.stdout,[B]).length>0||!I.has(F))continue;let D=k.has(U)?jt(k.get(U)):null,Y=!1;if(D){let Ke=await n(t,["network","inspect",U]);Y=Ke.code===0&&wo(Ke.stdout,[B],D)}if(!Y&&k.has(U)){let Ke=(await O(n,t,["ps","--all","--filter",`network=${U}`,"--format","{{.Names}}"])).trim();if(Ke!==""){w(U,`WARNING: the ${U} network does not route exactly to ${B}, and ${Ke.split(`
493
- `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`);continue}await O(n,t,["network","rm",U]),k.delete(U)}if(!Y){let Ke=D&&D.subnet||ja([...k.values(),...x]);if(D=Ke?jt(Ke):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,ho([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 O(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",F])).trim().split(/\s+/).includes(U)){w(U,null);continue}let Re=await n(t,["network","connect",U,F]);if(Re.code!==0){w(U,`WARNING: ${F} could not join the ${U} network, so it has no route to ${B}: ${(Re.stderr||Re.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(q.has(L))continue;(await O(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 rr=k.size,g}let or=0;async function Zf(p,f){let g=[],w=(v,U)=>{U===null?Ge.delete(v):Ge.get(v)!==U&&(Ge.set(v,U),g.push(U))},S=Hk(p,e.peers??[]);if(S.size===0&&or===0)return g;let k=await n(t,["network","ls","--format","json"]);if(k.code!==0)return g;let x=new Map,_=[];for(let v of k.stdout.trim()?JSON.parse(k.stdout):[]){let U=v.subnets?.[0]?.subnet??"";v.name&&Ua(v.name)&&_.push(U),v.name&&cn(v.name)!==null&&x.set(v.name,U)}or=x.size;let I=await O(n,t,["ps","--format","json"]),j=new Set((I.trim()?JSON.parse(I):[]).flatMap(v=>v.Names??[])),q=S.size>0&&e.network?await n(t,["network","inspect",e.network]):null;for(let[v,U]of[...S].sort(([F],[B])=>F<B?-1:F>B?1:0)){if(q?.code===0&&bo(q.stdout,[U.peer]).length>0)continue;let F=U.containers.filter(Y=>j.has(Y));if(F.length===0)continue;let B=x.has(v)?jt(x.get(v)):null,D=!1;if(B){let Y=await n(t,["network","inspect",v]);D=Y.code===0&&wo(Y.stdout,[U.peer],B)}if(!D&&x.has(v)){let Y=(await O(n,t,["ps","--all","--filter",`network=${v}`,"--format","{{.Names}}"])).trim();if(Y!==""){w(v,`WARNING: the ${v} 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 O(n,t,["network","rm",v]),_.splice(_.indexOf(x.get(v)),1),x.delete(v)}if(!D){let Y=B&&B.subnet||ja(_);if(B=Y?jt(Y):null,!B){w(v,`WARNING: no /28 is free for the ${v} network, so ${F.join(", ")} has no route to ${U.peer}`);continue}let me=await n(t,ho([U.peer],{name:v,subnet:B.subnet,gateway:B.gateway}));if(me.code!==0){w(v,`WARNING: the ${v} network could not be made: ${(me.stderr||me.stdout).trim().split(`
497
- `)[0]}`);continue}x.set(v,B.subnet),_.push(B.subnet),g.push(`made the ${v} network (${B.subnet}), routing only to ${U.peer}`)}for(let Y of F){if((await O(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",Y])).trim().split(/\s+/).includes(v)){w(`${v} ${Y}`,null);continue}let Re=await n(t,["network","connect",v,Y]);if(Re.code!==0){w(`${v} ${Y}`,`WARNING: ${Y} could not join the ${v} network, so it has no route to ${U.peer}: ${(Re.stderr||Re.stdout).trim().split(`
498
- `)[0]}`);continue}w(`${v} ${Y}`,null),g.push(`${Y} joined the ${v} network: its route to ${U.peer} comes from there`)}}let L=new Set(p.filter(v=>v.state!=="deleted").map(v=>v.group??v.ref));for(let v of[...x.keys()]){if(S.has(v))continue;let U=cn(v);if(L.has(U.group)&&!f)continue;let F=(await O(n,t,["ps","--all","--filter",`network=${v}`,"--format","{{.Names}}"])).trim();for(let B of F===""?[]:F.split(`
499
- `))(await n(t,["network","disconnect",v,B])).code===0&&g.push(`${B} left the ${v} network: ${U.peer} holds none of its cluster's pods`);(await n(t,["network","rm",v])).code===0&&(x.delete(v),g.push(`removed the ${v} network: ${U.peer} holds none of its cluster's pods`))}return or=x.size,g}async function Qf(p,f){let g=await n(t,[...ve(p),"-tA","-v","ON_ERROR_STOP=1",...Mu(f).flatMap(S=>["-c",S])],{timeoutMs:it}),w=Number(g.stdout.trim().split(`
500
- `).pop());return g.code===0&&Number.isInteger(w)?w:null}async function eg(p){let f=await n(t,[...ve(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&&uo.test(g)?g:null}async function tg(p,f){if(!$a.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 = '${At}') then create role ${At} with login replication; end if; end $$`,`alter role ${At} with login replication noinherit password '${f}'`],role:At,line:`hostssl replication ${At} all scram-sha-256`,replaceLine:!1})}async function ng(p,f,g,w=!0){let S=e.rootless??(typeof process.getuid!="function"||process.getuid()!==0);return va(g),Oi(p,{statements:Lu(f,w),role:an,line:va(S?ln:g),replaceLine:!0})}async function Oi(p,f){let g=H(p).pod,w=[...ve(p),"-v","ON_ERROR_STOP=1"];if((await n(t,[...w,...f.statements.flatMap(j=>["-c",j])],{timeoutMs:it})).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 Ni(p,x.replace(/\/[^/]+$/,""))?!1:(await n(t,[...w,"-tAc","select pg_reload_conf()"])).code===0}async function Ni(p,f){let g=H(p).pod,w=[...ve(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 rg(p,f){if(!uo.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[[Hu,Ku(f.password)],[Gu,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:it});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:Ai,replayLsn:Nf,currentLsn:If,settleTimeline:Cf,podHealth:Mf,setSyncNames:Hf,...e.guardRoot?{syncGuards:Ff}:{},dropKeeperSlots:Qf,bridgeContainers:Gf,applyPodIsolation:zf,joinPeerNetwork:Vf,requestDiskQuota:Kf,ensureTempFileLimit:qf,ensureMaxWalSize:Wf,ensureKeeperRole:tg,ensureReplicaRole:ng,dropIdleReplicaSlot:Bf,writeFollowFiles:rg,podCertificate:eg,swapHomeData:Lf,adoptHomeData:jf,promoteStandby:Uf,nodesAnswer:Df,preflight:i,create:u,createServices:Gt,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 O(n,t,["stop","--time","5",...f]),await O(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 O(n,t,["stop","--time","5",...S]),await O(n,t,["start",at(p)])}let w=f.filter(S=>!S.stack||g!==null).map(S=>S.container);w.length>0&&await O(n,t,["start",...w])},async publishedPorts(p){return kn(p)},async setAsideSidecars(p){let f=(await l(p)).filter(g=>!g.separate&&!g.stack&&!g.setAside);for(let g of f)await O(n,t,["rm","--force","--ignore",Mo(g.container)]),await O(n,t,["rename",g.container,Mo(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 O(n,t,["rm","--force","--ignore",Mo(g.container)]),await O(n,t,["rename",g.container,Mo(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 O(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",ht(p),"--port",String(Fa),"--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 O(n,t,["ps","--all","--filter",`label=${gn}`,"--format","json"]),w=g.trim()?JSON.parse(g):[],S=new Map;for(let _ of w){let I=_.Labels?.[gn];I&&S.set(I,{spec:_.Labels?.[$m]??"",state:_.State??"",elsewhere:xm(_,e.podBind),joins:_.Labels?.[vm]||null})}let k=new Set;for(let _ of $k(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?mn(_.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 q=e.composeHostService?await e.composeHostService(_):_;if(vk(q))throw new Error("it names host secrets, and this host cannot resolve them");let L=e.hostServiceLocal?.(q,p),v=L?{...q,...L.env&&Object.keys(L.env).length>0?{env:{...q.env,...L.env}}:{},...L.volumes?{volumes:L.volumes}:{},...L.network?{network:L.network}:{}}:q,U=Ft(v.name),F=j===null?Pk(v.name,p):[],B=_k(v,{joins:j,publishes:F});if(I&&I.spec===B&&!(I.state!=="running"&&I.elsewhere)){I.state!=="running"?(await O(n,t,["start",U]),f.push({name:v.name,action:"started",reason:I.state}),k.add(v.name)):j!==null&&k.has(j)&&(await O(n,t,["restart",U]),f.push({name:v.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(v.image),I){for(let[Y,me]of S)me.joins===v.name&&(await O(n,t,["rm","--force","--ignore",Ft(Y)]),S.delete(Y));await O(n,t,["rm","--force","--ignore",U])}await O(n,t,[...await r(),"run","--detach","--name",U,"--label",`${gn}=${v.name}`,"--label",`${$m}=${B}`,...j!==null?["--label",`${vm}=${j}`]:[],...v.healthPath&&v.hostPort>0?["--label",`${jo}=${v.hostPort}:${v.healthPath}`]:[],"--memory",`${v.memoryMb}m`,"--pids-limit",String(zk),"--restart","no",...j!==null?["--network",`container:${Ft(j)}`]:v.network?["--network",v.network]:e.network?["--network",e.network]:[],...(v.volumes??[]).flatMap(Y=>["--volume",Y]),...j===null?[...v.hostPort>0?[[v.hostPort,v.port]]:[],...F].flatMap(([Y,me])=>yn(e.podBind).flatMap(Re=>["--publish",`${Re}:${Y}:${me}`])):[],...v.adminPort&&v.adminHostPort?["--publish",`127.0.0.1:${v.adminHostPort}:${v.adminPort}`]:[],...Do(v.env),v.image,...v.command??[]],{env:{...v.env}}),f.push({name:v.name,action:I?"recreated":"created",reason:D}),j===null&&k.add(v.name)}for(let _ of[...S.keys()].sort((I,j)=>+!S.get(I)?.joins-+!S.get(j)?.joins))await O(n,t,["rm","--force","--ignore",Ft(_)]),f.push({name:_,action:"removed",reason:"it is no longer on the list"});return f},async hostServiceHealth(p){let f=await O(n,t,["ps","--all","--filter",`label=${gn}`,"--format","json"]),g=(f.trim()?JSON.parse(f):[]).map(S=>S.Labels?.[gn]??"").filter(S=>S!=="");if(g.length===0)return[];let w=Date.now()+p;for(;;){let S=[];for(let k of g)S.push(await Am(n,t,k,Ft(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=>Am(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,[...ve(p),"-c","checkpoint"],{timeoutMs:it});let w=Date.now()+g;for(;;){let S=await n(t,[...ve(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 O(n,t,["stop","--time",String(wm),k.container]);S!==null&&await O(n,t,["stop","--time",String(wm),at(p)]),await O(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 O(n,t,["rm","--force","--ignore",S.container]);w!==null&&await O(n,t,["rm","--force","--ignore",at(p)]),await O(n,t,["rm","--force","--ignore","--volumes",H(p).pod])},async removeData(p){await O(n,t,["volume","rm","--force",H(p).pod]);let f=Wn(p);(await n(t,["volume","exists",f])).code===0&&await O(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&&Oa(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 O(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 ${Tt(f)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
503
- `);await O(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 O(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:yp(p,f!==null)}),f!==null){let g=[...ve(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 Ni(p,S))throw new Error(`${p}: TLS for the copies' routers could not be turned on`);await O(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 O(n,t,["exec",..._e,"--interactive",f.pod,"psql","--username","snoutpod_admin","--dbname",w,"--quiet","-tA","-F",Xa,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Jp});return Vp(S)},async applyDbAccess(p,f){let g=H(p),w=await O(n,t,["exec",..._e,"--interactive","--env",To,g.pod,"psql","--username","snoutpod_admin","--dbname",g.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[To]:Op(p,f)},stdin:Rp,timeoutMs:6e4});return Tp(w)},async runProjectSql(p,f){let g=H(p);await O(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=Ft(p),S=Tt(f),k=Tt(g),x=I=>["exec",w,"psql","--username",S,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",I];(await O(n,t,x(`select 1 from pg_database where datname = '${k}'`))).trim()!=="1"&&await O(n,t,x(`create database "${k}"`))},async renameProject(p,f){f!==p.ref&&await O(n,t,["exec",..._e,"--interactive",H(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:e_(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 Ei(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 Ai(p)===!0)return!0;if(Date.now()>=g||await Ei(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?ka(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?ka(f.stdout):null},async setReadOnly(p,f){let g=H(p);await O(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 Am(e,t,n,r,o=Mm){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=Tk(c?.[jo]);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 Xk(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 Qk(e,t){return(e.State??"").toLowerCase()==="created"&&typeof e.Created=="number"&&e.Created>0&&t-e.Created*1e3>Zk}function ii(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function e_(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..${Tm} 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(${Rm/Tm});`," end loop;",` raise exception 'database % still has sessions after ${Rm}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 t_(e){if(!e?.issuer)return[];try{return["--env",`${Xp}=${Zp(e.issuer)}`]}catch{return[]}}function n_(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var De,si,He,hn,Jn,ym,wm,yk,wk,Nm,fn,km,gn,$m,vm,Im,di,jo,Mm,Ok,Pm,_e,it,Ck,Em,Kk,Lm,qk,Wk,zk,Yk,Zk,Rm,Tm,Oo=W(()=>{"use strict";Me();Sa();ko();po();Ta();Ia();Na();zn();Gp();_a();zp();Yp();za();Qp();Ca();om();Ra();Wa();lm();Ao();gm();De="snoutpod.ref",si="snoutpod.generation",He="snoutpod.service",hn="snoutpod.services-generation",Jn="snoutpod.service-spec",ym="snoutpod.service-pod";wm=10,yk=/^[a-z][a-z0-9-]{0,30}$/;wk=/^[a-z][a-z0-9_]{0,62}$/;Nm="-set-aside",fn=1024*1024,km=/^[0-9a-f]{12,64}$/;gn="snoutpod.host-service",$m="snoutpod.host-service-spec",vm="snoutpod.host-service-joins";Im=/^[A-Za-z_][A-Za-z0-9_]*$/;di=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,jo="snoutpod.health";Mm=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()})},Ok="crun";Pm="/usr/sbin/nft",_e=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp -c synchronous_commit=local"],it=3e4,Ck=3e4;Em=200,Kk=1024,Lm=512,qk=45e3,Wk=1e3,zk=4096;Yk=8;Zk=12e4;Rm=30,Tm=300});var Qm={};Li(Qm,{DEFAULT_LOCAL_MEMORY_MB:()=>Gm,DEFAULT_LOCAL_PORT:()=>i_,ENGINE:()=>ie,ENGINE_NAME:()=>mi,IMAGE:()=>hi,NETWORK:()=>Ot,NEW_PROJECT_MAJOR:()=>gi,NO_BLACKHOLE_ROUTES:()=>zm,PUBLIC_IMAGE:()=>u_,PUBLISHED_MAJORS:()=>wn,capacity:()=>Wm,chooseLocalImage:()=>Zn,dropIfUnused:()=>Xm,ensureImage:()=>p_,ensureNetwork:()=>y_,exec:()=>we,hasData:()=>Ho,localImageFor:()=>qm,localPsql:()=>S_,localRuntime:()=>h_,localSpec:()=>Ym,localStatus:()=>yi,localUri:()=>Vm,networkPolicyInPlace:()=>Jm,newLocalPassword:()=>c_,newLocalRef:()=>l_,podLog:()=>Zm,podName:()=>bn,podmanReady:()=>f_,removeLocal:()=>$_,staleImage:()=>fi,startLocal:()=>b_,stopLocal:()=>__,volumePgVersion:()=>Uo});import{randomBytes as Bm}from"node:crypto";import{existsSync as Um}from"node:fs";import{platform as Bo}from"node:os";import{delimiter as r_,join as o_}from"node:path";function s_(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Bo()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(r_))if(t&&e.some(n=>Um(o_(t,n))))return"podman";return Um("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Zn(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Xn(gi);let n=Number(pi(e));if(!wn.includes(n)){let r=e.trim()||"(empty)",o=wn.map(s=>Xn(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${wn.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Xn(n)}function Xn(e){return{name:`${a_}:${e}`,published:`${Hm}:${e}`,major:e}}function l_(){return Au(e=>Bm(e))}function c_(){return Bm(18).toString("base64url")}async function p_(e,t=we,n=Zn(null)){let r=n.published;if(!r)return null;if((await t(ie,["image","exists",n.name])).code===0)return await m_(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(ie,["pull",r],{timeoutMs:Km}).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 m_(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:Km}).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 f_(e=d_,t=hi){if(mi!=="podman")return`SNOUTPOD_ENGINE=${mi}: 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 g_(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 Ho(e){let t=Bo()==="darwin"||Bo()==="win32";return Mp(we,e,{binary:ie,look:t?g_:void 0})}async function Uo(e,t=we,n=Bo()){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 wn){let c=Xn(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 (${wn.map(a=>Xn(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 qm(e,t=Uo){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Zn(null));se(e);let n=t===Uo?Fo.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Zn(o)}catch(s){throw new Error(`${bn(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Uo&&(Fo.set(e,r),r.catch(()=>Fo.delete(e))),r}async function Wm(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function h_(e){return jm({exec:we,binary:ie,network:Ot,hasData:Ho,capacity:Wm,repository:e})}function bn(e){return H(e).pod}async function y_(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await w_()||await we(ie,So({name:Ot}));return}let t=So({name:Ot,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Jm())return;await e?.(),await we(ie,["network","rm","--force",Ot]).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(`${zm}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ot} with egress blocked${n?`: ${n}`:""}`)}async function w_(){return(await we(ie,["network","inspect",Ot,"--format","json"])).code===0}async function Jm(){let e=await we(ie,["network","inspect",Ot,"--format","json"]);if(e.code!==0)return!1;try{return pp(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Vm(e){let t=H(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Ym(e,t={}){return se(e.ref),{ref:e.ref,generation:1,image:t.image??hi,memoryMb:t.memoryMb??Gm,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 Yn(e,t){e.onProgress?.(t)}async function b_(e,t,n={}){let r=n.image??(await qm(t.ref,n.pgVersion)).name,o=Ym(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Ho,c=(await e.list()).find(u=>u.ref===t.ref),i=null;if(c?c.status==="stopped"&&(i=await(n.staleImage??fi)(t.ref,r))?(Yn(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??Xm)(i)):c.status==="stopped"?(Yn(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Yn(n,"The database is already running."),c.status==="running"&&await(n.staleImage??fi)(t.ref,r)&&Yn(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Yn(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 k_(t.ref,s,n.podLog??Zm));return await e.applyOwnerPassword(o),await e.createServices(o),yi(e,t,n)}function Fm(e){return e.trim().replace(/^sha256:/,"")}async function fi(e,t){let[n,r]=await Promise.all([we(ie,["container","inspect","--format","{{.Image}}",bn(e)]).catch(()=>null),we(ie,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Fm(n.stdout),s=Fm(r.stdout);return o&&s&&o!==s?o:null}async function Xm(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 S_(e,t){let n=H(e.ref);return we(ie,["exec","--interactive","-e","PGPASSWORD",bn(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 Zm(e){let t=await we(ie,["logs","--tail","20",bn(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function k_(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${bn(e)} never became ready in ${t}ms
647
+ `});function Rm(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 Am,jR,UR,Tm=J(()=>{"use strict";rr();Ko();Am="SNOUTPOD_OAUTH_ISSUER",jR=256*1024,UR=5*6e4});function kn(e){return`${z(e).pod}-guard`}function Om(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:`${qo}/${wi}`}:{},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:`${qo}/epoch`,LEPIS_GUARD_STATUS:`${qo}/status.json`,LEPIS_GUARD_LEASE_MS:String(r),LEPIS_GUARD_TICK_MS:String(Math.max(1,Math.min(1e3,Math.floor(r/5))))}}function Nm(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 Im(e,t,n){if(ie(e),!n.hostDir.startsWith("/")||n.hostDir.includes(":"))throw new Error(`not a host directory for a volume: ${n.hostDir}`);return["run","--detach","--name",kn(e),"--label",`${zo}=${e}`,"--label",`${bi}=${n.fingerprint}`,"--network","host","--userns","auto","--volume",`${n.hostDir}:${qo}:U`,"--memory","64m","--pids-limit","64","--restart","no",...n.envNames.flatMap(r=>["--env",r]),t.image,"guard"]}function Cm(e,t,n,r){let o=2*(r.leaseMs??1e4)/1e3;return n-e<=o+60?!1:t===null||n-t>o}var wi,qo,zo,bi,Mm=J(()=>{"use strict";De();wi="witness-ca.pem";qo="/guard",zo="snoutpod.guard",bi="snoutpod.guard-spec"});function jm(e){return ie(e),{anchor:`snoutpod-${e}-copy`,router:`snoutpod-${e}-copy-lepis`,service:t=>`snoutpod-${e}-copy-${_n(t)}`}}var Lm,Dm,Um=J(()=>{"use strict";De();Ko();Lm="snoutpod.copy",Dm="snoutpod.copy-service"});import{createHash as Dk}from"node:crypto";function mt(e){return`${z(ie(e)).pod}-${Wo}`}function Hm(e){return pt(e)?(e.services??[]).filter(t=>t.name!==he):[]}function Wt(e,t){return pt(e)&&t.name!==he}function Gm(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 ki(e,t){if(t.hostPort===null||t.hostPort===void 0)return null;let n=Gm(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 _i(e,t){let n=ki(e,t),r=Km(e,t);return n===null?t:{...t,hostPort:n,...r===null?{}:{adminHostPort:r}}}function Km(e,t){return!t.adminPort||!t.adminHostPort?null:ki(e,{name:`${t.name} admin`,hostPort:t.adminHostPort})}function vi(e){return Hm(e).flatMap(t=>{let n=Km(e,t);return n===null||!t.adminPort?[]:[[n,t.adminPort]]}).sort((t,n)=>t[0]-n[0])}function $i(e){let t=Hm(e);return t.length>0&&Gm(e),t.flatMap(n=>{let r=ki(e,n);return r===null?[]:[[r,n.port]]}).sort((n,r)=>n[0]-r[0])}function qm(e,t){let n=vi(e);return Dk("sha256").update(`stack:${t}:`).update(JSON.stringify($i(e))).update(n.length===0?"":`admin:${JSON.stringify(n)}`).digest("hex").slice(0,32)}var Pt,Si,Wo,Fm,Bm,zm=J(()=>{"use strict";De();Bo();Pt="snoutpod.stack",Si="snoutpod.stack-spec",Wo="stack",Fm=16,Bm=16});import{createHash as sr,randomBytes as Uk}from"node:crypto";import{access as Fk,constants as Bk,mkdir as Wm,readFile as Hk,readdir as Gk,rename as Kk,rm as qk,writeFile as zk}from"node:fs/promises";import{delimiter as Wk,join as Vt}from"node:path";function cf(e,t,n,r){return sr("sha256").update(Ai(e)).update(e.smtpSecretEnv&&t?sr("sha256").update(t).digest("hex"):"").update(n===void 0?"":`router:${n}`).update(r===void 0?"":`@${r}`).digest("hex").slice(0,32)}function Jk(e,t){return li(t),sr("sha256").update(`separated:${t}:${bn}:`).update(cf(e)).digest("hex").slice(0,32)}function Ai(e){if(Array.isArray(e))return`[${e.map(Ai).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)}:${Ai(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function _n(e){if(!Vk.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Dt(e){if(!Yk.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Xk(e,t,n,r,o){if(r===void 0)return`postgres://${Dt(t)}:${n}@127.0.0.1:5432/${ie(e)}`;if(o!==void 0&&o!==$t(e))throw new Error(`${o} is not ${e}'s router`);return`postgres://${`${Dt(t)}:${n}@${o??"127.0.0.1"}`}:${Ue(r,"router port")}/${ie(e)}?sslmode=disable`}function Zk(e,t,n){let r=`${t}=`,o=e.split(`
648
+ `).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 Yo(e,t){if(!t.dbUriEnv||!t.dbUriRole||t.name===he)return;let n=(e.services??[]).find(r=>r.name===he);if(n)return fi.includes(t.dbUriRole)?n.port:pt(e)?bn:void 0}function Ri(e,t){return pt(e)&&Yo(e,t)!==void 0?$t(e.ref):void 0}function Ym(e,t,n,r){let o=ci(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]=Xk(e.ref,s,a,Yo(e,t),Ri(e,t))}if(t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),t.adminPortEnv&&t.adminPort){if(!uf.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 Xm(e,t){return`${z(e).pod}-${_n(t)}`}function Qk(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 Jo(e){return`${e}${df}`}function Qm(e){return typeof e=="string"&&e.endsWith(df)}function xi(e){let t=e.Names?.[0];return t===void 0?void 0:t.replace(/^\//,"")}function Jt(e){return`snoutpod-host-${_n(e)}`}function e_(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 sr("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 t_(e){return[...e.filter(t=>!t.joins),...e.filter(t=>t.joins)]}function n_(e){return Object.keys(e.secretEnv??{}).length>0||Object.keys(e.files??{}).length>0||!!e.envFrom||!!e.databaseUrl}function r_(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 Oi(e){return!e.adminPort||!e.adminHostPort?null:[Ue(e.adminHostPort,`${e.name}: adminHostPort`),Ue(e.adminPort,`${e.name}: adminPort`)]}function o_(e,t){let n=[],r=xn(t);for(let o of e??[]){if(_n(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=Oi(o);if(s&&n.push("--publish",`127.0.0.1:${s[0]}:${s[1]}`),o.name===he){let a=o.hostPort+Uo;Ue(a,`${o.name}: admin hostPort`);for(let c of r)n.push("--publish",`${c}:${a}:${jo}`)}}return n}function s_(e,t,n){if(ie(e.ref),t.name!==he)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=li(e.routerPodPort),o=([s,a])=>xn(n.bind).flatMap(c=>["--publish",`${c}:${Ue(s,"router hostPort")}:${a}`]);return["run","--detach","--name",$t(e.ref),"--label",`${Lt}=${e.ref}`,"--label",`${Pn}=${e.servicesGeneration??0}`,"--label",`${or}=${n.fingerprint}`,"--network",n.network,"--userns=auto","--memory",`${t.memoryMb}m`,"--pids-limit",String(gf),"--restart","no",...o([r.postgres,t.port]),...o([r.admin,jo]),...o([r.passthrough,bn]),...pf(t),"--volume",`${nr(e.ref)}:${Fo}:ro`,...n.envNames.flatMap(s=>["--env",s]),t.image]}function a_(e,t){if(ie(e.ref),!t.network)throw new Error(`${e.ref}: a services-only pod needs the pod network`);return["run","--detach","--name",mt(e.ref),"--label",`${Pt}=${e.ref}`,"--label",`${Si}=${t.fingerprint}`,"--network",t.network,"--userns=auto","--memory",`${Fm}m`,"--pids-limit",String(Bm),"--restart","no","--stop-signal","SIGKILL",...$i(e).flatMap(([n,r])=>xn(t.bind).flatMap(o=>["--publish",`${o}:${Ue(n,"stack hostPort")}:${Ue(r,"service port")}`])),...vi(e).flatMap(([n,r])=>["--publish",`127.0.0.1:${Ue(n,"stack adminHostPort")}:${Ue(r,"admin port")}`]),"--entrypoint","sleep",e.image,"infinity"]}function i_(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 xn(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 Vo(e){let t=[];for(let n of Object.keys(e)){if(!uf.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function pf(e,t={}){if(t.adminPublished&&e.adminHealthPath&&Oi(e)){if(!Ti.test(e.adminHealthPath))throw new Error(`${e.name}: not a health path: ${JSON.stringify(e.adminHealthPath)}`);return["--label",`${Xo}=${e.adminHostPort}:${e.adminHealthPath}`]}let n=e.healthPath;if(n==null||n==="")return[];if(!Ti.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",`${Xo}=${e.hostPort}:${n}`])}function l_(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||!Ti.test(r)?null:{port:n,path:r}}async function d_(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Fk(o,Bk.X_OK));for(let o of n.split(Wk))if(o)try{let s=Vt(o,e);return await r(s),s}catch{}return null}function u_(){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(`
649
+ `)}function m_(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function f_(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 Ni(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function g_(e,t){return t.image===null||t.data===null||t.image===t.data?null:`${e} was not started: its data directory was made by Postgres ${t.data} and it was handed a Postgres ${t.image} image. A data directory starts only on the major that made it. Its data is untouched, and it starts again on a ${t.data} image.`}async function h_(e,t,n,r){return{image:await y_(e,t,r),data:await w_(e,t,n)}}async function y_(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 f_(r,n)}async function w_(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",z(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?Ni(s.stdout):null}catch{return null}}function ff(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:z(t.ref).pod})).sort((t,n)=>t.ref<n.ref?-1:t.ref>n.ref?1:0)}function b_(e,t,n){if(t.size>1)return`the ${Ae} 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 ${Ae} network holds ${r}'s copies, and another project may not share its bridge`}function S_(e,t){let n=new Map,r=new Set(t.length>0?ff(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=Fp(s,a),i=n.get(c)??{group:s,peer:a,containers:[]};i.containers.push(z(o.ref).pod),o.ref===s&&i.containers.push($t(o.ref)),n.set(c,i)}}return n}function k_(e,t){let n=[];for(let[r,o]of[...t].sort(([s],[a])=>s<a?-1:s>a?1:0)){if(!zt(o))continue;let s=To(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=wn(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 rf(e,t){let n=xn(t);return(e.Ports??[]).some(r=>{let o=r.host_ip??"";return o!==""&&o!=="0.0.0.0"&&o!=="::"&&!n.includes(o)})}function x_(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:of;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,of)}`),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 E_(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??c_;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await d_(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function hf(e){let t=e.binary??"podman",n=e.exec,r=E_(e),o=e.probe??mf;async function s(p){try{await n(t,["exec",...$e,"--interactive",z(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:p_,stdin:u_()})}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=${Lt}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${Pt}`,"--format","json"])]),w=await A(p.trim()?JSON.parse(p):[],!0),S=new Map;for(let K of g.trim()?JSON.parse(g):[]){let q=h(K);q&&S.set(q,(S.get(q)??!1)||K.State==="running")}let k=[...w.flatMap(K=>{let q=K.Labels?.[We]?K.Labels?.[Pt]:void 0;return q===void 0?[K]:S.has(q)?[S.get(q)?K:{...K,State:"exited"}]:[]}),...(f.trim()?JSON.parse(f):[]).flatMap(K=>{let q=K.Labels?.[Lt];return q?[{...K,Labels:{...K.Labels,[je]:q,[We]:he}}]:[]})],x=new Map,_=new Map,I=new Map,U=new Map,Y=new Set([...S.keys(),...(f.trim()?JSON.parse(f):[]).flatMap(K=>K.Labels?.[Lt]??[])]);for(let K of k){let q=K.Labels?.[je],D=K.Labels?.[We];if(!q||!D)continue;if(Qm(xi(K))){U.set(q,[...U.get(q)??[],D]);continue}let Q=x.get(q)??[];Q.push(D),x.set(q,Q),Ei(K.State)==="stopped"&&_.set(q,[..._.get(q)??[],D]);let ge=Number(K.Labels?.[Pn]??0),Ne=Number.isInteger(ge)?ge:0;I.set(q,Math.max(I.get(q)??Ne,Ne))}let j=k.filter(K=>K.Labels?.[je]&&!K.Labels?.[We]),$=j.map(K=>O_(K,Date.now())?"stopped":Ei(K.State)),B=await R_(j,A_,(K,q)=>$[q]==="running"?Promise.resolve(!0):e.hasData(K.Labels?.[je]??""));return j.map((K,q)=>{let D=K.Labels?.[je]??"",Q=Number(K.Labels?.[Pi]);return{ref:D,status:$[q]??"unknown",generation:Number.isInteger(Q)?Q:null,hasData:B[q]??!1,services:x.get(D)??[],servicesStopped:_.get(D)??[],...Y.has(D)?{separated:!0}:{},...U.has(D)?{setAside:U.get(D)}:{},...rf(K,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?.[We]).length}async function i(p){let f=await(e.majors??((g,w)=>h_(n,t,g,w)))(p.ref,p.image);return g_(p.ref,f)}async function u(p){let f=z(p.ref),g=await i(p);if(g)throw new Error(g);let w=e.credentials,S=await ym(e.repository,w?()=>w.forProject(p.ref,p.bucket):null),k={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...bm(S)},x=[...await r(),"run","--detach","--name",f.pod,"--label",`${je}=${p.ref}`,"--label",`${Pi}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,"--pids-limit",String(__),...C_(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?xn(e.podBind).flatMap(_=>["--publish",`${_}:${p.port}:5432`]):[],...o_(ii(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"]:[],...I_(p.oauth),...x_(p.tuning).flatMap(_=>["--env",_]),"--env",`SNOUTPOD_RETENTION_FULL=${lp(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...wm(S),p.image,"snoutpod-postgres"];await N(n,t,x,{env:k})}async function l(p){return(await H(p)).services}async function m(p){return(await l(p)).filter(f=>!f.setAside)}function h(p){return p.Labels?.[We]?void 0:p.Labels?.[Pt]}async function A(p,f){let g=e.inPlaceRoot;if(g===void 0)return p;let w=await Gk(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(!Zm.test(_))continue;if(!S.has(_)){f&&await qk(Vt(g,x),{force:!0});continue}let I=Qk(await Hk(Vt(g,x),"utf8").catch(()=>""));I!==null&&k.set(_,I)}return p.map(x=>{let _=x.Id&&x.Labels?.[We]?k.get(x.Id):void 0;return _===void 0?x:{...x,Labels:{...x.Labels,[or]:_.spec,[Pn]:String(_.generation)}}})}async function E(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 se(p,f,g){let w=g.swap===-1?["--memory-swap","-1"]:g.swap>g.bytes?["--memory-swap",`${f+Math.floor((g.swap-g.bytes)/vn)}m`]:[];if((await n(t,["update","--memory",`${f}m`,...w,p])).code!==0)return!1;let k=await E(p);return k!==null&&k.bytes===f*vn}async function F(p,f){let g=e.inPlaceRoot;if(g===void 0||!Zm.test(p))return;await Wm(g,{recursive:!0});let w=Vt(g,`${p}.json`);await zk(`${w}.tmp`,JSON.stringify(f)),await Kk(`${w}.tmp`,w)}async function H(p){let[f,g,w]=await Promise.all([N(n,t,["ps","--all","--filter",`label=${je}=${ie(p)}`,"--filter",`label=${We}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${Lt}=${p}`,"--format","json"]),N(n,t,["ps","--all","--filter",`label=${Pt}=${p}`,"--format","json"])]),S=await A(f.trim()?JSON.parse(f):[],!1),k=[];for(let _ of S){let I=_.Labels?.[We];if(!I)continue;let U=Number(_.Labels?.[Pn]??0),Y=Qm(xi(_));k.push({name:I,container:Y?xi(_)??"":Xm(p,I),id:_.Id??"",spec:_.Labels?.[or]??"",pod:_.Labels?.[Jm]??"",running:_.State==="running",generation:Number.isInteger(U)?U:0,separate:!1,stack:_.Labels?.[Pt]===p,setAside:Y})}for(let _ of g.trim()?JSON.parse(g):[]){if(_.Labels?.[Lt]!==p)continue;let I=Number(_.Labels?.[Pn]??0);k.push({name:he,container:$t(p),id:_.Id??"",spec:_.Labels?.[or]??"",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):[])h(_)===p&&(x={id:_.Id??"",spec:_.Labels?.[Si]??"",running:_.State==="running"});return{services:k,anchor:x}}async function M(p){let f=await n(t,["container","inspect","--format","{{.Id}}",p]);return f.code===0?f.stdout.trim():""}function de(p,f){return f?.databaseOnly?{services:p.services.filter(g=>!g.separate&&!g.stack),anchor:null}:p}async function G(p,f,g=new Map){let w=[];for(let Y of f){let j=Y.dbUriRole??null;Y.dbUriEnv&&j&&!w.includes(j)&&w.push(Dt(j))}let S=new Map;if(w.length===0)return S;let k=await e.fixedRolePasswords?.()??new Map,x={},_=[],I=[];w.some(Y=>fi.includes(Y))&&I.push(km,_m);for(let Y of w.filter(j=>gi.includes(j)))I.push(vm(Y));let U=new Map;if(p.derivedServicePasswords===!0)for(let Y of w.filter(di))U.set(Y,await ui(p.adminPassword,Y));return w.forEach((Y,j)=>{let $=k.get(Y)??(U.has(Y)?void 0:g.get(Y));if($!==void 0){S.set(Y,$);return}let B=U.get(Y)??Uk(24).toString("base64url"),K=`SNOUTPOD_SERVICE_PASSWORD_${j}`;S.set(Y,B),x[K]=B,_.push("--env",K),I.push(`\\getenv service_password_${j} ${K}`),I.push(`\\set service_role_${j} ${Y}`),I.push(`alter role :"service_role_${j}" with password :'service_password_${j}';`)}),I.push(""),await N(n,t,["exec",...$e,"--interactive",..._,z(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",z(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:x,stdin:I.join(`
650
+ `)}),S}async function oe(p,f,g){let w=new Map;for(let x of g){let _=x.dbUriRole,I=f.find(j=>j.name===x.name);if(!x.dbUriEnv||!_||w.has(_)||I===void 0)continue;let U=await n(t,["container","inspect","--format","{{range .Config.Env}}{{println .}}{{end}}",I.container]),Y=U.code===0?Zk(U.stdout,x.dbUriEnv,Dt(_)):null;Y!==null&&w.set(_,Y)}if(w.size===0)return w;let S=await n(t,["exec",...$e,z(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(`
651
+ `)){let[_,I]=x.trim().split(" ");_&&I&&k.set(_,I)}for(let[x,_]of w)Xa(k.get(x)??"",_)||w.delete(x);return w}async function Z(p){let f=nr(p.ref);await N(n,t,["volume","create","--ignore",f]),await N(n,t,[...await r(),"run","--rm","--network","none","--volume",`${f}:${Fo}`,"--entrypoint","bash",p.image,"-c",Vp])}async function me(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=m_(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 rt(p){let f=await n(t,["container","inspect","--format","{{json .HostConfig.PortBindings}}",z(p).pod]);return f.code===0?i_(f.stdout):null}async function ht(p,f={}){let g=p.services??[],w=pt(p)?[...g.filter(P=>P.name===he),...g.filter(P=>P.name!==he)]:g,{services:S,anchor:k}=await H(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])),U=P=>pt(p)&&P.name===he,Y=w.filter(P=>Wt(p,P)),j="";if(Y.length>0){if(!w.some(P=>P.name===he))throw new Error(`${p.ref}: a services-only pod reaches its database through the router, and spec.services has no ${he}`);if(!e.network)throw new Error(`${p.ref}: a services-only pod needs the pod network`);if(Y.some(P=>P.name===Wo))throw new Error(`${p.ref}: a service may not be called ${Wo}, which is the services-only pod's own name`);j=qm(p,e.network)}let $=Y.length>0&&k!==null&&k.running&&k.id!==""&&k.spec===j,B=w.some(P=>!U(P)&&!Wt(p,P))?await M(z(p.ref).pod):"",K=w.some(P=>!U(P)&&!Wt(p,P)&&Oi(P))?new Set(await rt(p.ref)??[]):new Set,q=$?k?.id??"":"",D=P=>Wt(p,P)?q:B,Q=P=>U(P)?Jk(P,p.routerPodPort):cf(Wt(p,P)?_i(p,P):P,_,Yo(p,P),Ri(p,P)),ge=new Map(w.map(P=>[P.name,Q(P)])),Ne=p.servicesGeneration??0,Ye=new Set;if(e.inPlaceRoot!==void 0)for(let P of w){let ye=I.get(P.name),Me=D(P);if(U(P)||!ye||!ye.running||ye.id===""||Me===""||ye.pod!==Me||ye.spec===ge.get(P.name))continue;let et=await E(ye.container);et===null||et.bytes%vn!==0||et.bytes/vn>=P.memoryMb||Q({...P,memoryMb:et.bytes/vn})===ye.spec&&await se(ye.container,P.memoryMb,et)&&(await F(ye.id,{spec:ge.get(P.name)??"",generation:Ne}),Ye.add(P.name),e.log?.(`${p.ref}: ${P.name} took ${P.memoryMb} MB in place (was ${et.bytes/vn} MB), not stopped`))}let Re=new Set(w.filter(P=>{let ye=I.get(P.name);if(Ye.has(P.name))return!1;if(!ye||!ye.running||ye.spec!==ge.get(P.name))return!0;let Me=D(P);return!U(P)&&(Me===""||ye.pod!==Me)}).map(P=>P.name));if(f.renewPasswords)for(let P of w)P.dbUriEnv&&P.dbUriRole&&Re.add(P.name);if(Re.size===0&&Ye.size===0&&w.length>0&&!x.some(P=>P.generation>=Ne))for(let P of w)Re.add(P.name);let Qi=P=>P.dbUriEnv&&P.dbUriRole?P.dbUriRole:null;for(let P=!0;P;){P=!1;let ye=new Set(w.filter(Me=>Re.has(Me.name)).map(Qi));for(let Me of w){let et=Qi(Me);!Re.has(Me.name)&&et!==null&&ye.has(et)&&(Re.add(Me.name),P=!0)}}let el=new Set(w.filter(P=>Re.has(P.name)).map(P=>P.image));(Y.length>0&&!$||Re.has(he))&&el.add(p.image);for(let P of el)await me(P);let Mg=P=>f.keepSeparated===!0&&(P.separate||P.stack)&&!Re.has(P.name);for(let P of x)(Re.has(P.name)||!ge.has(P.name))&&!Mg(P)&&await N(n,t,["rm","--force","--ignore",P.container]);if(k!==null&&!$&&!(f.keepSeparated===!0&&Y.length===0)&&await N(n,t,["rm","--force","--ignore",mt(p.ref)]),Y.length>0&&!$){if(await N(n,t,[...await r(),...a_(p,{network:e.network,bind:e.podBind,fingerprint:j})]),q=await M(mt(p.ref)),q==="")throw new Error(`${p.ref}: the services-only pod was made and its id could not be read`);await f.afterStart?.(mt(p.ref))}let fr=w.filter(P=>Re.has(P.name)),tl=S.filter(P=>P.setAside),ms=await G(p,fr,tl.length>0&&!f.renewPasswords?await oe(p,tl,fr):new Map);fr.some(P=>P.name===he)&&await Z(p);for(let P of fr){if(U(P)){let nl=ci(Wp(p.ref,P.env),p.adminPassword);await N(n,t,[...await r(),...s_(p,P,{network:e.network,bind:e.podBind,fingerprint:ge.get(P.name)??"",envNames:Vo(nl).filter(Dg=>Dg!=="--env")})],{env:nl}),await f.afterStart?.($t(p.ref));continue}let ye=Wt(p,P),Me=ye?mt(p.ref):z(p.ref).pod,et=Xm(p.ref,P.name),Lg=[...await r(),"run","--detach","--name",et,"--label",`${je}=${p.ref}`,"--label",`${Pi}=${p.generation}`,"--label",`${We}=${P.name}`,"--label",`${Pn}=${p.servicesGeneration??0}`,"--label",`${or}=${ge.get(P.name)??""}`,"--label",`${Jm}=${D(P)}`,...ye?["--label",`${Pt}=${p.ref}`]:[],"--network",`container:${Me}`,"--userns",`container:${Me}`,"--memory",`${P.memoryMb}m`,"--pids-limit",String(gf),"--restart","no",...pf(ye?_i(p,P):P,{adminPublished:ye||P.adminHostPort!==null&&P.adminHostPort!==void 0&&K.has(P.adminHostPort)}),...P.name===he?["--volume",`${nr(p.ref)}:${Fo}:ro`]:[],...Vo(Ym(p,P,ms,_)),P.image];Re.has(he)||await Je(p,P,ms),await N(n,t,Lg,{env:Ym(p,P,ms,_)})}}async function Je(p,f,g){let w=Yo(p,f),S=f.dbUriRole,k=S?g.get(S):void 0;if(w===void 0||w===bn||!S||k===void 0)return;let x=z(p.ref),_=Ri(p,f)??"127.0.0.1",{waitMs:I,retryMs:U}=e.routerLogin??{waitMs:v_,retryMs:$_},Y=Date.now()+I;for(let j=1;;j++){let $=await n(t,["exec","--env","PGPASSWORD","--env","PGSSLMODE=disable","--env","PGCONNECT_TIMEOUT=3",x.pod,"psql","--host",_,"--port",String(w),"--username",Dt(S),"--dbname",x.database,"--no-password","--tuples-only","--no-align","--command","select 1"],{env:{PGPASSWORD:k}});if($.code===0){e.log?.(j===1?`${p.ref}: the router took ${S}'s password at once, ${f.name} starts`:`${p.ref}: the router took ${S}'s password on try ${j}, after ${j-1} refused, ${f.name} starts`);return}if(Date.now()>=Y){let B=`${$.stderr}
652
+ ${$.stdout}`.trim().split(`
653
+ `).filter(Boolean).pop()??`exit ${$.code}`;throw new Error(`${p.ref}: the router did not take ${S}'s new password within ${I}ms (${j} tries; last: ${B}), so ${f.name} was not started`)}await new Promise(B=>setTimeout(B,U))}}async function Wi(p,f){if(f%6!==0)return!1;let g=await n(t,["container","inspect","--format","{{.State.Status}}",z(p).pod]);return g.code===0&&/^(exited|stopped|dead)$/.test(g.stdout.trim())}function Pe(p){return["exec",...$e,z(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres"]}async function cg(p){let f=await n(t,[...Pe(p),"-tAc",xp]);return f.code===0?Ep(f.stdout):null}async function dg(p){let f=await n(t,[...Pe(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 Ji(p){let f=await n(t,[...Pe(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 ug(p,f){let g=x=>n(t,[...Pe(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 pg(p){let f=await n(t,[...Pe(p),"-tAc",dp],{timeoutMs:ft});return f.code===0?up(f.stdout):null}async function mg(p,f){let g=new Map;for(let S of f){if(typeof S!="string"){ie(S.ref);let k=vp(S);k&&g.set(S.ref,k);continue}ie(S),g.set(S,z(S).pod)}if(g.size===0)return new Map;let w=await n(t,["exec",z(p).pod,"timeout",String(10),"sh","-c",$p,"sh",...g.values()]);return w.code===0?Pp(w.stdout,g):null}async function fg(p,f){ie(f);let g=[];for(let x of[p,f]){let _=z(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 U=await n(t,["volume","inspect","--format","{{.Mountpoint}}",_]),Y=U.code===0?U.stdout.trim().replace(/\/+$/,""):"";if(!Y)throw new Error(`the volume ${_} could not be found, so nothing was moved`);g.push(Y)}let S=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",za,"sh",g[0],g[1]]):await n("sh",["-c",za,"sh",g[0],g[1]]),k=hp(S.stdout,S.code);if(k)throw new Error(k)}async function gg(p,f){ie(p),ie(f);for(let _ of[p,f]){let I=z(_).pod,U=await n(t,["container","inspect","--format","{{.State.Status}}",I]);if(U.code===0&&U.stdout.trim()==="running")throw new Error(`${I} is running, so no data was moved`)}let g=z(p).pod;(await n(t,["volume","exists",g])).code!==0&&await N(n,t,["volume","create",g]);let w=[];for(let _ of[g,z(f).pod]){let I=await n(t,["volume","inspect","--format","{{.Mountpoint}}",_]),U=I.code===0?I.stdout.trim().replace(/\/+$/,""):"";if(!U)throw new Error(`the volume ${_} could not be found, so nothing was moved`);w.push(U)}let k=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",Wa,"sh",w[0],w[1]]):await n("sh",["-c",Wa,"sh",w[0],w[1]]),x=yp(k.stdout,k.code);if(x)throw new Error(x)}async function hg(p){return(await n(t,[...Pe(p),"-v","ON_ERROR_STOP=1",...Ja.flatMap(g=>["-c",g])])).code===0}async function yg(p){let f=e.guardRoot;if(!f)return;let g=await N(n,t,["ps","--all","--filter",`label=${zo}`,"--format","json"]),w=new Map((g.trim()?JSON.parse(g):[]).flatMap(k=>{let x=k.Labels?.[zo];return x?[[x,{fingerprint:k.Labels?.[bi]??"",status:Ei(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",kn(k)]);for(let{spec:k,guard:x}of p){if(k.port===null)continue;let _=Nm(x,k.port,k.generation,j=>sr("sha256").update(j).digest("hex")),I=w.get(k.ref);if(I?.fingerprint===_){if(I.status!=="running")await N(n,t,["start",kn(k.ref)]);else{let j=Vt(f,k.ref,"status.json"),$=await n(t,["unshare","stat","-c","%Y",j]),B=$.code===0?Number($.stdout.trim()):null;Cm(I.startedAt,Number.isFinite(B)?B:null,Math.floor(Date.now()/1e3),x)&&await N(n,t,["restart",kn(k.ref)])}continue}await N(n,t,["rm","--force","--ignore",kn(k.ref)]);let U=Vt(f,k.ref);await Wm(U,{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,Vt(U,wi)]),await me(x.image);let Y=Om(x,k.port,k.adminPassword);await N(n,t,[...await r(),...Im(k.ref,x,{hostDir:U,fingerprint:_,envNames:Vo(Y).filter(j=>j!=="--env")})],{env:Y})}}async function wg(p){return(await n(t,[...Pe(p),"-v","ON_ERROR_STOP=1","-c",qa],{timeoutMs:ft})).code===0}async function bg(p,f){return(await n(t,[...Pe(p),"-v","ON_ERROR_STOP=1",...mp(f).flatMap(w=>["-c",w])],{timeoutMs:ft})).code===0}async function Sg(){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],U=_.Labels?.[$n],Y=_.Labels?.[Lt];if(Y)return{name:$t(Y),ref:Y,service:!1};let j=_.Labels?.[Pt];if(j&&!_.Labels?.[We])return{name:mt(j),ref:j,service:!1};let $=_.Labels?.[Lm],B=_.Labels?.[Dm];if($&&(!B||B===he)){let K=jm($);return{name:B?K.router:K.anchor,ref:$,service:!1}}return I&&!_.Labels?.[We]?{name:z(I).pod,ref:I,service:!1}:U?{name:Jt(U),ref:U,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(`
654
+ `).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 kg(p,f){if(!e.quotaRequests||!Number.isInteger(f)||f<1)return!1;let{writeFile:g,rename:w}=await import("node:fs/promises"),S=z(p).pod,k=`${e.quotaRequests}/${S}`;return await g(`${e.quotaRequests}/.${S}.tmp`,`${f}
655
+ `),await w(`${e.quotaRequests}/.${S}.tmp`,k),!0}async function Vi(p,f,g){if(!Number.isInteger(g)||g<1)return!1;let w=await n(t,[...Pe(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,[...Pe(p),"-v","ON_ERROR_STOP=1","-c",`alter system set ${f} = '${g}MB'`,"-c","select pg_reload_conf()"],{timeoutMs:ft})).code===0}function _g(p,f){return Vi(p,"temp_file_limit",f)}function vg(p,f){return Vi(p,"max_wal_size",f)}async function $g(p){return(await n(t,["unshare","--rootless-netns",nf,"-f","-"],{stdin:p,timeoutMs:ft})).code===0}let ds=null,us=null,Ve=new Map,pr=0;async function Pg(p,f){let g=p.join(",");if(ds===g)return!0;let w=await n(t,["network","inspect",Ae]);if(w.code===0&&Oo(w.stdout,p))return ds=g,f("network",null),!0;if(w.code===0){let k=(await N(n,t,["ps","--all","--filter",`network=${Ae}`,"--format","{{.Names}}"])).trim();if(k!=="")return f("network",`WARNING: the ${Ae} network does not route exactly to ${p.join(", ")}, and ${k.split(`
656
+ `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`),!1;await N(n,t,["network","rm",Ae])}let S=await n(t,Ro(p));return S.code!==0?(f("network",`WARNING: the ${Ae} network could not be made: ${(S.stderr||S.stdout).trim().split(`
657
+ `)[0]}`),!1):(ds=g,f("network",null),f("made",null),f("made",`made the ${Ae} network, routing only to ${p.join(", ")}`),!0)}async function xg(p,f={}){let g=await Yi(p),w=[...await Eg(p),...await Ag(p),...await Rg(p,f.groupPeers===!0)],S=await Yi(p);return[...g,...w,...S]}let Xt=null;async function Yi(p){if(pr===0&&mr===0&&Xt===null&&!p.some(x=>ps(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&&si(x.name)&&g.set(x.name,x.subnets?.[0]?.subnet??"");if(g.size===0&&Xt===null)return[];let w=g.size===0?Hp:Bp(k_(p,g)),S=Date.now();if(Xt&&Xt.script===w&&S-Xt.at<15e3)return[];let k=await n(t,["unshare","--rootless-netns",nf,"-f","-"],{stdin:w,timeoutMs:ft});if(k.code!==0){let x=`WARNING: the peer networks' filter could not be loaded: ${(k.stderr||k.stdout).trim().split(`
658
+ `)[0]}`;return Ve.get("filter")!==x?(Ve.set("filter",x),[x]):[]}return Ve.delete("filter"),Xt=g.size===0?null:{script:w,at:S},[]}function ps(p){return p.guard!==void 0&&p.state!=="deleted"&&!!p.peerAddress}async function Eg(p){let f=[...new Set(e.peers??[])].sort(),g=ff(p.filter(D=>!ps(D)));if(f.length===0||g.length===0)return[];let w=[],S=(D,Q)=>{Q===null?Ve.delete(D):Ve.get(D)!==Q&&(Ve.set(D,Q),w.push(Q))},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(us?.key!==I){let D=e.network?await n(t,["network","inspect",e.network]):null,Q=D?.code===0?No(D.stdout,f):[];us={key:I,peers:f.filter(ge=>!Q.includes(ge))}}let U=us.peers;if(U.length===0||!await Pg(U,S))return w;let Y=await N(n,t,["inspect","--format","{{.Name}} {{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",..._.map(D=>D.container)]),j=new Set(Y.split(`
659
+ `).map(D=>D.trim().split(/\s+/)).filter(D=>D.slice(1).includes(Ae)).map(D=>D[0])),$=_.filter(D=>!j.has(D.container));if($.length===0)return w;let B=new Map(p.map(D=>[D.ref,D.group??D.ref])),K=await N(n,t,["ps","--all","--filter",`network=${Ae}`,"--format","json"]),q=new Set((K.trim()?JSON.parse(K):[]).map(D=>D.Labels?.[je]).filter(D=>!!D).map(D=>B.get(D)??D));for(let D of $){let Q=b_(D,q,$[0].group);if(Q){S(D.container,`${D.container} does not join ${Ae}: ${Q}`);continue}let ge=await n(t,["network","connect",Ae,D.container]);if(ge.code!==0){S(D.container,`WARNING: ${D.container} could not join the ${Ae} network, so it has no route to ${U.join(", ")}: `+(ge.stderr||ge.stdout).trim().split(`
660
+ `)[0]);continue}q.add(D.group),S(D.container,null),w.push(`${D.container} joined the ${Ae} network: its route to ${U.join(", ")} comes from there`)}return w}async function Ag(p){let f=p.filter(ps),g=[],w=(j,$)=>{$===null?Ve.delete(j):Ve.get(j)!==$&&(Ve.set(j,$),g.push($))};if(f.length===0&&pr===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 j of S.stdout.trim()?JSON.parse(S.stdout):[])j.name&&To(j.name)!==null?k.set(j.name,j.subnets?.[0]?.subnet??""):j.name&&wn(j.name)!==null&&x.push(j.subnets?.[0]?.subnet??"");pr=k.size;let _=await N(n,t,["ps","--format","json"]),I=new Set((_.trim()?JSON.parse(_):[]).flatMap(j=>j.Names??[])),U=f.length>0&&e.network?await n(t,["network","inspect",e.network]):null,Y=new Set;for(let j of f){let $=j.group??j.ref,B=Up($),K=z(j.ref).pod;Y.add(B);let q=j.peerAddress;if(U?.code===0&&No(U.stdout,[q]).length>0||!I.has(K))continue;let D=k.has(B)?zt(k.get(B)):null,Q=!1;if(D){let Ye=await n(t,["network","inspect",B]);Q=Ye.code===0&&Oo(Ye.stdout,[q],D)}if(!Q&&k.has(B)){let Ye=(await N(n,t,["ps","--all","--filter",`network=${B}`,"--format","{{.Names}}"])).trim();if(Ye!==""){w(B,`WARNING: the ${B} network does not route exactly to ${q}, and ${Ye.split(`
661
+ `).join(", ")} is on it, so it is left as it is: remake it once nothing is connected`);continue}await N(n,t,["network","rm",B]),k.delete(B)}if(!Q){let Ye=D&&D.subnet||oi([...k.values(),...x]);if(D=Ye?zt(Ye):null,!D){w(B,`WARNING: no /28 is free for the ${B} network, so ${K} has no route to ${q}`);continue}let Re=await n(t,Ro([q],{name:B,subnet:D.subnet,gateway:D.gateway}));if(Re.code!==0){w(B,`WARNING: the ${B} network could not be made: ${(Re.stderr||Re.stdout).trim().split(`
662
+ `)[0]}`);continue}k.set(B,D.subnet),g.push(`made the ${B} network (${D.subnet}), routing only to ${q}`)}if((await N(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",K])).trim().split(/\s+/).includes(B)){w(B,null);continue}let Ne=await n(t,["network","connect",B,K]);if(Ne.code!==0){w(B,`WARNING: ${K} could not join the ${B} network, so it has no route to ${q}: ${(Ne.stderr||Ne.stdout).trim().split(`
663
+ `)[0]}`);continue}w(B,null),g.push(`${K} joined the ${B} network: its route to ${q} comes from there`)}for(let j of k.keys()){if(Y.has(j))continue;(await N(n,t,["ps","--all","--filter",`network=${j}`,"--format","{{.Names}}"])).trim()===""&&(await n(t,["network","rm",j])).code===0&&(k.delete(j),g.push(`removed the ${j} network: its pair is no longer on this host`))}return pr=k.size,g}let mr=0;async function Rg(p,f){let g=[],w=($,B)=>{B===null?Ve.delete($):Ve.get($)!==B&&(Ve.set($,B),g.push(B))},S=S_(p,e.peers??[]);if(S.size===0&&mr===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 B=$.subnets?.[0]?.subnet??"";$.name&&si($.name)&&_.push(B),$.name&&wn($.name)!==null&&x.set($.name,B)}mr=x.size;let I=await N(n,t,["ps","--format","json"]),U=new Set((I.trim()?JSON.parse(I):[]).flatMap($=>$.Names??[])),Y=S.size>0&&e.network?await n(t,["network","inspect",e.network]):null;for(let[$,B]of[...S].sort(([K],[q])=>K<q?-1:K>q?1:0)){if(Y?.code===0&&No(Y.stdout,[B.peer]).length>0)continue;let K=B.containers.filter(Q=>U.has(Q));if(K.length===0)continue;let q=x.has($)?zt(x.get($)):null,D=!1;if(q){let Q=await n(t,["network","inspect",$]);D=Q.code===0&&Oo(Q.stdout,[B.peer],q)}if(!D&&x.has($)){let Q=(await N(n,t,["ps","--all","--filter",`network=${$}`,"--format","{{.Names}}"])).trim();if(Q!==""){w($,`WARNING: the ${$} network does not route exactly to ${B.peer}, and ${Q.split(`
664
+ `).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 Q=q&&q.subnet||oi(_);if(q=Q?zt(Q):null,!q){w($,`WARNING: no /28 is free for the ${$} network, so ${K.join(", ")} has no route to ${B.peer}`);continue}let ge=await n(t,Ro([B.peer],{name:$,subnet:q.subnet,gateway:q.gateway}));if(ge.code!==0){w($,`WARNING: the ${$} network could not be made: ${(ge.stderr||ge.stdout).trim().split(`
665
+ `)[0]}`);continue}x.set($,q.subnet),_.push(q.subnet),g.push(`made the ${$} network (${q.subnet}), routing only to ${B.peer}`)}for(let Q of K){if((await N(n,t,["inspect","--format","{{range $name, $value := .NetworkSettings.Networks}}{{$name}} {{end}}",Q])).trim().split(/\s+/).includes($)){w(`${$} ${Q}`,null);continue}let Ne=await n(t,["network","connect",$,Q]);if(Ne.code!==0){w(`${$} ${Q}`,`WARNING: ${Q} could not join the ${$} network, so it has no route to ${B.peer}: ${(Ne.stderr||Ne.stdout).trim().split(`
666
+ `)[0]}`);continue}w(`${$} ${Q}`,null),g.push(`${Q} joined the ${$} network: its route to ${B.peer} comes from there`)}}let j=new Set(p.filter($=>$.state!=="deleted").map($=>$.group??$.ref));for(let $ of[...x.keys()]){if(S.has($))continue;let B=wn($);if(j.has(B.group)&&!f)continue;let K=(await N(n,t,["ps","--all","--filter",`network=${$}`,"--format","{{.Names}}"])).trim();for(let q of K===""?[]:K.split(`
667
+ `))(await n(t,["network","disconnect",$,q])).code===0&&g.push(`${q} left the ${$} network: ${B.peer} holds none of its cluster's pods`);(await n(t,["network","rm",$])).code===0&&(x.delete($),g.push(`removed the ${$} network: ${B.peer} holds none of its cluster's pods`))}return mr=x.size,g}async function Tg(p,f){let g=await n(t,[...Pe(p),"-tA","-v","ON_ERROR_STOP=1",...pp(f).flatMap(S=>["-c",S])],{timeoutMs:ft}),w=Number(g.stdout.trim().split(`
668
+ `).pop());return g.code===0&&Number.isInteger(w)?w:null}async function Og(p){let f=await n(t,[...Pe(p),"-tAc","select pg_read_file(current_setting('ssl_cert_file')) where current_setting('ssl') = 'on'"]),g=`${f.stdout.trim()}
669
+ `;return f.code===0&&$o.test(g)?g:null}async function Ng(p,f){if(!Ga.test(f))throw new Error("not a SCRAM-SHA-256 verifier");return Xi(p,{statements:[`do $$ begin if not exists (select 1 from pg_roles where rolname = '${Mt}') then create role ${Mt} with login replication; end if; end $$`,`alter role ${Mt} with login replication noinherit password '${f}'`],role:Mt,line:`hostssl replication ${Mt} all scram-sha-256`,replaceLine:!1})}async function Ig(p,f,g,w=!0){let S=e.rootless??(typeof process.getuid!="function"||process.getuid()!==0);return Ka(g),Xi(p,{statements:fp(f,w),role:hn,line:Ka(S?yn:g),replaceLine:!0})}async function Xi(p,f){let g=z(p).pod,w=[...Pe(p),"-v","ON_ERROR_STOP=1"];if((await n(t,[...w,...f.statements.flatMap(U=>["-c",U])],{timeoutMs:ft})).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 Zi(p,x.replace(/\/[^/]+$/,""))?!1:(await n(t,[...w,"-tAc","select pg_reload_conf()"])).code===0}async function Zi(p,f){let g=z(p).pod,w=[...Pe(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 Cg(p,f){if(!$o.test(f.certificate))throw new Error("the primary's certificate is not one PEM certificate");let g=z(p).pod;for(let[w,S]of[[bp,kp(f.password)],[Sp,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:ft});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:me,inRecovery:Ji,replayLsn:cg,currentLsn:dg,settleTimeline:ug,podHealth:pg,setSyncNames:bg,...e.guardRoot?{syncGuards:yg}:{},dropKeeperSlots:Tg,bridgeContainers:Sg,applyPodIsolation:$g,joinPeerNetwork:xg,requestDiskQuota:kg,ensureTempFileLimit:_g,ensureMaxWalSize:vg,ensureKeeperRole:Ng,ensureReplicaRole:Ig,dropIdleReplicaSlot:wg,writeFollowFiles:Cg,podCertificate:Og,swapHomeData:fg,adoptHomeData:gg,promoteStandby:hg,nodesAnswer:mg,preflight:i,create:u,createServices:ht,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",z(p).pod])},async startServices(p){let{services:f,anchor:g}=await H(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",mt(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 rt(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",Jo(g.container)]),await N(n,t,["rename",g.container,Jo(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",Jo(g.container)]),await N(n,t,["rename",g.container,Jo(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=z(p),w=Date.now()+f;for(;;){if((await n(t,["exec",g.pod,"pg_isready","--host",$t(p),"--port",String(ai),"--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=${$n}`,"--format","json"]),w=g.trim()?JSON.parse(g):[],S=new Map;for(let _ of w){let I=_.Labels?.[$n];I&&S.set(I,{spec:_.Labels?.[ef]??"",state:_.State??"",elsewhere:rf(_,e.podBind),joins:_.Labels?.[tf]||null})}let k=new Set;for(let _ of t_(p)){let I=S.get(_.name);S.delete(_.name);try{await x(_,I)}catch(U){f.push({name:_.name,action:"failed",reason:U instanceof Error?U.message:String(U)})}}async function x(_,I){let U=_.joins?_n(_.joins):null;if(U!==null&&!p.some(Q=>Q.name===U&&!Q.joins))throw new Error(`it joins ${U}, which this host does not run`);let Y=e.composeHostService?await e.composeHostService(_):_;if(n_(Y))throw new Error("it names host secrets, and this host cannot resolve them");let j=e.hostServiceLocal?.(Y,p),$=j?{...Y,...j.env&&Object.keys(j.env).length>0?{env:{...Y.env,...j.env}}:{},...j.volumes?{volumes:j.volumes}:{},...j.network?{network:j.network}:{}}:Y,B=Jt($.name),K=U===null?r_($.name,p):[],q=e_($,{joins:U,publishes:K});if(I&&I.spec===q&&!(I.state!=="running"&&I.elsewhere)){I.state!=="running"?(await N(n,t,["start",B]),f.push({name:$.name,action:"started",reason:I.state}),k.add($.name)):U!==null&&k.has(U)&&(await N(n,t,["restart",B]),f.push({name:$.name,action:"started",reason:`${U}, whose network it shares, was started again`}));return}let D=I?I.spec===q?"published on another instance's address":"the spec changed":"it was not here";if(await me($.image),I){for(let[Q,ge]of S)ge.joins===$.name&&(await N(n,t,["rm","--force","--ignore",Jt(Q)]),S.delete(Q));await N(n,t,["rm","--force","--ignore",B])}await N(n,t,[...await r(),"run","--detach","--name",B,"--label",`${$n}=${$.name}`,"--label",`${ef}=${q}`,...U!==null?["--label",`${tf}=${U}`]:[],...$.healthPath&&$.hostPort>0?["--label",`${Xo}=${$.hostPort}:${$.healthPath}`]:[],"--memory",`${$.memoryMb}m`,"--pids-limit",String(P_),"--restart","no",...U!==null?["--network",`container:${Jt(U)}`]:$.network?["--network",$.network]:e.network?["--network",e.network]:[],...($.volumes??[]).flatMap(Q=>["--volume",Q]),...U===null?[...$.hostPort>0?[[$.hostPort,$.port]]:[],...K].flatMap(([Q,ge])=>xn(e.podBind).flatMap(Ne=>["--publish",`${Ne}:${Q}:${ge}`])):[],...$.adminPort&&$.adminHostPort?["--publish",`127.0.0.1:${$.adminHostPort}:${$.adminPort}`]:[],...Vo($.env),$.image,...$.command??[]],{env:{...$.env}}),f.push({name:$.name,action:I?"recreated":"created",reason:D}),U===null&&k.add($.name)}for(let _ of[...S.keys()].sort((I,U)=>+!S.get(I)?.joins-+!S.get(U)?.joins))await N(n,t,["rm","--force","--ignore",Jt(_)]),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=${$n}`,"--format","json"]),g=(f.trim()?JSON.parse(f):[]).map(S=>S.Labels?.[$n]??"").filter(S=>S!=="");if(g.length===0)return[];let w=Date.now()+p;for(;;){let S=[];for(let k of g)S.push(await sf(n,t,k,Jt(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=>sf(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,[...Pe(p),"-c","checkpoint"],{timeoutMs:ft});let w=Date.now()+g;for(;;){let S=await n(t,[...Pe(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}=de(await H(p),g);for(let k of w)await N(n,t,["stop","--time",String(Vm),k.container]);S!==null&&await N(n,t,["stop","--time",String(Vm),mt(p)]),await N(n,t,["stop","--time",String(f),z(p).pod])},async remove(p,f){let{services:g,anchor:w}=de(await H(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",mt(p)]),await N(n,t,["rm","--force","--ignore","--volumes",z(p).pod])},async removeData(p){await N(n,t,["volume","rm","--force",z(p).pod]);let f=nr(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,z(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${ie(p.ref)}_owner'`]);if(f.code===0&&Xa(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(`
670
+ `);await N(n,t,["exec",...$e,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",z(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 ${Dt(f)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
671
+ `);await N(n,t,["exec",...$e,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",z(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",z(p).pod,"psql","--username","snoutpod_admin","--dbname",ie(p),"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROUTER_PASSWORD:f??""},stdin:Jp(p,f!==null)}),f!==null){let g=[...Pe(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 Zi(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=z(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",yi,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Pm});return xm(S)},async applyDbAccess(p,f){let g=z(p),w=await N(n,t,["exec",...$e,"--interactive","--env",Go,g.pod,"psql","--username","snoutpod_admin","--dbname",g.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Go]:lm(p,f)},stdin:am,timeoutMs:6e4});return im(w)},async runProjectSql(p,f){let g=z(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=Jt(p),S=Dt(f),k=Dt(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",z(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:N_(f,p.ref)})},async waitReady(p,f){let g=z(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 Wi(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 Ji(p)===!0)return!0;if(Date.now()>=g||await Wi(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,z(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return f.code===0?Ba(f.stdout):null},async storageBytes(p){let f=await n(t,["exec",...$e,z(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?Ba(f.stdout):null},async setReadOnly(p,f){let g=z(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,z(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(`
672
+ `)[0]?.split("|")??[],S=Number(w);return{lastArchivedAt:g.trim()||null,failures:Number.isInteger(S)&&S>=0?S:0}}}}async function sf(e,t,n,r,o=mf){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=l_(c?.[Xo]);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 R_(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 O_(e,t){return(e.State??"").toLowerCase()==="created"&&typeof e.Created=="number"&&e.Created>0&&t-e.Created*1e3>T_}function Ei(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function N_(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..${lf} 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(${af/lf});`," end loop;",` raise exception 'database % still has sessions after ${af}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(`
673
+ `)}function I_(e){if(!e?.issuer)return[];try{return["--env",`${Am}=${Rm(e.issuer)}`]}catch{return[]}}function C_(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var je,Pi,We,Pn,or,Jm,Vm,Vk,Yk,df,vn,Zm,$n,ef,tf,uf,Ti,Xo,mf,c_,nf,$e,ft,p_,of,__,gf,v_,$_,P_,A_,T_,af,lf,Ko=J(()=>{"use strict";De();Fa();Co();Po();Ya();Qa();Za();rr();Sm();Ha();$m();Em();mi();Tm();ei();Mm();Va();pi();Um();Bo();zm();je="snoutpod.ref",Pi="snoutpod.generation",We="snoutpod.service",Pn="snoutpod.services-generation",or="snoutpod.service-spec",Jm="snoutpod.service-pod";Vm=10,Vk=/^[a-z][a-z0-9-]{0,30}$/;Yk=/^[a-z][a-z0-9_]{0,62}$/;df="-set-aside",vn=1024*1024,Zm=/^[0-9a-f]{12,64}$/;$n="snoutpod.host-service",ef="snoutpod.host-service-spec",tf="snoutpod.host-service-joins";uf=/^[A-Za-z_][A-Za-z0-9_]*$/;Ti=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Xo="snoutpod.health";mf=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()})},c_="crun";nf="/usr/sbin/nft",$e=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp -c synchronous_commit=local"],ft=3e4,p_=3e4;of=200,__=1024,gf=512,v_=45e3,$_=1e3,P_=4096;A_=8;T_=12e4;af=30,lf=300});var Of={};rl(Of,{DEFAULT_LOCAL_MEMORY_MB:()=>kf,DEFAULT_LOCAL_PORT:()=>U_,ENGINE:()=>ce,ENGINE_NAME:()=>Ii,IMAGE:()=>Li,NETWORK:()=>jt,NEW_PROJECT_MAJOR:()=>Mi,NO_BLACKHOLE_ROUTES:()=>Pf,PUBLIC_IMAGE:()=>G_,PUBLISHED_MAJORS:()=>En,capacity:()=>$f,chooseLocalImage:()=>lr,dropIfUnused:()=>Rf,ensureImage:()=>K_,ensureNetwork:()=>V_,exec:()=>be,hasData:()=>ts,localImageFor:()=>vf,localPsql:()=>Z_,localRuntime:()=>J_,localSpec:()=>Af,localStatus:()=>Di,localUri:()=>Ef,networkPolicyInPlace:()=>xf,newLocalPassword:()=>B_,newLocalRef:()=>F_,podLog:()=>Tf,podName:()=>An,podmanReady:()=>z_,removeLocal:()=>tv,staleImage:()=>Ci,startLocal:()=>X_,stopLocal:()=>ev,volumePgVersion:()=>Zo});import{randomBytes as bf}from"node:crypto";import{existsSync as yf}from"node:fs";import{platform as es}from"node:os";import{delimiter as M_,join as L_}from"node:path";function D_(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=es()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(M_))if(t&&e.some(n=>yf(L_(t,n))))return"podman";return yf("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function lr(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return ir(Mi);let n=Number(Ni(e));if(!En.includes(n)){let r=e.trim()||"(empty)",o=En.map(s=>ir(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${En.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return ir(n)}function ir(e){return{name:`${j_}:${e}`,published:`${Sf}:${e}`,major:e}}function F_(){return sp(e=>bf(e))}function B_(){return bf(18).toString("base64url")}async function K_(e,t=be,n=lr(null)){let r=n.published;if(!r)return null;if((await t(ce,["image","exists",n.name])).code===0)return await q_(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(ce,["pull",r],{timeoutMs:_f}).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(ce,["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 q_(e,t,n,r){let o=await t(ce,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
674
+ `):[];if(!s||s!==a)return;let c=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(ce,["pull",r],{timeoutMs:_f}).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(`
675
+ `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(ce,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),l=u?.code===0?u.stdout.trim():"";if(!l||l===s)return;let m=await t(ce,["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 z_(e=H_,t=Li){if(Ii!=="podman")return`SNOUTPOD_ENGINE=${Ii}: 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 be(ce,["--version"])}catch{return`${ce} 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`${ce} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await be(ce,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await be(ce,["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 W_(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 be(ce,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
676
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function ts(e){let t=es()==="darwin"||es()==="win32";return pm(be,e,{binary:ce,look:t?W_:void 0})}async function Zo(e,t=be,n=es()){let r=z(e).pod;if((await t(ce,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of En){let c=ir(a).name;if((await t(ce,["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 (${En.map(a=>ir(a).published).join(" or ")}).`);o=await t(ce,["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(ce,["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(ce,["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 vf(e,t=Zo){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(lr(null));ie(e);let n=t===Zo?Qo.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return lr(o)}catch(s){throw new Error(`${An(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Zo&&(Qo.set(e,r),r.catch(()=>Qo.delete(e))),r}async function $f(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function J_(e){return hf({exec:be,binary:ce,network:jt,hasData:ts,capacity:$f,repository:e})}function An(e){return z(e).pod}async function V_(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await Y_()||await be(ce,Io({name:jt}));return}let t=Io({name:jt,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await xf())return;await e?.(),await be(ce,["network","rm","--force",jt]).catch(()=>{});let o=await be(ce,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Pf}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${jt} with egress blocked${n?`: ${n}`:""}`)}async function Y_(){return(await be(ce,["network","inspect",jt,"--format","json"])).code===0}async function xf(){let e=await be(ce,["network","inspect",jt,"--format","json"]);if(e.code!==0)return!1;try{return Gp(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Ef(e){let t=z(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Af(e,t={}){return ie(e.ref),{ref:e.ref,generation:1,image:t.image??Li,memoryMb:t.memoryMb??kf,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 ar(e,t){e.onProgress?.(t)}async function X_(e,t,n={}){let r=n.image??(await vf(t.ref,n.pgVersion)).name,o=Af(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??ts,c=(await e.list()).find(u=>u.ref===t.ref),i=null;if(c?c.status==="stopped"&&(i=await(n.staleImage??Ci)(t.ref,r))?(ar(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??Rf)(i)):c.status==="stopped"?(ar(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(ar(n,"The database is already running."),c.status==="running"&&await(n.staleImage??Ci)(t.ref,r)&&ar(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(ar(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 Q_(t.ref,s,n.podLog??Tf));return await e.applyOwnerPassword(o),await e.createServices(o),Di(e,t,n)}function wf(e){return e.trim().replace(/^sha256:/,"")}async function Ci(e,t){let[n,r]=await Promise.all([be(ce,["container","inspect","--format","{{.Image}}",An(e)]).catch(()=>null),be(ce,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=wf(n.stdout),s=wf(r.stdout);return o&&s&&o!==s?o:null}async function Rf(e){let t=await be(ce,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await be(ce,["rmi",e]).catch(()=>{})}async function Z_(e,t){let n=z(e.ref);return be(ce,["exec","--interactive","-e","PGPASSWORD",An(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 Tf(e){let t=await be(ce,["logs","--tail","20",An(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Q_(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${An(e)} never became ready in ${t}ms
509
677
  --- pod log ---
510
- ${r}`}async function __(e,t,n={}){se(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Np),yi(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function $_(e,t,n){se(t),await e.remove(t),n&&(await e.removeData(t),Fo.delete(t))}async function yi(e,t,n={}){se(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Ho;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?Vm(t):null}}var mi,ie,gi,wn,a_,Hm,hi,Ot,i_,Gm,we,d_,u_,Km,Fo,zm,ef=W(()=>{"use strict";Ip();zn();Dp();ko();Oo();Me();mi=process.env.SNOUTPOD_ENGINE??"podman";ie=s_(),gi=18,wn=[17,18],a_="snoutpod/postgres",Hm="ghcr.io/snoutdata/snoutpod-postgres";hi=Zn(null).name,Ot=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",i_=54322,Gm=512,we=Cp();d_="cd packages/snoutpod && npm run image:build",u_=`${Hm}:${gi}`;Km=20*60*1e3;Fo=new Map;zm="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});Te();var Ue=["--admin","--token","--project"],es=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],qt=[...Ue,"--plan","--no-wait","--timeout",...es],Ce=[...qt,"--yes"],ts=[{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:[...Ue]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...Ue]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...Ue]},{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:[...qt]},{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:[...qt,"--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:[...qt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...qt]},{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:[...qt,"--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:[...Ue,...es]},{name:"cluster jobs",summary:"The latest jobs",flags:[...Ue]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...Ue]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...Ue,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...Ue,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...Ue,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...Ue,...es,"--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:[...Ue]},{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:[...Ue,"--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","--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 ns(e,t){return Ui[`${e} ${t}`]??Ui[t]??""}function Fi(e,t){let n=ts.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 hh}from"node:child_process";function yh(e){return`snout-${e}`}function Qt(e,t,n){return new Promise((r,o)=>{let s=hh("docker",["compose","-p",yh(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($(` ${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 wh(e){let t=await Qt(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 Al(e){let t=await wh(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 Rl(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await Qt(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 Tl(e){let t=await Qt(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 Ol(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
- `)})}$n();import{cpSync as Cl,existsSync as Nn,readdirSync as xh,readFileSync as Ml,rmSync as Dl,statSync as Eh,writeFileSync as Ll}from"node:fs";import{join as We,resolve as Ir}from"node:path";var bh=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Sh(e){return bh.test(e)}function Ct(e){if(!Sh(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 kh=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function _h(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!==".."&&kh.test(n))}var Ss=2*1024*1024,Nl=200;function Il(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>Nl)throw new Error(`a function may hold ${Nl} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!_h(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=$h(o);if(s.length>Ss)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Ss/1024} KB. A function is source code; put large files in Storage.`);return o}function $h(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 vh=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Ph=["SNOUTDATA_","SNOUT_"];function On(e){if(!vh.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Ph.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 Cv=64*1024;be();Te();de();function jl(e){let t=We(e.folder,"functions");return Nn(t)?xh(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Eh(We(t,n)).isDirectory()).sort():[]}async function Cr(e){d($(" docker compose run --rm functions-deploy"));let t=await Qt(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 Ul(e,t,n){let r=We(e.folder,".env"),o=Ml(r,"utf8"),s=(dr(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?$/,`
678
+ ${r}`}async function ev(e,t,n={}){ie(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,cm),Di(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function tv(e,t,n){ie(t),await e.remove(t),n&&(await e.removeData(t),Qo.delete(t))}async function Di(e,t,n={}){ie(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??ts;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?Ef(t):null}}var Ii,ce,Mi,En,j_,Sf,Li,jt,U_,kf,be,H_,G_,_f,Qo,Pf,Nf=J(()=>{"use strict";dm();rr();mm();Co();Ko();De();Ii=process.env.SNOUTPOD_ENGINE??"podman";ce=D_(),Mi=18,En=[17,18],j_="snoutpod/postgres",Sf="ghcr.io/snoutdata/snoutpod-postgres";Li=lr(null).name,jt=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",U_=54322,kf=512,be=um();H_="cd packages/snoutpod && npm run image:build",G_=`${Sf}:${Mi}`;_f=20*60*1e3;Qo=new Map;Pf="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});Te();var Be=["--admin","--token","--project"],fs=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],Zt=[...Be,"--plan","--no-wait","--timeout",...fs],Le=[...Zt,"--yes"],gs=[{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: a page offering Google, GitHub and your work email; --provider goes straight to one, --sso to your company's identity provider, --device types a code into a browser 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:[...Zt]},{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:[...Zt,"--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:[...Le,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...Le]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...Zt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...Zt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...Le,"--distribution-key","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...Le]},{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:[...Le,"--keyspace","--schemas","--min-bytes"]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...Le]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...Le,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...Le]},{name:"cluster range move",summary:"Give a range to another node",flags:[...Le,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...Le,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...Le,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...Le,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...Zt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...Le,"--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:[...Le]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...Be,...fs]},{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,...fs,"--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"]}],sl={"--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 or github, straight to it. Without it, login opens a page offering every way in","--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 hs(e,t){return sl[`${e} ${t}`]??sl[t]??""}function al(e,t){let n=gs.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}fe();ke();pe();import{spawn as Jh}from"node:child_process";function Vh(e){return`snout-${e}`}function cn(e,t,n){return new Promise((r,o)=>{let s=Jh("docker",["compose","-p",Vh(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 Yh(e){let t=await cn(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 sc(e){let t=await Yh(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(`${L(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${ee([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
679
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function ac(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await cn(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 ic(e){let t=await cn(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 lc(e){b({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${ee([["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}`]])}
680
+ `)})}Nn();import{cpSync as uc,existsSync as Hn,readdirSync as oy,readFileSync as pc,rmSync as mc,statSync as sy,writeFileSync as fc}from"node:fs";import{join as Ze,resolve as zr}from"node:path";var Xh=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Zh(e){return Xh.test(e)}function Ht(e){if(!Zh(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 Qh=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function ey(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!==".."&&Qh.test(n))}var Fs=2*1024*1024,cc=200;function dc(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>cc)throw new Error(`a function may hold ${cc} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!ey(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=ty(o);if(s.length>Fs)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Fs/1024} KB. A function is source code; put large files in Storage.`);return o}function ty(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 ny=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var ry=["SNOUTDATA_","SNOUT_"];function Bn(e){if(!ny.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=ry.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 fP=64*1024;ke();Te();pe();function gc(e){let t=Ze(e.folder,"functions");return Hn(t)?oy(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&sy(Ze(t,n)).isDirectory()).sort():[]}async function Wr(e){d(v(" docker compose run --rm functions-deploy"));let t=await cn(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 hc(e,t,n){let r=Ze(e.folder,".env"),o=pc(r,"utf8"),s=(Sr(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 fc(r,i.test(o)?o.replace(i,c):`${o.replace(/\n?$/,`
513
681
  `)}${c}
514
- `),!0}async function ks(e,t,n){Ct(t);let r=Ir(n.dir??We(process.cwd(),"functions",t));if(!Nn(We(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=We(e.folder,"functions",t);if(Ir(o)!==r){Dl(o,{recursive:!0,force:!0}),Cl(r,o,{recursive:!0});let a=We(r,"..","_shared");Nn(a)&&Ir(a)!==Ir(We(e.folder,"functions","_shared"))&&Cl(a,We(e.folder,"functions","_shared"),{recursive:!0})}let s=Ul(e,t,!n.verifyJwt);if(await Cr(e),s){let a=await Qt(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:_s(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function Fl(e,t,n){let r=await ks(e,t,n);b(r,()=>{d(`${M(t)} deployed locally: ${r.url}`),n.verifyJwt||d($(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function _s(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Mr(e){return{ref:e.ref,local:e.folder,functions:jl(e).map(t=>({name:t,url:_s(e,t)}))}}function Bl(e){let t=jl(e);b(Mr(e),()=>{if(t.length===0){d(`No functions in ${We(e.folder,"functions")}.`);return}process.stdout.write(`${X([["NAME","URL"],...t.map(n=>[n,_s(e,n)])])}
515
- `)})}async function $s(e,t){Ct(t);let n=We(e.folder,"functions",t);if(!Nn(n))throw new C("not-found",`this local project has no function called ${t}`);return Dl(n,{recursive:!0,force:!0}),Ul(e,t,!1),await Cr(e),{ref:e.ref,name:t,removed:!0}}async function Hl(e,t){b(await $s(e,t),()=>d(`${t} is gone.`))}function Dr(e){return We(e.folder,"functions",".env")}function vs(e){let t=Dr(e);return Nn(t)?dr(Ml(t,"utf8")):new Map}function Gl(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Ll(Dr(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
682
+ `),!0}async function Bs(e,t,n){Ht(t);let r=zr(n.dir??Ze(process.cwd(),"functions",t));if(!Hn(Ze(r,"index.ts")))throw new y(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=Ze(e.folder,"functions",t);if(zr(o)!==r){mc(o,{recursive:!0,force:!0}),uc(r,o,{recursive:!0});let a=Ze(r,"..","_shared");Hn(a)&&zr(a)!==zr(Ze(e.folder,"functions","_shared"))&&uc(a,Ze(e.folder,"functions","_shared"),{recursive:!0})}let s=hc(e,t,!n.verifyJwt);if(await Wr(e),s){let a=await cn(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:Hs(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function yc(e,t,n){let r=await Bs(e,t,n);b(r,()=>{d(`${L(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 Hs(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Jr(e){return{ref:e.ref,local:e.folder,functions:gc(e).map(t=>({name:t,url:Hs(e,t)}))}}function wc(e){let t=gc(e);b(Jr(e),()=>{if(t.length===0){d(`No functions in ${Ze(e.folder,"functions")}.`);return}process.stdout.write(`${ee([["NAME","URL"],...t.map(n=>[n,Hs(e,n)])])}
683
+ `)})}async function Gs(e,t){Ht(t);let n=Ze(e.folder,"functions",t);if(!Hn(n))throw new C("not-found",`this local project has no function called ${t}`);return mc(n,{recursive:!0,force:!0}),hc(e,t,!1),await Wr(e),{ref:e.ref,name:t,removed:!0}}async function bc(e,t){b(await Gs(e,t),()=>d(`${t} is gone.`))}function Vr(e){return Ze(e.folder,"functions",".env")}function Ks(e){let t=Vr(e);return Hn(t)?Sr(pc(t,"utf8")):new Map}function Sc(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;fc(Vr(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
516
684
  `)+(t.size?`
517
- `:""))}async function Kl(e,t){for(let r of t){let o=On(r.name);if(o)throw new h(o)}let n=vs(e);for(let r of t)n.set(r.name,r.value);Gl(e,n),await Cr(e),b({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${M(r.name)} set in ${Dr(e)}`)})}function Lr(e){return{ref:e.ref,local:e.folder,secrets:[...vs(e).keys()].sort().map(t=>({name:t}))}}function ql(e){let t=Lr(e),n=t.secrets.map(r=>r.name);b(t,()=>{d(n.length?n.join(`
518
- `):`No function secrets in ${Dr(e)}.`)})}async function Wl(e,t){let n=vs(e);if(!n.delete(t))throw new C("not-found",`${t} is not set for this local project`);Gl(e,n),await Cr(e),b({ref:e.ref,unset:t},()=>d(`${t} unset.`))}be();kt();wt();ps();hr();de();Rr();pe();pe();be();wt();de();async function zl(){let{projects:e,allowance:t}=await qe();b({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${X(Ah(e))}
519
- `)})}function Ah(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):"-"]:[],pt(n.lastConnectionAt)])]}async function Ps(e){let t=await E("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 Tn(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d($(` ${a}`)))}})}e.link&&zt(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;b({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}
520
- `),d(""),d(`Ready. ${M(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 Jl(e,t,n={}){let r=`cloud-project-${e}`,o=await E(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 Rn(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d($(` ${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 Vl(e){let t=await E("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 Yl(){let{teams:e}=await Or();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 Xl(e){let{teams:t}=await Or(),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();Te();be();kt();de();import{createInterface as Th}from"node:readline/promises";var tt={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`}.`},Oh={plan:"quota","not-allowed":"forbidden",sharded:"conflict","not-ready":"conflict","owner-cap":"quota","too-soon":"not-ready",busy:"not-ready",full:"no-capacity"};function Zl(e){let t=Number(e);if(!/^[123]$/.test(e.trim())||!Number.isInteger(t))throw new h(tt.which);return t}async function Nh(e){let t=Th({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 Ql(e,t){if(t.instances===void 0){let s=await E("cloud-project-spread",{ref:e,preview:!0});b(s,()=>{d(tt.now(s.ref,s.current)),d(s.what),d(tt.most(s.most)),s.most>s.current&&!s.open&&d(tt.notOpen)});return}let n=t.instances;if(!t.yes&&!t.ask&&!Se())throw new h(tt.noAsk);let r=await E("cloud-project-spread",{ref:e,preview:!0,instances:n});if(r.outcome==="refused")throw new C(Oh[r.code??""]??"conflict",r.error??tt.notOpen,{ref:e,instances:n,code:r.code});if(r.outcome==="none"){b({ref:r.ref,instances:n,changed:!1},()=>d(tt.nothing(r.ref)));return}if(process.stderr.write(`${r.what}
685
+ `:""))}async function kc(e,t){for(let r of t){let o=Bn(r.name);if(o)throw new y(o)}let n=Ks(e);for(let r of t)n.set(r.name,r.value);Sc(e,n),await Wr(e),b({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${L(r.name)} set in ${Vr(e)}`)})}function Yr(e){return{ref:e.ref,local:e.folder,secrets:[...Ks(e).keys()].sort().map(t=>({name:t}))}}function _c(e){let t=Yr(e),n=t.secrets.map(r=>r.name);b(t,()=>{d(n.length?n.join(`
686
+ `):`No function secrets in ${Vr(e)}.`)})}async function vc(e,t){let n=Ks(e);if(!n.delete(t))throw new C("not-found",`${t} is not set for this local project`);Sc(e,n),await Wr(e),b({ref:e.ref,unset:t},()=>d(`${t} unset.`))}ke();Tt();Et();Ts();xr();pe();Hr();fe();fe();Te();ke();Et();pe();async function $c(){let{projects:e,allowance:t}=await Xe();b({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${ee(ay(e))}
687
+ `)})}function ay(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?L("read-only"):n.state==="ready"?n.state:L(n.state)}${n.serverSilent?` ${L("(server not answering)")}`:""}`,n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],St(n.lastConnectionAt)])]}async function qs(e){let t=e.servers==="dedicated";if(e.servers!==void 0&&e.servers!=="dedicated"&&e.servers!=="shared")throw new y("--servers is shared or dedicated");if(t!==(e.size!==void 0))throw new y(t?"--servers dedicated needs --size small, medium or large":"--size goes with --servers dedicated");let n=await T("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 Fn(r,{timeoutMs:e.timeoutMs??(t?15*6e4:void 0),onTick:c=>{c!==a&&(a=c,d(v(` ${c}`)))}})}e.link&&en(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}
688
+ `),d(""),d(`Ready. ${L(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 Pc(e,t,n={}){let r=`cloud-project-${e}`,o=await T(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 Un(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 xc(e){let t=await T("cloud-project-reset-password",{ref:e});b(t,()=>{process.stdout.write(`${t.password}
689
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Ec(){let{teams:e}=await Kr();b({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${ee([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
690
+ `)})}async function Ac(e){let{teams:t}=await Kr(),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}fe();Te();ke();Tt();pe();import{createInterface as ly}from"node:readline/promises";var lt={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`}.`},cy={plan:"quota","not-allowed":"forbidden",sharded:"conflict","not-ready":"conflict","owner-cap":"quota","too-soon":"not-ready",busy:"not-ready",full:"no-capacity"};function Rc(e){let t=Number(e);if(!/^[123]$/.test(e.trim())||!Number.isInteger(t))throw new y(lt.which);return t}async function dy(e){let t=ly({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 Tc(e,t){if(t.instances===void 0){let s=await T("cloud-project-spread",{ref:e,preview:!0});b(s,()=>{d(lt.now(s.ref,s.current)),d(s.what),d(lt.most(s.most)),s.most>s.current&&!s.open&&d(lt.notOpen)});return}let n=t.instances;if(!t.yes&&!t.ask&&!_e())throw new y(lt.noAsk);let r=await T("cloud-project-spread",{ref:e,preview:!0,instances:n});if(r.outcome==="refused")throw new C(cy[r.code??""]??"conflict",r.error??lt.notOpen,{ref:e,instances:n,code:r.code});if(r.outcome==="none"){b({ref:r.ref,instances:n,changed:!1},()=>d(lt.nothing(r.ref)));return}if(process.stderr.write(`${r.what}
523
691
  ${r.message?`${r.message}
524
- `:""}`),!t.yes&&!await(t.ask??Nh)(tt.ask(r.ref,n)))throw new C("failed",tt.declined);let o=await E("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(tt.done(o.ref,o.copies)),o.message&&d(o.message)})}pe();de();async function ec(e){let t=await E("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($(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function tc(){let{tokens:e}=await E("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",pt(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
526
- `)})}async function nc(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await E("cloud-token-revoke",t);b(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}pe();Te();de();async function rc(e){let t=await Fe(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(`${sc(t.appKey,t.serverKey)}
527
- `),Mh(t.keysIssuedAt)})}async function oc(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=Tr(await E("cloud-project-rotate-jwt",{ref:e}));b(n,()=>{process.stdout.write(`${sc(n.appKey,n.serverKey)}
528
- `),d(""),d(n.breaks),d($(" snoutdata keys --json to read them again later"))})}function sc(e,t){return X([["KEY","VALUE"],["app key",e],["server key",t]])}function Mh(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($(` issued ${e.slice(0,10)}`))}pe();Te();de();import{readdir as Lh,readFile as jh,stat as Uh}from"node:fs/promises";import{join as Es,relative as Fh,sep as Bh}from"node:path";var ac=["functions"];async function xs(e){try{return(await Uh(e)).isDirectory()}catch{return!1}}async function Hh(e,t){if(t){if(!await xs(t))throw new h(`${t} is not a directory`);return t}for(let n of ac){let r=Es(n,e);if(await xs(r))return r}throw new h(`no folder found for ${e} at ${ac[0]}/${e}; use --dir to say where it is.`)}async function ic(e){let t=[];async function n(r){for(let o of await Lh(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Es(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await jh(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:Fh(e,s).split(Bh).join("/"),text:c})}}return await n(e),t}async function Gh(e,t,n){let r=await ic(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=Es(t,"..","_shared");if(!await xs(s))return{entrypoint:o,files:r};let a=(await ic(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 As(e,t,n){Ct(t);let r=await Hh(t,n.dir),{entrypoint:o,files:s}=await Gh(t,r,n.entrypoint),a=Il({entrypoint:o,files:s});return{answer:await E("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 cc(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await As(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($(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d($(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Rs(e){return E("cloud-project-functions",{ref:e})}function Ts(e,t){return Ct(t),E("cloud-project-functions",{ref:e,action:"delete",name:t})}function Os(e,t,n){if(Ct(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new h("functions size needs --memory MB, --concurrency N, or --reset");return E("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Kh(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 dc(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 uc(e,t,n){let r=await Os(e,t,n),o=r.functions.find(s=>s.name===t);b(r,()=>{let s=o?Kh(o,r.limit):null;d(`${M(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d($(" The next request runs at it; workers already running finish what they hold."));let a=dc(r.limit);a&&d($(` ${a}`))})}async function pc(e){let t=await Rs(e);b(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d($(" 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($(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=dc(t.limit);n&&d($(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function lc(e){let t=e.dropped_before>0?$(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?$(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?M("error "):"";return`${$(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function mc(e,t,n){let r=a=>E("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($(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${lc(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(`${lc(c)}
531
- `),s=c.at}}async function fc(e,t){let n=await Ts(e,t);b(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}pe();import{spawn as fy}from"node:child_process";import{createHash as gy}from"node:crypto";import{createReadStream as vc}from"node:fs";import{mkdtemp as hy,rm as yy,stat as jr}from"node:fs/promises";import{request as wy}from"node:http";import{request as by}from"node:https";import{tmpdir as Sy}from"node:os";import{basename as ky,dirname as _y,join as Ds}from"node:path";var Wh="x-snoutdata-upload-bytes",zh="x-snoutdata-upload-sha256",Jh="/done",Vh=/^[0-9a-f]{64}$/,Yh=/^sha256:[0-9a-f]{64}$/;function Xh(e){return Vh.test(e)}var Be=class extends Error{constructor(n,r){super(`the upload failed (${n})`);this.status=n;this.body=r}};function Ns(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function gc(e){let t=String(Ns(e).digest??"");if(!Yh.test(t))throw new Be(502,JSON.stringify({error:"The upload finished without saying what it stored, so there is nothing to deploy. Build again."}));return t}var Zh=15*6e4,Qh=2e3;async function hc(e){let{ticketUrl:t,file:n,transport:r}=e;if(!Xh(e.sha256))throw new Error("an upload is declared with its SHA-256, in hex");let o=await r.call("POST",t,{[Wh]:String(e.bytes),[zh]:e.sha256});if(o.status===405){e.step?.("uploading");let l=await r.put(t,n,{});if(l.status!==200)throw new Be(l.status,l.body);return gc(l.body)}if(o.status!==200)throw new Be(o.status,o.body);let s=Ns(o.body);if(typeof s.url!="string"||!s.url.startsWith("https://")||!s.headers||typeof s.headers!="object")throw new Be(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 Be(a.status===403?401:502,JSON.stringify({error:m}))}e.step?.("storing");let c=r.now()+(e.waitMs??Zh),i=e.pollMs??Qh,u=!1;for(;!u;){let l=await r.call("POST",`${t}${Jh}`,{});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 Be(l.status,l.body)}for(;;){let l=await r.call("GET",t,{});if(l.status!==200)throw new Be(l.status,l.body);let m=Ns(l.body);if(m.state==="stored")return gc(l.body);if(m.state==="failed")throw new Be(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 Be(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 ey=/^[a-z0-9][a-z0-9_-]{0,31}$/;function ty(e){return ey.test(e)}function wc(e){return ty(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 ny=/^sha256:[0-9a-f]{64}$/,ry=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,oy=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,sy=/^(?:[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 ay(e){return ny.test(e)}function bc(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),!ay(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),!ry.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()),sy.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=>!oy.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 mP=["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 Is(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 iy(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=Is(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 ly(e,t,n){let r=Is(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 cy=[["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 fP=1024**3;var yc=16,dy=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,uy=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function py(e,t){let n=e??[],r={error:`${t}: ports are up to ${yc} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>yc)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 my(e){let t=e.trim().toLowerCase(),n=uy.exec(t);if(n){let r=Is(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 cy.some(([u,l])=>o>=l&&ly(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 iy(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.`}:dy.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 Sc(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=r?t.slice(0,n):t,s=py(r?t.slice(n+1).split(",").map(c=>c.trim()):[],o.trim());if("error"in s)return s;let a=my(o);return typeof a!="string"?a:{destination:a,ports:s}}function Cs(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var $y=new Set(["pending","pulling","starting","restarting"]),vy=5*6e4;function Br(e){let t=wc(e);if(t)throw new Error(t)}function Ls(e,t,n){Br(t);let r=bc(n.image);if("error"in r)throw new Error(r.error);return E("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 en(e){return E("cloud-project-containers",{ref:e})}function js(e,t){return Br(t),E("cloud-project-containers",{ref:e,action:"delete",name:t})}function Us(e,t){return Br(t),E("cloud-project-containers",{ref:e,action:"restart",name:t})}function Ur(e,t){return E("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 Pc(e,t){let n=Date.now()+vy,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await en(e)).containers.find(o=>o.name===t)??null,!r||!$y.has(r.status))return r;return r}function Fs(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 xc(e,t,n){await Ec(e,t,await Ls(e,t,n),n.wait!==!1)}async function Ec(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d($(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await Pc(e,t)??o),b({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${M(t)} deployed.`);return}d(`${M(t)} ${Fs(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d($(" 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($(" Keep what matters in your database.")),n.allowance&&d($(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var Py=2*1024**3,xy=3*6e4;async function Ey(e){let t;try{t=await jr(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 jr(Ds(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:_y(e),file:e}}function Ay(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function Bs(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 Ry(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?Bs(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 Ty(e){return new Promise((t,n)=>{let r=fy("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Ac(e,t,n,r){return new Promise((o,s)=>{let a=new URL(e),c=(a.protocol==="http:"?wy:by)(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 Oy(e,t,n){let r=Object.keys(n).length>0?n:{"content-type":"application/octet-stream","content-length":String((await jr(t)).size)};return Ac(e,"PUT",r,o=>{let s=vc(t);s.on("error",a=>o.destroy(a)),s.pipe(o)})}function Ny(e){return new Promise((t,n)=>{let r=gy("sha256");vc(e).on("error",n).on("data",o=>r.update(o)).on("end",()=>t(r.digest("hex")))})}var Iy={call:(e,t,n)=>Ac(t,e,{"content-length":"0",...n},r=>r.end()),put:Oy,sleep:e=>new Promise(t=>setTimeout(t,e)),now:()=>Date.now()},Rc={docker:Ty,scratch:()=>hy(Ds(Sy(),"snoutdata-build-")),remove:e=>yy(e,{recursive:!0,force:!0}),size:async e=>(await jr(e)).size,hash:Ny,transport:Iy,ask:e=>E("cloud-project-containers",e),say:e=>d($(e)),now:()=>Date.now()};function kc(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function _c(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 Cy(e,t,n,r=Rc){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 _c(r,e,t),s=await r.scratch();try{let a=Ds(s,"image.tar");if(r.say(`${t}: building ${n.file?ky(n.file):n.context} for linux/arm64`),await r.docker(Ay({...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>Py)throw new Error(`the image is ${kc(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()>xy||(o=await _c(r,e,t));let i=await r.hash(a);r.say(`${t}: uploading the image (${kc(c)})`);try{return await hc({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 Be?new Error(Ry(u.status,u.body)):u}}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(Bs(o instanceof Error?o.message:String(o)))}}async function Tc(e,t,n,r=Rc){Br(t);let o=await Ey(n.path),s=await Cy(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(Bs(c instanceof Error?c.message:String(c)))}await Ec(e,t,a,n.wait!==!1)}async function Oc(e){let t=await en(e);b(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d($(" Containers run on the Plus and Pro plans.")):d($(" snoutdata containers deploy <name> <image>"));return}d(X([["name","status","image","url"],...t.containers.map(r=>[r.name,Fs(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d($(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d($(` Sent this month: ${Ms(n.usedBytes)} of ${Ms(n.allowanceBytes)}. At ${Ms(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Fr(e,t){if(t===null)return E("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=Sc(r);if("error"in o)throw new Error(o.error);n.push(o)}return E("cloud-project-containers",{ref:e,action:"egress",allow:n})}function My(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).`,$(" 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(` ${Cs(n)}${n.ports.length===0?$(" (any port)"):""}`);return t.push($(` and always: ${e.allowlist.alwaysAllowed.map(Cs).join(", ")}, and each other`)),t.push($(" snoutdata containers egress clear to allow the public internet again")),t}async function Hr(e,t){let n=t.clear?await Fr(e,null):t.set?await Fr(e,t.set):await en(e);b({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of My(n))d(r);(t.set||t.clear)&&d($(" Running containers follow it within a minute; nothing restarts."))})}function Ms(e){return`${Math.round(e/1024**3*10)/10} GB`}async function Nc(e,t){await Us(e,t);let n=await Pc(e,t);b({ref:e,containers:n?[n]:[]},()=>{d(n?`${M(t)} ${Fs(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Ic(e,t){let n=await js(e,t);b(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function $c(e){return`${$(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?M(e.message):e.message}`}async function Cc(e,t,n){let r=((await Ur(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($(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${$c(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 Ur(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${$c(a)}
533
- `),o=a.at}}pe();Te();de();function Dc(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function tn(){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 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 tn()}]}return e.map(n=>{let r=Dc(n);if(!r)throw new h(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function jc(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 tn()})}else for(let s of t){let a=Dc(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=On(s.name);if(a)throw new h(a)}let o=null;for(let s of r)o=await E("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($(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d($(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function Uc(e,t){let n=On(t);if(n)throw new h(n);let r=await E("cloud-project-secrets",{ref:e,action:"unset",name:t});b(r,()=>{d(`${t} is gone.`)})}async function Fc(e){let t=await E("cloud-project-secrets",{ref:e});b(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d($(" 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`,pt(n.updatedAt)])])}
534
- `),d($("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Bn}from"node:child_process";import{appendFileSync as dd,existsSync as Vr,readFileSync as ud,statSync as kw,unlinkSync as _w,writeFileSync as rn}from"node:fs";import{dirname as $w,join as Yr}from"node:path";import{tmpdir as vw}from"node:os";import{open as pd}from"node:fs/promises";function Bc(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 Hc(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 Gc(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 Hs={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Ly=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Gr=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"]),jy=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Uy={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},Fy=new Set(["push.settings"]),By=["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 Hy(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=By.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 Gy(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 Kc(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
536
- `)){let u=Hy(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)&&Gr.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=Uy[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(Gr.has(m)){s++;continue}r.push(u.line);continue}if(l==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(y)||(l==="SCHEMA"?Gr.has(y):Gr.has(m))||jy.test(y)||Ly.test(T)&&l!=="SCHEMA"){s++;continue}r.push(u.line)}let a=Gy(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"&&Fy.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:Ky(u,a)}}),rest:r,products:c.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function Ky(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();Te();be();de();pe();be();var Ks=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],Wc=["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"],zc=["off","advise","auto"];function In(e){return e==="done"||e==="failed"||e==="cancelled"}function Cn(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 nt(Vy(e.project))}let n=e.admin??qc(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??qc(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 Gs(qy(n),r)}function qc(e){return e===void 0||e===""?void 0:e}function qy(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:"&&!Wy(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 Wy(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var zy=6e4,Gs=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(zy)})}catch(i){let u=ur(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 Jc(s.status,c);return c}},Jy={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function Jc(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?Jy[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":dt(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 Vy(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 Yy={"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"},Xy="cloud-project-shards",nt=class{target;ref;#t;constructor(t,n=E){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:Pt(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=Yy[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(Xy,{ref:this.ref,...t})}catch(n){throw Zy(n)}}};function Pt(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 Zy(e){if(!(e instanceof Ae))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return Jc(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":dt(e.status);return new C(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Dn(e){if(Array.isArray(e))return`[${e.map(Dn).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)}:${Dn(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var zs=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
- ${Dn(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
538
- ${Dn(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
539
- ${Dn(t)}`)}},Qy=new zs,Js=["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"],ew="2024-11-05";function ee(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var J={type:"string"},Vc={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:[...Js]}},required:["op"],additionalProperties:!0},Kr={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},ft={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)."},jn=[{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:ft})},{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:ft,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:ft})},{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:ft,operation:Vc},["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:ft,operation:Vc,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:ft,id:Kr})},{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:ft,id:Kr,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:ft,id:Kr},["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:ft,id:Kr},["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"])}],tw=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"]),nw=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Yc=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Vs(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Vs)};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 Ys(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 rw(e){return{...e,annotations:tw.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:nw.has(e.name)}}}function ow(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Ln(e,t){return{jsonrpc:"2.0",id:e,result:t}}function qr(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function R(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Ln(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function sw(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Mn(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 qs(e){return{id:e.id,op:e.op,state:e.state,finished:In(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 Xc(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"||!Js.includes(r.op))return`${t}: operation.op must be one of ${Js.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 Ws(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 aw(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var iw="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 lw(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 R(e,iw,!0);s=r.shards(a)}catch(u){return R(e,u instanceof Error?u.message:String(u),!0)}let c=o.shardsPlans??Qy,i=a??"";try{switch(t){case"cluster_status":return R(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 R(e,{summary:l.summary,advice:l.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:Mn(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 R(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=Xc(n,t);if(typeof u=="string")return R(e,u,!0);let l=await s.plan(u);return c.remember(u,i),R(e,{plan:Mn(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=Xc(n,t);if(typeof u=="string")return R(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 R(e,{ran:!1,plan:Mn(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?R(e,{ran:!0,done:l.done,answer:l.answer}):R(e,{ran:!0,job:l.job,plan:Mn(l.plan),next:`Follow it with cluster_watch_job {"id": ${l.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return R(e,{jobs:await s.jobs()});let u=Ws(n);if(u===null)return R(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let l=await s.job(u);return R(e,{...qs(l),plan:l.plan&&Array.isArray(l.plan.steps)?Mn(l.plan):null})}case"cluster_watch_job":{let u=Ws(n);if(u===null)return R(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(In(y.state))return R(e,qs(y),y.state!=="done");if(Date.now()>=m)return R(e,{...qs(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=Ws(n);if(u===null)return R(e,`${t} needs id, a job number.`,!0);let l=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return R(e,{...l,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return qr(e,-32602,`unknown tool: ${t}`)}}catch(u){return R(e,aw(u),!0)}}function re(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Zc(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 Ln(r,{protocolVersion:ew,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Ln(r,{});case"tools/list":return Ln(r,{tools:ow([...jn.map(rw),...n.borrowed??[]])});case"tools/call":return cw(r,s,t,n);default:return qr(r,-32601,`unknown method: ${o}`)}}async function cw(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(jn.some(l=>l.name===o)||(r.borrowed?.some(l=>l.name===o)??!1)))return qr(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_")&&jn.some(l=>l.name===o))return lw(e,o,s,n,r);let i=re(s,"ref");if(c.includes(o)&&!i)return R(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!Yc.includes(o))return R(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${Yc.join(", ")}.`,!0);try{switch(o){case"whoami":return R(e,await n.whoami());case"list_projects":return R(e,Vs(await n.listProjects()));case"create_project":{let l=re(s,"name");return l?R(e,await n.createProject({name:l,region:re(s,"region")??void 0,teamId:re(s,"teamId")??void 0})):R(e,"create_project needs a name.",!0)}case"get_connection_url":{let l=await n.connection(i);return R(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 R(e,await n.setProjectState("pause",i));case"resume_project":return R(e,await n.setProjectState("resume",i));case"scale_project":{let l=s.instances;if(l!==void 0&&l!==1&&l!==2&&l!==3)return R(e,"instances is 1, 2 or 3.",!0);let m=s.confirm===!0&&l!==void 0;return R(e,await n.scaleProject(i,l,m))}case"usage":{let l=typeof s.days=="number"?s.days:void 0;return R(e,await n.usage(i,l))}case"export_status":return R(e,await n.exportStatus(i));case"start_export":return R(e,await n.requestExport(i));case"reset_password":{let l=await n.resetPassword(i),m=await n.connection(i);return R(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 R(e,await n.listTeams());case"get_project":return R(e,Vs(await n.getProject(i)));case"list_products":return R(e,await n.getProducts(i));case"set_product":{let l=re(s,"product");return l!=="auth"&&l!=="storage"&&l!=="data-api"?R(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?R(e,"set_product needs enabled: true or false.",!0):R(e,await n.setProduct(i,l,s.enabled))}case"list_domains":return R(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let l=re(s,"hostname");return l?R(e,await n.domainAction(i,o.split("_")[0],l)):R(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let l=await n.restoreWindow(i),m=l&&l.restore?Ys(i,l.restore):null;return R(e,m?{...l,reason:m}:l)}case"restore_to_point":{let l=re(s,"at");return l?R(e,await n.restoreTo(i,l,re(s,"name")??void 0)):R(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?R(e,"deploy_function needs a project ref and a function name.",!0):R(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?R(e,await n.listFunctions(l)):R(e,"list_functions needs a project ref.",!0)}case"size_function":{let l=re(s,"ref"),m=re(s,"name");if(!l||!m)return R(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,N=s.reset===!0;return!N&&y===void 0&&T===void 0?R(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):R(e,await n.sizeFunction(l,m,{memoryMb:y,concurrency:T,reset:N}))}case"delete_function":{let l=re(s,"ref"),m=re(s,"name");return!l||!m?R(e,"delete_function needs a project ref and a function name.",!0):R(e,await n.deleteFunction(l,m))}case"list_containers":{let l=re(s,"ref");return l?R(e,await n.listContainers(l)):R(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 R(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 R(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?R(e,`${o} needs a project ref and a container name.`,!0):R(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 R(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return R(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?R(e,await n.containerEgress(l)):R(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let l=re(s,"ref");if(!l)return R(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"))?R(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):m===(y!==void 0)?R(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):R(e,await n.setContainerEgress(l,m?null:y))}case"list_function_secrets":{let l=re(s,"ref");return l?R(e,await n.listFunctionSecrets(l)):R(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let l=re(s,"ref");if(!l)return R(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return R(e,await n.functionLogs(l,{name:re(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return R(e,await n.listTokens());case"create_token":{let l=re(s,"name");if(!l)return R(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=re(s,"expires"),y=m?sw(m):void 0;return y===null?R(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):R(e,await n.createToken(l,y,re(s,"project")??void 0))}case"revoke_token":{let l=re(s,"id");return l?R(e,await n.revokeToken(l)):R(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return R(e,await n.pushMigrations(i,{dir:re(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?R(e,await n.setProjectState("delete",i)):R(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(l=>l.name===o)?n.callBorrowed?Ln(e,await n.callBorrowed(o,s)):R(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):qr(e,-32602,`unknown tool: ${o}`)}}catch(l){return R(e,l instanceof Error?l.message:String(l),!0)}}function Qc(e){let t=e.split(`
540
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Wr=["auth","storage","data-api","push"];function Mt(e){return{error:e instanceof Error?e.message:String(e)}}async function nn(e){let[t,n,r,o]=await Promise.all([E("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Mt),E("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}),Mt),E("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Mt),E("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Mt)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:dw}}var dw={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function td(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Wr.includes(t))throw new h(`say which product: ${Wr.join(", ")}`);return t}async function Xs(e,t,n){if(t==="auth"){let r=await E("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 E("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 E(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 nd(e){let t=await nn(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($(` snoutdata products enable ${a} --ref ${e}`))})}async function rd(e,t,n){let r=await Xs(e,t,n);b(r,()=>d(r.note))}async function zr(e){let t=await E("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Zs(e,t,n){if(!n)throw new h(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await E("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function ed(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 od(e,t,n){if(t===void 0||t==="list"){let o=await zr(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 Zs(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(xr()||ed(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||ed(r.domain,e)),r.next&&d(r.next)})}async function Qs(e){return E("cloud-project-restore",{ref:e},"GET")}async function ea(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 E("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function sd(e){let t=await Qs(e);b(t,()=>{let n=t.restore,r=Ys(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d($(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function ad(e,t,n){let r=await ea(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($(" snoutdata projects list to see when it is ready"))})}async function ta(e){let{projects:t}=await qe(),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([nn(e),E("cloud-project-functions",{ref:e}).then(c=>(c.functions??[]).map(i=>i.name),Mt),E("cloud-project-secrets",{ref:e}).then(c=>(c.secrets??[]).map(i=>i.name),Mt),zr(e).then(c=>c.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Mt)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function id(e){let t=await ta(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();kt();pe();de();import{existsSync as uw,mkdirSync as pw,readFileSync as mw,renameSync as fw,writeFileSync as gw}from"node:fs";import{homedir as na}from"node:os";import{dirname as hw,join as yw}from"node:path";import{rootCertificates as ww}from"node:tls";var bw="SNOUTDATA_DB_SSLMODE";function Sw(e=na()){let t=yw(e,".snoutdata","ca-roots.pem"),n=`${ww.join(`
692
+ `:""}`),!t.yes&&!await(t.ask??dy)(lt.ask(r.ref,n)))throw new C("failed",lt.declined);let o=await T("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(lt.done(o.ref,o.copies)),o.message&&d(o.message)})}fe();pe();async function Oc(e){let t=await T("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});b(t,()=>{process.stdout.write(`${t.token}
693
+ `),d(""),d(`${L(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 Nc(){let{tokens:e}=await T("cloud-token-list");b({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${ee([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?L(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"])])}
694
+ `)})}async function Ic(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await T("cloud-token-revoke",t);b(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}fe();Te();pe();async function Cc(e){let t=await qe(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(`${Lc(t.appKey,t.serverKey)}
695
+ `),my(t.keysIssuedAt)})}async function Mc(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 y("keys rotate needs --force, because this cannot be undone");let n=Gr(await T("cloud-project-rotate-jwt",{ref:e}));b(n,()=>{process.stdout.write(`${Lc(n.appKey,n.serverKey)}
696
+ `),d(""),d(n.breaks),d(v(" snoutdata keys --json to read them again later"))})}function Lc(e,t){return ee([["KEY","VALUE"],["app key",e],["server key",t]])}function my(e){d(""),d(`The ${L("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${L("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(v(` issued ${e.slice(0,10)}`))}fe();Te();pe();import{readdir as gy,readFile as hy,stat as yy}from"node:fs/promises";import{join as Ws,relative as wy,sep as by}from"node:path";var Dc=["functions"];async function zs(e){try{return(await yy(e)).isDirectory()}catch{return!1}}async function Sy(e,t){if(t){if(!await zs(t))throw new y(`${t} is not a directory`);return t}for(let n of Dc){let r=Ws(n,e);if(await zs(r))return r}throw new y(`no folder found for ${e} at ${Dc[0]}/${e}; use --dir to say where it is.`)}async function jc(e){let t=[];async function n(r){for(let o of await gy(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Ws(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await hy(s),c=a.toString("utf8");if(Buffer.compare(Buffer.from(c,"utf8"),a)!==0)throw new y(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:wy(e,s).split(by).join("/"),text:c})}}return await n(e),t}async function ky(e,t,n){let r=await jc(t);if(r.length===0)throw new y(`${t} has no files in it`);let o=n??(r.some(c=>c.path==="index.ts")?"index.ts":"");if(!o)throw new y(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Ws(t,"..","_shared");if(!await zs(s))return{entrypoint:o,files:r};let a=(await jc(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 Js(e,t,n){Ht(t);let r=await Sy(t,n.dir),{entrypoint:o,files:s}=await ky(t,r,n.entrypoint),a=dc({entrypoint:o,files:s});return{answer:await T("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 Fc(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Js(e,t,n),c=r.functions.find(i=>i.name===t);b(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${L(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),c&&d(` ${c.url}`),n.verifyJwt||(d(""),d(L(" 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 Vs(e){return T("cloud-project-functions",{ref:e})}function Ys(e,t){return Ht(t),T("cloud-project-functions",{ref:e,action:"delete",name:t})}function Xs(e,t,n){if(Ht(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new y("functions size needs --memory MB, --concurrency N, or --reset");return T("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function _y(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 Bc(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 Hc(e,t,n){let r=await Xs(e,t,n),o=r.functions.find(s=>s.name===t);b(r,()=>{let s=o?_y(o,r.limit):null;d(`${L(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=Bc(r.limit);a&&d(v(` ${a}`))})}async function Gc(e){let t=await Vs(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(`${ee([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":L("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])])}
697
+ `),t.limit.maxFunctions!==null&&d(v(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Bc(t.limit);n&&d(v(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Uc(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"?L("error "):"";return`${v(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Kc(e,t,n){let r=a=>T("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(`${Uc(a)}
698
+ `)}),!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(`${Uc(c)}
699
+ `),s=c.at}}async function qc(e,t){let n=await Ys(e,t);b(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}fe();import{spawn as zy}from"node:child_process";import{createHash as Wy}from"node:crypto";import{createReadStream as td}from"node:fs";import{mkdtemp as Jy,rm as Vy,stat as Xr}from"node:fs/promises";import{request as Yy}from"node:http";import{request as Xy}from"node:https";import{tmpdir as Zy}from"node:os";import{basename as Qy,dirname as ew,join as na}from"node:path";var $y="x-snoutdata-upload-bytes",Py="x-snoutdata-upload-sha256",xy="/done",Ey=/^[0-9a-f]{64}$/,Ay=/^sha256:[0-9a-f]{64}$/;function Ry(e){return Ey.test(e)}var ze=class extends Error{constructor(n,r){super(`the upload failed (${n})`);this.status=n;this.body=r}};function Zs(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function zc(e){let t=String(Zs(e).digest??"");if(!Ay.test(t))throw new ze(502,JSON.stringify({error:"The upload finished without saying what it stored, so there is nothing to deploy. Build again."}));return t}var Ty=15*6e4,Oy=2e3;async function Wc(e){let{ticketUrl:t,file:n,transport:r}=e;if(!Ry(e.sha256))throw new Error("an upload is declared with its SHA-256, in hex");let o=await r.call("POST",t,{[$y]:String(e.bytes),[Py]:e.sha256});if(o.status===405){e.step?.("uploading");let l=await r.put(t,n,{});if(l.status!==200)throw new ze(l.status,l.body);return zc(l.body)}if(o.status!==200)throw new ze(o.status,o.body);let s=Zs(o.body);if(typeof s.url!="string"||!s.url.startsWith("https://")||!s.headers||typeof s.headers!="object")throw new ze(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 ze(a.status===403?401:502,JSON.stringify({error:m}))}e.step?.("storing");let c=r.now()+(e.waitMs??Ty),i=e.pollMs??Oy,u=!1;for(;!u;){let l=await r.call("POST",`${t}${xy}`,{});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 ze(l.status,l.body)}for(;;){let l=await r.call("GET",t,{});if(l.status!==200)throw new ze(l.status,l.body);let m=Zs(l.body);if(m.state==="stored")return zc(l.body);if(m.state==="failed")throw new ze(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 ze(504,JSON.stringify({error:"The project's host took too long to store the image. Build again in a moment."}));await r.sleep(i)}}pe();var Ny=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Iy(e){return Ny.test(e)}function Vc(e){return Iy(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 Cy=/^sha256:[0-9a-f]{64}$/,My=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,Ly=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,Dy=/^(?:[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 jy(e){return Cy.test(e)}function Yc(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),!jy(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),!My.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()),Dy.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=>!Ly.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 JP=["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 Qs(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 Uy(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=Qs(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 Fy(e,t,n){let r=Qs(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 By=[["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 VP=1024**3;var Jc=16,Hy=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,Gy=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function Ky(e,t){let n=e??[],r={error:`${t}: ports are up to ${Jc} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Jc)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 qy(e){let t=e.trim().toLowerCase(),n=Gy.exec(t);if(n){let r=Qs(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 By.some(([u,l])=>o>=l&&Fy(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 Uy(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.`}:Hy.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 Xc(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=r?t.slice(0,n):t,s=Ky(r?t.slice(n+1).split(",").map(c=>c.trim()):[],o.trim());if("error"in s)return s;let a=qy(o);return typeof a!="string"?a:{destination:a,ports:s}}function ea(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var tw=new Set(["pending","pulling","starting","restarting"]),nw=5*6e4;function eo(e){let t=Vc(e);if(t)throw new Error(t)}function ra(e,t,n){eo(t);let r=Yc(n.image);if("error"in r)throw new Error(r.error);return T("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 dn(e){return T("cloud-project-containers",{ref:e})}function oa(e,t){return eo(t),T("cloud-project-containers",{ref:e,action:"delete",name:t})}function sa(e,t){return eo(t),T("cloud-project-containers",{ref:e,action:"restart",name:t})}function Zr(e,t){return T("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 nd(e,t){let n=Date.now()+nw,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await dn(e)).containers.find(o=>o.name===t)??null,!r||!tw.has(r.status))return r;return r}function aa(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 rd(e,t,n){await od(e,t,await ra(e,t,n),n.wait!==!1)}async function od(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 nd(e,t)??o),b({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${L(t)} deployed.`);return}d(`${L(t)} ${aa(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(L(" 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 rw=2*1024**3,ow=3*6e4;async function sw(e){let t;try{t=await Xr(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 Xr(na(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:ew(e),file:e}}function aw(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function ia(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 iw(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?ia(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 lw(e){return new Promise((t,n)=>{let r=zy("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function sd(e,t,n,r){return new Promise((o,s)=>{let a=new URL(e),c=(a.protocol==="http:"?Yy:Xy)(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 cw(e,t,n){let r=Object.keys(n).length>0?n:{"content-type":"application/octet-stream","content-length":String((await Xr(t)).size)};return sd(e,"PUT",r,o=>{let s=td(t);s.on("error",a=>o.destroy(a)),s.pipe(o)})}function dw(e){return new Promise((t,n)=>{let r=Wy("sha256");td(e).on("error",n).on("data",o=>r.update(o)).on("end",()=>t(r.digest("hex")))})}var uw={call:(e,t,n)=>sd(t,e,{"content-length":"0",...n},r=>r.end()),put:cw,sleep:e=>new Promise(t=>setTimeout(t,e)),now:()=>Date.now()},ad={docker:lw,scratch:()=>Jy(na(Zy(),"snoutdata-build-")),remove:e=>Vy(e,{recursive:!0,force:!0}),size:async e=>(await Xr(e)).size,hash:dw,transport:uw,ask:e=>T("cloud-project-containers",e),say:e=>d(v(e)),now:()=>Date.now()};function Zc(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function Qc(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 pw(e,t,n,r=ad){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 Qc(r,e,t),s=await r.scratch();try{let a=na(s,"image.tar");if(r.say(`${t}: building ${n.file?Qy(n.file):n.context} for linux/arm64`),await r.docker(aw({...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>rw)throw new Error(`the image is ${Zc(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()>ow||(o=await Qc(r,e,t));let i=await r.hash(a);r.say(`${t}: uploading the image (${Zc(c)})`);try{return await Wc({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 ze?new Error(iw(u.status,u.body)):u}}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(ia(o instanceof Error?o.message:String(o)))}}async function id(e,t,n,r=ad){eo(t);let o=await sw(n.path),s=await pw(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(ia(c instanceof Error?c.message:String(c)))}await od(e,t,a,n.wait!==!1)}async function ld(e){let t=await dn(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(ee([["name","status","image","url"],...t.containers.map(r=>[r.name,aa(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: ${ta(n.usedBytes)} of ${ta(n.allowanceBytes)}. At ${ta(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Qr(e,t){if(t===null)return T("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=Xc(r);if("error"in o)throw new Error(o.error);n.push(o)}return T("cloud-project-containers",{ref:e,action:"egress",allow:n})}function mw(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(` ${ea(n)}${n.ports.length===0?v(" (any port)"):""}`);return t.push(v(` and always: ${e.allowlist.alwaysAllowed.map(ea).join(", ")}, and each other`)),t.push(v(" snoutdata containers egress clear to allow the public internet again")),t}async function to(e,t){let n=t.clear?await Qr(e,null):t.set?await Qr(e,t.set):await dn(e);b({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of mw(n))d(r);(t.set||t.clear)&&d(v(" Running containers follow it within a minute; nothing restarts."))})}function ta(e){return`${Math.round(e/1024**3*10)/10} GB`}async function cd(e,t){await sa(e,t);let n=await nd(e,t);b({ref:e,containers:n?[n]:[]},()=>{d(n?`${L(t)} ${aa(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function dd(e,t){let n=await oa(e,t);b(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function ed(e){return`${v(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?L(e.message):e.message}`}async function ud(e,t,n){let r=((await Zr(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(`${ed(s)}
700
+ `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await Zr(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${ed(a)}
701
+ `),o=a.at}}fe();Te();pe();function md(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function un(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function fd(e,t){if(e.length===0)throw new y("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new y("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await un()}]}return e.map(n=>{let r=md(n);if(!r)throw new y(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function gd(e,t,n){if(t.length===0)throw new y("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new y("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await un()})}else for(let s of t){let a=md(s);if(!a)throw new y(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Bn(s.name);if(a)throw new y(a)}let o=null;for(let s of r)o=await T("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});b(o,()=>{for(let s of r)d(`${L(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 hd(e,t){let n=Bn(t);if(n)throw new y(n);let r=await T("cloud-project-secrets",{ref:e,action:"unset",name:t});b(r,()=>{d(`${t} is gone.`)})}async function yd(e){let t=await T("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(`${ee([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,St(n.updatedAt)])])}
702
+ `),d(v("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Xn}from"node:child_process";import{appendFileSync as Bd,existsSync as lo,readFileSync as Hd,statSync as Qw,unlinkSync as eb,writeFileSync as mn}from"node:fs";import{dirname as tb,join as co}from"node:path";import{tmpdir as nb}from"node:os";import{open as Gd}from"node:fs/promises";function wd(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 bd(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 Sd(e){let t=e.split(`
703
+ `).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 la={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},gw=/^(snoutpod_admin|snout_[a-z]+_admin)$/,no=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"]),hw=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,yw={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},ww=new Set(["push.settings"]),bw=["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 Sw(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=bw.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 kw(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 kd(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
704
+ `)){let u=Sw(i);if(!u)continue;let{type:l,schema:m,name:h,owner:A}=u;if(l==="TABLE DATA"||l==="SEQUENCE SET"){let se=/^(schema_migrations|migrations)$/.test(h)&&no.has(m);if(/realtime/.test(m)||m==="snout_live"||se||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(m==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let F=yw[m];if(F){F==="storage"&&(h==="objects"||h==="files")&&o.add("Storage objects are restored as rows; the files themselves are not part of an export. Upload them again into this project."),n.set(F,[...n.get(F)??[],u]);continue}if(no.has(m)){s++;continue}r.push(u.line);continue}if(l==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(l==="SCHEMA"?no.has(h):no.has(m))||hw.test(h)||gw.test(A)&&l!=="SCHEMA"){s++;continue}r.push(u.line)}let a=kw(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"&&ww.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:_w(u,a)}}),rest:r,products:c.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function _w(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}fe();Te();ke();pe();fe();ke();var da=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],vd=["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"],$d=["off","advise","auto"];function Gn(e){return e==="done"||e==="failed"||e==="cancelled"}function Kn(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 ct(Ew(e.project))}let n=e.admin??_d(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??_d(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 ca(vw(n),r)}function _d(e){return e===void 0||e===""?void 0:e}function vw(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:"&&!$w(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 $w(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var Pw=6e4,ca=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(Pw)})}catch(i){let u=kr(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 Pd(s.status,c);return c}},xw={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function Pd(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?xw[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":yt(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 Ew(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 Aw={"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"},Rw="cloud-project-shards",ct=class{target;ref;#t;constructor(t,n=T){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:Nt(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=Aw[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(Rw,{ref:this.ref,...t})}catch(n){throw Tw(n)}}};function Nt(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 Tw(e){if(!(e instanceof Oe))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return Pd(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":yt(e.status);return new C(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function zn(e){if(Array.isArray(e))return`[${e.map(zn).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)}:${zn(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var ma=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}
705
+ ${zn(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
706
+ ${zn(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
707
+ ${zn(t)}`)}},Ow=new ma,fa=["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"],Nw="2024-11-05";function ne(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var X={type:"string"},xd={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:[...fa]}},required:["op"],additionalProperties:!0},ro={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},_t={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)."},Jn=[{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:ne({})},{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:ne({})},{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:ne({name:X,region:X,teamId:X},["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:ne({ref:X},["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:ne({ref:X},["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:ne({ref:X,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:ne({ref:X},["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:ne({ref:X,dir:X,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:ne({ref:X,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:ne({ref:X},["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:ne({ref:X},["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:ne({ref:X},["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:ne({})},{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:ne({})},{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:ne({name:X,expires:X,project:X},["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:ne({id:X},["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:ne({ref:X,name:X,dir:X,entrypoint:X,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:ne({ref:X},["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:ne({ref:X,name:X,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:ne({ref:X,name:X},["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:ne({ref:X},["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:ne({ref:X,name:X,image:X,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:ne({ref:X,name:X},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:ne({ref:X,name:X},["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:ne({ref:X,name:X,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:ne({ref:X},["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:ne({ref:X,allow:{type:"array",items:X},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:ne({ref:X},["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:ne({ref:X,name:X,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:ne({ref:X},["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:ne({ref:X},["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:ne({ref:X,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:ne({ref:X},["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:ne({ref:X,hostname:X},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:ne({ref:X,hostname:X},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:ne({ref:X,hostname:X},["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:ne({ref:X},["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:ne({ref:X,at:X,name:X},["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:ne({project:_t})},{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:ne({project:_t,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:ne({project:_t})},{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:ne({project:_t,operation:xd},["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:ne({project:_t,operation:xd,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:ne({project:_t,id:ro})},{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:ne({project:_t,id:ro,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:ne({project:_t,id:ro},["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:ne({project:_t,id:ro},["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:ne({ref:X},["ref"])}],Iw=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"]),Cw=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Ed=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function ga(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(ga)};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 ha(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 Mw(e){return{...e,annotations:Iw.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Cw.has(e.name)}}}function Lw(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Wn(e,t){return{jsonrpc:"2.0",id:e,result:t}}function oo(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 Wn(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Dw(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function qn(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 ua(e){return{id:e.id,op:e.op,state:e.state,finished:Gn(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 Ad(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"||!fa.includes(r.op))return`${t}: operation.op must be one of ${fa.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 pa(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 jw(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var Uw="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 Fw(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,Uw,!0);s=r.shards(a)}catch(u){return O(e,u instanceof Error?u.message:String(u),!0)}let c=o.shardsPlans??Ow,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:qn(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=Ad(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:qn(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=Ad(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 h=n.confirm===!0;return O(e,{ran:!1,plan:qn(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?O(e,{ran:!0,done:l.done,answer:l.answer}):O(e,{ran:!0,job:l.job,plan:qn(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=pa(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,{...ua(l),plan:l.plan&&Array.isArray(l.plan.steps)?qn(l.plan):null})}case"cluster_watch_job":{let u=pa(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 h=await s.job(u);if(Gn(h.state))return O(e,ua(h),h.state!=="done");if(Date.now()>=m)return O(e,{...ua(h),note:`Still ${h.state} after ${l}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(A=>setTimeout(A,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=pa(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 oo(e,-32602,`unknown tool: ${t}`)}}catch(u){return O(e,jw(u),!0)}}function ae(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Rd(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 Wn(r,{protocolVersion:Nw,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Wn(r,{});case"tools/list":return Wn(r,{tools:Lw([...Jn.map(Mw),...n.borrowed??[]])});case"tools/call":return Bw(r,s,t,n);default:return oo(r,-32601,`unknown method: ${o}`)}}async function Bw(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Jn.some(l=>l.name===o)||(r.borrowed?.some(l=>l.name===o)??!1)))return oo(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_")&&Jn.some(l=>l.name===o))return Fw(e,o,s,n,r);let i=ae(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&&!Ed.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: ${Ed.join(", ")}.`,!0);try{switch(o){case"whoami":return O(e,await n.whoami());case"list_projects":return O(e,ga(await n.listProjects()));case"create_project":{let l=ae(s,"name");return l?O(e,await n.createProject({name:l,region:ae(s,"region")??void 0,teamId:ae(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,ga(await n.getProject(i)));case"list_products":return O(e,await n.getProducts(i));case"set_product":{let l=ae(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=ae(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?ha(i,l.restore):null;return O(e,m?{...l,reason:m}:l)}case"restore_to_point":{let l=ae(s,"at");return l?O(e,await n.restoreTo(i,l,ae(s,"name")??void 0)):O(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let l=ae(s,"ref"),m=ae(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:ae(s,"dir")??void 0,entrypoint:ae(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let l=ae(s,"ref");return l?O(e,await n.listFunctions(l)):O(e,"list_functions needs a project ref.",!0)}case"size_function":{let l=ae(s,"ref"),m=ae(s,"name");if(!l||!m)return O(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,A=typeof s.concurrency=="number"?s.concurrency:void 0,E=s.reset===!0;return!E&&h===void 0&&A===void 0?O(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):O(e,await n.sizeFunction(l,m,{memoryMb:h,concurrency:A,reset:E}))}case"delete_function":{let l=ae(s,"ref"),m=ae(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=ae(s,"ref");return l?O(e,await n.listContainers(l)):O(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let l=ae(s,"ref"),m=ae(s,"name"),h=ae(s,"image");if(!l||!m||!h)return O(e,"deploy_container needs a project ref, a container name and an image.",!0);let A=typeof s.port=="number"?s.port:void 0;return O(e,await n.deployContainer(l,m,{image:h,...A===void 0?{}:{port:A}}))}case"restart_container":case"delete_container":{let l=ae(s,"ref"),m=ae(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=ae(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:ae(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"container_egress":{let l=ae(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=ae(s,"ref");if(!l)return O(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(A=>typeof A=="string"))?O(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):m===(h!==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:h))}case"list_function_secrets":{let l=ae(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=ae(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:ae(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return O(e,await n.listTokens());case"create_token":{let l=ae(s,"name");if(!l)return O(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=ae(s,"expires"),h=m?Dw(m):void 0;return h===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,h,ae(s,"project")??void 0))}case"revoke_token":{let l=ae(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:ae(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?Wn(e,await n.callBorrowed(o,s)):O(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):oo(e,-32602,`unknown tool: ${o}`)}}catch(l){return O(e,l instanceof Error?l.message:String(l),!0)}}function Td(e){let t=e.split(`
708
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var so=["auth","storage","data-api","push"];function Gt(e){return{error:e instanceof Error?e.message:String(e)}}async function pn(e){let[t,n,r,o]=await Promise.all([T("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Gt),T("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}),Gt),T("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Gt),T("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Gt)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Hw}}var Hw={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Nd(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!so.includes(t))throw new y(`say which product: ${so.join(", ")}`);return t}async function ya(e,t,n){if(t==="auth"){let r=await T("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 T("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await T(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 Id(e){let t=await pn(e);b(t,()=>{let n=(c,i,u="")=>[c,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?L("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${ee([["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")])}
709
+ `);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 Cd(e,t,n){let r=await ya(e,t,n);b(r,()=>d(r.note))}async function ao(e){let t=await T("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function wa(e,t,n){if(!n)throw new y(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await T("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Od(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(`${ee(n)}
710
+ `)}async function Md(e,t,n){if(t===void 0||t==="list"){let o=await ao(e);b(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${ee([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":L("no"),s.certificate?.state??""])])}
711
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new y(`unknown command: domains ${t}`);let r=await wa(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(Ur()||Od(r.domain,e),re("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||Od(r.domain,e)),r.next&&d(r.next)})}async function ba(e){return T("cloud-project-restore",{ref:e},"GET")}async function Sa(e,t,n){if(Number.isNaN(Date.parse(t)))throw new y(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return T("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Ld(e){let t=await ba(e);b(t,()=>{let n=t.restore,r=ha(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 Dd(e,t,n){let r=await Sa(e,t,n);b(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${L(r.project.name)} (${r.project.ref}).`),d(v(" snoutdata projects list to see when it is ready"))})}async function ka(e){let{projects:t}=await Xe(),n=t.find(c=>c.ref===e);if(!n)throw new y(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([pn(e),T("cloud-project-functions",{ref:e}).then(c=>(c.functions??[]).map(i=>i.name),Gt),T("cloud-project-secrets",{ref:e}).then(c=>(c.secrets??[]).map(i=>i.name),Gt),ao(e).then(c=>c.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Gt)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function jd(e){let t=await ka(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(`${ee([["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)]])}
712
+ `)})}fe();ke();Tt();fe();pe();import{existsSync as Gw,mkdirSync as Kw,readFileSync as qw,renameSync as zw,writeFileSync as Ww}from"node:fs";import{homedir as _a}from"node:os";import{dirname as Jw,join as Vw}from"node:path";import{rootCertificates as Yw}from"node:tls";var Xw="SNOUTDATA_DB_SSLMODE";function Zw(e=_a()){let t=Vw(e,".snoutdata","ca-roots.pem"),n=`${Yw.join(`
545
713
  `)}
546
- `,r=null;try{r=uw(t)?mw(t,"utf8"):null}catch{r=null}if(r!==n){pw(hw(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;gw(o,n,{mode:420}),fw(o,t)}return t}function Un(e,t=process.env,n=na()){if(e==="disable")return{mode:"disable"};let r=t[bw]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Sw(n)}}function Fn(e,t=process.env,n=na()){let{mode:r,rootCert:o}=Un(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function md(e){try{let{projects:t}=await qe(),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 oa(e){await md(e);let t=await Fe(e);b(t,()=>{process.stdout.write(`${t.uri}
547
- `)})}async function fd(e,t){if(t.length===0&&!Se()){let o=Yt();te("usage",`db psql with no statements opens an interactive session, and ${o?Vt(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await md(e);let n=await Fe(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Bn("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Fn(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 gd(e,t,n="DATABASE_URL"){let r=Yr(e,".env"),o=`${n}=${t}`,s=Pw(e);if(!Vr(r))return rn(r,`${o}
548
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&kw(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=ud(r,"utf8"),c=new RegExp(`^${n}=.*$`,"m");return c.test(a)?(rn(r,a.replace(c,o)),{path:r,replaced:!0,ignored:s}):(dd(r,a.endsWith(`
714
+ `,r=null;try{r=Gw(t)?qw(t,"utf8"):null}catch{r=null}if(r!==n){Kw(Jw(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Ww(o,n,{mode:420}),zw(o,t)}return t}function Vn(e,t=process.env,n=_a()){if(e==="disable")return{mode:"disable"};let r=t[Xw]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Zw(n)}}function Yn(e,t=process.env,n=_a()){let{mode:r,rootCert:o}=Vn(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Kd(e){try{let{projects:t}=await Xe(),n=t.find(r=>r.ref===e);n?.readOnly?ue(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&ue(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function $a(e){await Kd(e);let t=await qe(e);b(t,()=>{process.stdout.write(`${t.uri}
715
+ `)})}async function qd(e,t){if(t.length===0&&!_e()){let o=rn();re("usage",`db psql with no statements opens an interactive session, and ${o?nn(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Kd(e);let n=await qe(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,...Yn(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 zd(e,t,n="DATABASE_URL"){let r=co(e,".env"),o=`${n}=${t}`,s=rb(e);if(!lo(r))return mn(r,`${o}
716
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Qw(r).mode&63&&ue(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Hd(r,"utf8"),c=new RegExp(`^${n}=.*$`,"m");return c.test(a)?(mn(r,a.replace(c,o)),{path:r,replaced:!0,ignored:s}):(Bd(r,a.endsWith(`
549
717
  `)?`${o}
550
718
  `:`
551
719
  ${o}
552
- `),{path:r,replaced:!1,ignored:s})}function Pw(e){let t=e;for(;!Vr(Yr(t,".git"));){let s=$w(t);if(s===t)return!1;t=s}let n=Yr(e,".gitignore"),r=Vr(n)?ud(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(dd(n,r===""||r.endsWith(`
720
+ `),{path:r,replaced:!1,ignored:s})}function rb(e){let t=e;for(;!lo(co(t,".git"));){let s=tb(t);if(s===t)return!1;t=s}let n=co(e,".gitignore"),r=lo(n)?Hd(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Bd(n,r===""||r.endsWith(`
553
721
  `)?`.env
554
722
  `:`
555
723
  .env
556
- `),!0)}async function hd(e,t={}){if(t.statusOnly){let{export:a}=await Zt(e);return b({ref:e,export:a},()=>ra(a)),0}let n=await An(e);if(!n.export.pending&&n.export.url&&!t.out)return b({ref:e,export:n.export},()=>ra(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Ew(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},()=>ra(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 Aw(r.url,t.out),s=r.rolesSql?xw(t.out):null;return s&&r.rolesSql&&rn(s,r.rolesSql.endsWith(`
724
+ `),!0)}async function Wd(e,t={}){if(t.statusOnly){let{export:a}=await ln(e);return b({ref:e,export:a},()=>va(a)),0}let n=await jn(e);if(!n.export.pending&&n.export.url&&!t.out)return b({ref:e,export:n.export},()=>va(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await sb(e,t.timeoutMs);if(r.error&&re("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return b({ref:e,export:r},()=>va(r)),0;r.url||re("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await ab(r.url,t.out),s=r.rolesSql?ob(t.out):null;return s&&r.rolesSql&&mn(s,r.rolesSql.endsWith(`
557
725
  `)?r.rolesSql:`${r.rolesSql}
558
- `,"utf8"),b({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${gt(o)} to ${t.out}.`),d($(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d($("Into any other Postgres, in this order:")),d($(` psql --dbname <your database> -f ${s}`)),d($(` pg_restore --dbname <your database> ${t.out}`)),d($("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d($("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d($("Owners and grants are skipped: this export carries no roles file.")))}),0}function xw(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Ew(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Zt(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 Aw(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 pd(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 ra(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 ${pt(e.completedAt)}, ${gt(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
559
- `),e.urlExpiresAt&&d($(`That link stops working ${pt(e.urlExpiresAt)}.`))):d($("The link has expired. Run the export again to sign a new one; the copy is kept."))}function gt(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 yd(e,t){let n=t.file;n||te("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Vr(n)||te("usage",`no file at ${n}`,{file:n});let r=await pd(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Bc(o.slice(0,s.bytesRead)),c=await Fe(e);c.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await qe(),u=i.find(oe=>oe.ref===e),l=await Rw(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=Hc(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 cd("pg_restore",["--list",n]),Z=await cd("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=Kc(oe.stdout,Z.stdout),y.products.length>0){let le=await nn(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 Tw(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 Ow(c,n,y):await Jr(c,m.tool,n),N=Gc(T.stderr);for(let oe of y?.notes??[])d($(oe));return T.code!==0&&N.fatal.length===0&&N.total>0?(b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:N.harmless},()=>{d(`Restored into ${e}.`),d(`${N.harmless} error${N.harmless===1?"":"s"} above ${N.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:N.fatal}),b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function Rw(e){return new Promise(t=>{let n=Bn("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,...Fn(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 Tw(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Bn("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,...Fn(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 cd(e,t){return new Promise(n=>{let r=Bn(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 Ow(e,t,n){let r=Yr(vw(),`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){rn(`${r}.sql`,`set role ${Hs[c]};
563
- ${i.map(y=>`delete from ${y};`).join(`
726
+ `,"utf8"),b({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${vt(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 ob(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function sb(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await ln(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 ab(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 Gd(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 va(e){if(e.error){ue(`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)}, ${vt(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
727
+ `),e.urlExpiresAt&&d(v(`That link stops working ${St(e.urlExpiresAt)}.`))):d(v("The link has expired. Run the export again to sign a new one; the copy is kept."))}function vt(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 Jd(e,t){let n=t.file;n||re("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),lo(n)||re("usage",`no file at ${n}`,{file:n});let r=await Gd(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=wd(o.slice(0,s.bytesRead)),c=await qe(e);c.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await Xe(),u=i.find(se=>se.ref===e),l=await ib(c);l===null&&re("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=bd(a,{existingTables:l,readOnly:u?.readOnly===!0},{force:t.force});m.ok||re("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let se=await Fd("pg_restore",["--list",n]),F=await Fd("pg_restore",["--section=post-data","--file=-",n]);if((se.code!==0||F.code!==0)&&re("failed",`pg_restore could not read ${n}: ${(se.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=kd(se.stdout,F.stdout),h.products.length>0){let H=await pn(e),M=G=>!!(G&&typeof G=="object"&&"enabled"in G&&G.enabled),de=h.products.filter(G=>!M(H[G]));de.length>0&&re("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:
728
+ ${de.map(G=>` snoutdata products enable ${G} --ref ${e}`).join(`
729
+ `)}`,{ref:e,file:n,refusal:"products-off",products:de})}}if(h&&h.platformData.length>0){let se=h.platformData.flatMap(({tables:M})=>M),F=await lb(c,se);F===null&&re("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let H=h.platformData.filter(({tables:M})=>M.some(de=>F.includes(de))).map(({product:M})=>M);H.length>0&&re("conflict",`${H.join(", ")} on ${e} ${H.length===1?"has":"have"} not finished setting up ${H.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:H})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let A=h?await cb(c,n,h):await io(c,m.tool,n),E=Sd(A.stderr);for(let se of h?.notes??[])d(v(se));return A.code!==0&&E.fatal.length===0&&E.total>0?(b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:E.harmless},()=>{d(`Restored into ${e}.`),d(`${E.harmless} error${E.harmless===1?"":"s"} above ${E.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):(A.code!==0&&re("failed",`${m.tool} exited ${A.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:E.fatal}),b({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function ib(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,...Yn(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{ue(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 lb(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,...Yn(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(`
730
+ `).map(c=>c.trim()).filter(Boolean):null))})}function Fd(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=>{ue(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 cb(e,t,n){let r=co(nb(),`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){mn(`${r}.sql`,`set role ${la[c]};
731
+ ${i.map(h=>`delete from ${h};`).join(`
564
732
  `)}
565
- `,"utf8");let m=await Jr(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}rn(`${r}.platform`,`${u.join(`
733
+ `,"utf8");let m=await io(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}mn(`${r}.platform`,`${u.join(`
566
734
  `)}
567
- `,"utf8");let l=await Jr(e,"pg_restore",t,["--data-only",`--role=${Hs[c]}`,`--use-list=${r}.platform`]);o=o||l.code,s+=l.stderr}rn(`${r}.rest`,`${n.rest.join(`
735
+ `,"utf8");let l=await io(e,"pg_restore",t,["--data-only",`--role=${la[c]}`,`--use-list=${r}.platform`]);o=o||l.code,s+=l.stderr}mn(`${r}.rest`,`${n.rest.join(`
568
736
  `)}
569
- `,"utf8");let a=await Jr(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{_w(`${r}${a}`)}catch{}}}function Jr(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=Bn(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Fn(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();Te();de();function Iw(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 wd(e,t,n){let r=Iw(t,n.level);if(r.action==="list"){let s=await E("cloud-project-db-access",{ref:e,action:"list"});b(s,()=>Cw(s));return}if(r.action==="grant"){let s=await E("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});b(s,()=>Mw(s));return}let o=await E("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});b(o,()=>Lw(o))}function Cw(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($(` psql "${t.connection.keywords}"`)))}function Mw(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($(` psql "${e.connection.keywords}"`))}function Dw(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Lw(e){let t=Dw(e);for(let n of t.slice(0,-1))d(n);ce(t[t.length-1])}pe();Te();de();var jw=6e4;async function sa(e,t={}){return E("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function Uw(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 Fw(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function bd(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 Bw={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 Sd(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[Fw(e.at),Bw[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function Hw(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function Gw(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($(` 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,bd(Date.now()-a.joined_at),bd(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>jw?" (silent: likely gone)":"")]);d(X([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
737
+ `,"utf8");let a=await io(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{eb(`${r}${a}`)}catch{}}}function io(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,...Yn(e.ssl)}}),i="";c.stderr?.on("data",u=>{let l=String(u);i+=l,process.stderr.write(l)}),c.on("error",u=>{ue(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}))})}fe();Te();pe();function ub(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new y(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new y("db access list takes no argument");if(t!==void 0)throw new y("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new y("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 y(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new y("db access revoke needs an email address or a role name");if(t!==void 0)throw new y("--level is for db access grant");return{action:"revoke",who:r};default:throw new y(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Vd(e,t,n){let r=ub(t,n.level);if(r.action==="list"){let s=await T("cloud-project-db-access",{ref:e,action:"list"});b(s,()=>pb(s));return}if(r.action==="grant"){let s=await T("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});b(s,()=>mb(s));return}let o=await T("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});b(o,()=>gb(o))}function pb(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(`${ee([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?L(n.email):n.email,n.level,n.role])])}
738
+ `);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 mb(e){e.connection&&process.stdout.write(`${e.connection.keywords}
739
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${L(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 fb(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function gb(e){let t=fb(e);for(let n of t.slice(0,-1))d(n);ue(t[t.length-1])}fe();Te();pe();var hb=6e4;async function Pa(e,t={}){return T("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function yb(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 y(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function wb(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Yd(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 bb={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 Xd(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[wb(e.at),bb[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function Sb(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function kb(e){let t=e.inspect;if(d(L(`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(L(`${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,Yd(Date.now()-a.joined_at),Yd(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>hb?" (silent: likely gone)":"")]);d(ee([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
572
740
  `).map(a=>` ${a}`).join(`
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} ${Hw(a)}`)}}}async function kd(e,t={}){if(t.watch&&xr())throw new h("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await sa(e,{channel:t.channel});if(b(n.inspect,()=>Gw(n)),!t.watch)return 0;d(""),d($("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 sa(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Sd(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($(` now ${a||"no channels open"}
575
- `))}}}async function _d(e,t={}){let n=t.since?Uw(t.since):0,r=await sa(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(`${Sd(o)}
576
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d($(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}pe();Te();be();de();var Xr=[{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 $d(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:!Xr.some(r=>r.baseUrl&&r.kind==="openai"&&r.baseUrl===n)}function vd(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 Pd(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 qw(e){return e===void 0||e.trim()===""?void 0:Number(e)}function xd(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(qw);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 Qr=class{send;constructor(t){this.send=t}providers(){return this.list("admin/providers")}putProvider(t,n){return this.call("PUT",`admin/providers/${rt(t)}`,n)}deleteProvider(t){return this.none("DELETE",`admin/providers/${rt(t)}`)}upstreamModels(t){return typeof t=="string"?this.list(`admin/providers/${rt(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/${Zr(t)}`,n)}deleteModel(t){return this.call("DELETE",`admin/models/${Zr(t)}`)}routes(){return this.list("admin/routes")}putRoute(t,n){return this.call("PUT",`admin/routes/${Zr(t)}`,n)}deleteRoute(t){return this.none("DELETE",`admin/routes/${Zr(t)}`)}health(){return this.list("admin/health")}credentials(){return this.list("admin/credentials")}putCredential(t,n){return this.call("PUT",`admin/credentials/${rt(t)}`,n)}deleteCredential(t){return this.none("DELETE",`admin/credentials/${rt(t)}`)}keys(){return this.list("admin/keys")}createKey(t){return this.call("POST","admin/keys",t)}revokeKey(t){return this.none("DELETE",`admin/keys/${rt(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/${rt(t)}`,n)}deleteBudget(t){return this.none("DELETE",`admin/budgets/${rt(t)}`)}limits(){return this.list("admin/limits")}createLimit(t){return this.call("POST","admin/limits",t)}deleteLimit(t){return this.none("DELETE",`admin/limits/${rt(t)}`)}groups(){return this.list("admin/groups")}usage(t={}){return this.call("GET",`admin/usage${Ed(t)}`)}requests(t={}){return this.call("GET",`admin/requests${Ed(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 rt(e){return encodeURIComponent(e)}function Zr(e){return e.split("/").map(rt).join("/")}function Ed(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 Hn(e){if(e==null||e==="")return null;let t=Number(e);return Number.isFinite(t)?t:null}async function to(e,t){return E("cloud-project-ai",t===void 0?{ref:e}:{ref:e,enable:t})}function Ww(e){if(e instanceof Ae){let t=e.body?.error;if(t&&typeof t=="object"&&typeof t.message=="string")return new Ae(e.status,t.message,typeof t.code=="string"?t.code:null,e.body)}return e}function zw(e){return async(t,n,r)=>{try{return await E("cloud-project-ai",{ref:e,admin:{method:t,path:n,...r===void 0?{}:{body:r}}})}catch(o){throw Ww(o)}}}function Pe(e){return new Qr(zw(e))}function Jw(e){return e instanceof Ae&&(e.status===502||e.status===404&&e.serverCode!=="ai-off")}function aa(e){return Number.isInteger(e)?`$${e}`:Math.abs(e*100-Math.round(e*100))<1e-9?`$${e.toFixed(2)}`:`$${e}`}function Rd(e){return e?`${aa(e.input)} in, ${aa(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 ia(e){let t=e.period==="rolling"?`per ${e.days??"?"} days`:e.period==="since"?`since ${e.starts_on??"?"}`:`a ${e.period}`,n=Hn(e.amount);return`${n===null?`$${e.amount}`:aa(n)} ${t}, ${e.hard?"hard":"soft"}`}function Vw(e){let t=Hn(e);return t===null?"-":`$${t.toFixed(t!==0&&t<.01?4:2)}`}function eo(e){return e===null?"never":new Date(e*1e3).toISOString().slice(0,16).replace("T"," ")}function Od(e,t=Date.now()){return new Date(t-(e-1)*864e5).toISOString().slice(0,10)}function Yw(e,t){return`${e} ${t}${e===1?"":"s"}`}async function Nd(e){let t=await to(e);if(!t.ai.enabled){b({ref:e,enabled:!1,endpoint:t.ai.endpoint},()=>{d(`AI is off for ${e}.`),d($(` 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:Od(30)})]),a=[r,o,s].find(y=>y.status==="rejected");if(a&&Jw(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+(Hn(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($(" snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(Cd(c)),d(""),d(i.length===0?"No budgets.":i.map(y=>`Budget: ${Td(y)}, ${ia(y)}`).join(`
577
- `)),d(`Last 30 days: ${Yw(m,"request")}, $${l.toFixed(2)}.`)})}async function Id(e,t){let n=await to(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($(" 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 Cd(e){return X([["MODEL","PROVIDER","UPSTREAM","PRICE PER M TOKENS","SERVED"],...e.map(t=>[t.name,t.provider,t.upstream,Rd(t.price)+(t.price_from==="catalogue"?" (catalogue)":""),t.served?"yes":`no: ${t.problem??(t.enabled?"not served":"disabled")}`])])}async function Md(e){let t=await Pe(e).models();b(t,()=>{d(t.length===0?`No models on ${e}'s gateway yet.`:Cd(t))})}async function Dd(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,Xw(n),n.models.join(", ")||"-"])]));for(let n of t.filter(r=>r.problem))ce(`${n.name}: ${n.problem}`)})}function Xw(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 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 Zw(e){let t=Xr.map(r=>r.id).join("|");if(!e)throw new h(`ai models add needs --source ${t}: where the model runs`);let n=Xr.find(r=>r.id===e);if(!n)throw new h(`--source is one of ${t}, not "${e}"`);return n}function Qw(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($(` ${o.label}: ${o.href}`))}d("")}function eb(e,t,n){if(n)return{name:n,existing:t.find(o=>o.name===n)??null};let r=t.filter(o=>$d(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 jd(e,t,n){let r=Zw(n.source),o=n.upstream?.trim();if(!o)throw Qw(r),new h(`Give the model's id as ${r.name} names it, with --model (for example ${r.model.example})`);let s=t?.trim()||vd(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}=eb(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 tn():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=Pd(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 N=xd({provider:u,upstream:o,input:n.priceIn,output:n.priceOut,caching:r.caching,enabled:!0,access:"everyone"});if("problem"in N)throw new h(N.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,N.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 ${Rd(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($(` snoutdata ai test --model ${s}`))})}async function Ud(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 tb(e){return(e??"").split(",").map(t=>t.trim()).filter(t=>t!=="")}function nb(e){return X([["ROUTE","TRIES, IN ORDER","SORT","SKIPS"],...e.map(t=>[t.name,t.tried.join(", ")||"-",t.sort,t.skipped.join(", ")||"-"])])}async function Fd(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($(" snoutdata ai routes set NAME --models first,second [--sort price]"));return}d(nb(t))})}async function Bd(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=tb(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($(` Call it as the model "${t}", or: snoutdata ai test --model ${t}`))})}async function Hd(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 rb(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 ${eo(e.expires_at).slice(0,10)}`:"active"}async function Gd(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",eo(n.created_at),n.last_used_at!==null?eo(n.last_used_at):n.last_used_on??"never",rb(n)])]))})}async function Kd(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 to(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 ${eo(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($(` OPENAI_BASE_URL=${o.ai.endpoint} OPENAI_API_KEY=<that key>`))})}async function qd(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 Wd(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($(" snoutdata ai budgets set project --amount 5")),d($(" snoutdata ai budgets set users --amount 0.50"));return}d(X([["ID","FOR","BUDGET"],...t.map(n=>[n.id,Td(n),ia(n)])]))})}function ob(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 sb(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 zd(e,t,n){let r=ob(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=sb(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"}, ${ia(i)}.`))}async function Jd(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 Ad=["day","model","provider","operation","key","user","group"];async function Vd(e,t){let n=t.by??"model";if(!Ad.includes(n))throw new h(`--by is one of ${Ad.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:Od(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+(Hn(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),Vw(a.cost)+(a.unpriced>0?` (+${a.unpriced} unpriced)`:"")])])),d(`${o.from} to ${o.to}: $${s.toFixed(2)}.`)})}async function Yd(e,t){let n=await to(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 N=await T.text();try{l=N?JSON.parse(N):{}}catch{l={error:{message:Nr(T.status,T.statusText,N)}}}}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(dt(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($(`${a}, ${m} ms, ${l.usage?.prompt_tokens??"?"} tokens in, ${l.usage?.completion_tokens??"?"} out, through ${n.ai.endpoint}`))}),0}pe();Te();de();import{readFile as ib}from"node:fs/promises";var lb="api.snoutdata.com";function Qd(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}.${lb}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function cb(e){return Qd(e,await E("cloud-project-auth",{ref:e,action:"status"}))}async function no(e,t){return Qd(e,await E("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function la(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??$("not set")],["redirects",e.redirects.length?e.redirects.join(", "):$("none")],["google",n],["google client id",t.clientId??$("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($(` snoutdata products enable auth --ref ${e.ref}`))}async function db(){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 eu(e,t,n,r){if(t===void 0||t==="show"){let o=await cb(e);b(o,()=>la(o));return}if(t==="google"){if(n[0]==="off"){let a=await no(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 db();if(!a)throw new h("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await no(e,o);b(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):la(s),d($(` 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 no(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($(` 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 no(e,o);b(s,()=>la(s));return}if(t==="templates"){let o=await E("cloud-project-auth",{ref:e,action:"templates"});b(o,()=>Zd(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Xd.includes(o))throw new h(`auth template needs a kind: ${Xd.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 ib(r.file,"utf8"),i=await E("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 E("cloud-project-auth",{ref:e,action:"template",template:s});b(a,()=>{Zd(a.templates),d($(" 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 Xd=["confirmation","recovery","magic_link","invite","email_change"];function Zd(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 Tb}from"node:crypto";import{existsSync as Ob,readdirSync as Nb,readFileSync as Ib,statSync as Cb}from"node:fs";import{isAbsolute as Mb,join as ma,relative as Db,resolve as Lb}from"node:path";function ub(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function ro(e,t,n={}){let r=ub(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 on(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as ca,createHmac as oo,pbkdf2Sync as pb,randomBytes as mb,timingSafeEqual as fb}from"node:crypto";import{readFileSync as gb}from"node:fs";import{homedir as hb}from"node:os";import{connect as yb,isIP as wb}from"node:net";import{connect as bb}from"node:tls";var Et=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"}},Sb=196608,kb=80877103;function tu(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function xt(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function _b(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 nu(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 da=class{constructor(t,n=mb(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(N=>[N.slice(0,1),N.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=pb(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),c=oo("sha256",a).update("Client Key").digest(),i=ca("sha256").update(c).digest(),u=`c=biws,r=${r}`,l=`${this.firstBare},${t},${u}`,m=oo("sha256",i).update(l).digest(),y=Buffer.alloc(c.length);for(let N=0;N<y.length;N++)y[N]=c[N]^m[N];let T=oo("sha256",a).update("Server Key").digest();return this.serverSignature=oo("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||!fb(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")}},ua=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 $b(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 vb(e,t){return new Promise((n,r)=>{let o=yb({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 Pb(e,t,n,r){let o=await vb(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(kb,4),o.write(s),await $b(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=bb({socket:o,host:e.host,...wb(e.host)===0?{servername:e.host}:{},rejectUnauthorized:c,...c&&n?{ca:gb(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 so=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=Un(t.ssl,r,n.home??hb()),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 Pb(t,a,s,N=>i.push(N)),T=new e(y,new ua(y));return await T.startup(t),T})(),m])}catch(y){throw u(),y}finally{clearTimeout(l)}}send(t,n){this.socket.write(tu(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[xt(s),xt(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(Sb,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",xt(t.password));else if(a===5){let c=ca("md5").update(t.password+t.user).digest("hex"),i=ca("md5").update(Buffer.concat([Buffer.from(c),s.body.subarray(4,8)])).digest("hex");this.send("p",xt(`md5${i}`))}else if(a===10){let c=_b(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 da(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([xt("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 Et(nu(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",xt(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 Et(nu(a.body));break;case"G":n===void 0?this.send("f",xt("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",xt("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(tu("X",Buffer.alloc(0)))}};function ru(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===`
741
+ `))}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} ${Sb(a)}`)}}}async function Zd(e,t={}){if(t.watch&&Ur())throw new y("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await Pa(e,{channel:t.channel});if(b(n.inspect,()=>kb(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 Pa(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Xd(a)}
742
+ `);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"}
743
+ `))}}}async function Qd(e,t={}){let n=t.since?yb(t.since):0,r=await Pa(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(`${Xd(o)}
744
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(v(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}fe();Te();ke();pe();var uo=[{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 eu(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:!uo.some(r=>r.baseUrl&&r.kind==="openai"&&r.baseUrl===n)}function tu(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 nu(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 vb(e){return e===void 0||e.trim()===""?void 0:Number(e)}function ru(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(vb);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 mo=class{send;constructor(t){this.send=t}providers(){return this.list("admin/providers")}putProvider(t,n){return this.call("PUT",`admin/providers/${dt(t)}`,n)}deleteProvider(t){return this.none("DELETE",`admin/providers/${dt(t)}`)}upstreamModels(t){return typeof t=="string"?this.list(`admin/providers/${dt(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/${po(t)}`,n)}deleteModel(t){return this.call("DELETE",`admin/models/${po(t)}`)}routes(){return this.list("admin/routes")}putRoute(t,n){return this.call("PUT",`admin/routes/${po(t)}`,n)}deleteRoute(t){return this.none("DELETE",`admin/routes/${po(t)}`)}health(){return this.list("admin/health")}credentials(){return this.list("admin/credentials")}putCredential(t,n){return this.call("PUT",`admin/credentials/${dt(t)}`,n)}deleteCredential(t){return this.none("DELETE",`admin/credentials/${dt(t)}`)}keys(){return this.list("admin/keys")}createKey(t){return this.call("POST","admin/keys",t)}revokeKey(t){return this.none("DELETE",`admin/keys/${dt(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/${dt(t)}`,n)}deleteBudget(t){return this.none("DELETE",`admin/budgets/${dt(t)}`)}limits(){return this.list("admin/limits")}createLimit(t){return this.call("POST","admin/limits",t)}deleteLimit(t){return this.none("DELETE",`admin/limits/${dt(t)}`)}groups(){return this.list("admin/groups")}usage(t={}){return this.call("GET",`admin/usage${ou(t)}`)}requests(t={}){return this.call("GET",`admin/requests${ou(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 dt(e){return encodeURIComponent(e)}function po(e){return e.split("/").map(dt).join("/")}function ou(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 Zn(e){if(e==null||e==="")return null;let t=Number(e);return Number.isFinite(t)?t:null}async function go(e,t){return T("cloud-project-ai",t===void 0?{ref:e}:{ref:e,enable:t})}function $b(e){if(e instanceof Oe){let t=e.body?.error;if(t&&typeof t=="object"&&typeof t.message=="string")return new Oe(e.status,t.message,typeof t.code=="string"?t.code:null,e.body)}return e}function Pb(e){return async(t,n,r)=>{try{return await T("cloud-project-ai",{ref:e,admin:{method:t,path:n,...r===void 0?{}:{body:r}}})}catch(o){throw $b(o)}}}function Ee(e){return new mo(Pb(e))}function xb(e){return e instanceof Oe&&(e.status===502||e.status===404&&e.serverCode!=="ai-off")}function xa(e){return Number.isInteger(e)?`$${e}`:Math.abs(e*100-Math.round(e*100))<1e-9?`$${e.toFixed(2)}`:`$${e}`}function au(e){return e?`${xa(e.input)} in, ${xa(e.output)} out`:"no price"}function iu(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 Ea(e){let t=e.period==="rolling"?`per ${e.days??"?"} days`:e.period==="since"?`since ${e.starts_on??"?"}`:`a ${e.period}`,n=Zn(e.amount);return`${n===null?`$${e.amount}`:xa(n)} ${t}, ${e.hard?"hard":"soft"}`}function Eb(e){let t=Zn(e);return t===null?"-":`$${t.toFixed(t!==0&&t<.01?4:2)}`}function fo(e){return e===null?"never":new Date(e*1e3).toISOString().slice(0,16).replace("T"," ")}function lu(e,t=Date.now()){return new Date(t-(e-1)*864e5).toISOString().slice(0,10)}function Ab(e,t){return`${e} ${t}${e===1?"":"s"}`}async function cu(e){let t=await go(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=Ee(e),[r,o,s]=await Promise.allSettled([n.models(),n.budgets(),n.usage({by:"day",from:lu(30)})]),a=[r,o,s].find(h=>h.status==="rejected");if(a&&xb(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((h,A)=>h+(Zn(A.cost)??0),0),m=u.reduce((h,A)=>h+A.requests,0);b({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!0,models:c,budgets:i,last30Days:{requests:m,cost:l}},()=>{d(`${L(`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(uu(c)),d(""),d(i.length===0?"No budgets.":i.map(h=>`Budget: ${iu(h)}, ${Ea(h)}`).join(`
745
+ `)),d(`Last 30 days: ${Ab(m,"request")}, $${l.toFixed(2)}.`)})}async function du(e,t){let n=await go(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 uu(e){return ee([["MODEL","PROVIDER","UPSTREAM","PRICE PER M TOKENS","SERVED"],...e.map(t=>[t.name,t.provider,t.upstream,au(t.price)+(t.price_from==="catalogue"?" (catalogue)":""),t.served?"yes":`no: ${t.problem??(t.enabled?"not served":"disabled")}`])])}async function pu(e){let t=await Ee(e).models();b(t,()=>{d(t.length===0?`No models on ${e}'s gateway yet.`:uu(t))})}async function mu(e){let t=await Ee(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(ee([["PROVIDER","KIND","WHERE","KEY","MODELS"],...t.map(n=>[n.name,n.kind,n.kind==="bedrock"?n.region??"":n.base_url,Rb(n),n.models.join(", ")||"-"])]));for(let n of t.filter(r=>r.problem))ue(`${n.name}: ${n.problem}`)})}function Rb(e){return e.key==="environment"?"from the environment":e.key==="none"||e.credential===null?"none":e.credential_stored?"stored":"missing"}async function fu(e,t){if(!t)throw new y("ai providers remove needs the provider's name (see `snoutdata ai providers`)");await Ee(e).deleteProvider(t),b({ref:e,removed:t},()=>d(`Removed the provider ${t}.`))}function Tb(e){let t=uo.map(r=>r.id).join("|");if(!e)throw new y(`ai models add needs --source ${t}: where the model runs`);let n=uo.find(r=>r.id===e);if(!n)throw new y(`--source is one of ${t}, not "${e}"`);return n}function Ob(e){d(L(`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(L(n));for(let s of r.steps)d(` - ${s}`);let o=r.console??r.link;o&&d(v(` ${o.label}: ${o.href}`))}d("")}function Nb(e,t,n){if(n)return{name:n,existing:t.find(o=>o.name===n)??null};let r=t.filter(o=>eu(e,o));if(r.length===1)return{name:r[0].name,existing:r[0]};if(r.length>1)throw new y(`${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 gu(e,t,n){let r=Tb(n.source),o=n.upstream?.trim();if(!o)throw Ob(r),new y(`Give the model's id as ${r.name} names it, with --model (for example ${r.model.example})`);let s=t?.trim()||tu(o),a=Ee(e),[c,i]=await Promise.all([a.providers(),a.models()]);if(i.some(M=>M.name===s))throw new y(`${e} already has a model called ${s}. Name this one differently, or remove that one first.`);let{name:u,existing:l}=Nb(r,c,n.provider);if(l&&l.kind!==r.kind)throw new y(`The provider ${u} speaks ${l.kind}, not ${r.name}: choose another --provider name`);if(l?.origin==="file"&&n.stdin)throw new y(`The provider ${u} comes from the gateway's providers file, so its key is set there, not here`);let m=n.stdin?await un():void 0,h=r.auth==="bedrock"&&n.accessKeyId!==void 0,A=null;if(!l){if(r.auth!=="optional"&&!m)throw new y(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 M=nu(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 M)throw new y(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":M.problem);A=M}let E=ru({provider:u,upstream:o,input:n.priceIn,output:n.priceOut,caching:r.caching,enabled:!0,access:"everyone"});if("problem"in E)throw new y(E.problem.replace("input and the output rate","--price-in and --price-out"));let se=!1,F=!1,H;try{if(A&&(await a.putProvider(u,A.provider),se=!0),H=await a.putModel(s,E.model),F=!0,A?.credential)await a.putCredential(u,A.credential);else if(l&&m){if(!l.credential)throw new y(`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(M){throw F&&await a.deleteModel(s).catch(()=>{}),se&&await a.deleteProvider(u).catch(()=>{}),M}H=(await a.models()).find(M=>M.name===s)??H,b(H,()=>{d(`Added ${L(s)} (${o} on ${u}${se?", a new provider":""}), at ${au(H.price)}${H.price_from==="catalogue"?" from the shipped catalogue":""}.`),H.price||ue("Nothing prices this model, so signed-in users are refused it. Give it a price with --price-in and --price-out."),!H.served&&H.problem&&ue(`It is not served yet: ${H.problem}`),d(v(` snoutdata ai test --model ${s}`))})}async function hu(e,t){if(!t)throw new y("ai models remove needs the model's name (see `snoutdata ai models`)");let n=await Ee(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 Ib(e){return(e??"").split(",").map(t=>t.trim()).filter(t=>t!=="")}function Cb(e){return ee([["ROUTE","TRIES, IN ORDER","SORT","SKIPS"],...e.map(t=>[t.name,t.tried.join(", ")||"-",t.sort,t.skipped.join(", ")||"-"])])}async function yu(e){let t=await Ee(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(Cb(t))})}async function wu(e,t,n){if(!t)throw new y("ai routes set needs the route's name: snoutdata ai routes set NAME --models first,second");let r=Ib(n.models);if(r.length===0)throw new y("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 y(`--sort is order (as written), price (the cheapest first), latency or throughput (the fastest first, as measured), not ${o}`);let s=Ee(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(`${L(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&&ue(`skipped, not served: ${a.skipped.join(", ")}`),d(v(` Call it as the model "${t}", or: snoutdata ai test --model ${t}`))})}async function bu(e,t){if(!t)throw new y("ai routes remove needs the route's name (see `snoutdata ai routes`)");await Ee(e).deleteRoute(t),b({name:t,removed:!0},()=>d(`Removed the route ${t}. Its models are untouched.`))}function Mb(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 ${fo(e.expires_at).slice(0,10)}`:"active"}async function Su(e){let t=await Ee(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(ee([["NAME","ID","KEY","MODELS","CREATED","LAST USED","STATE"],...t.map(n=>[n.name,n.id,n.hint,n.models?.join(", ")??"all",fo(n.created_at),n.last_used_at!==null?fo(n.last_used_at):n.last_used_on??"never",Mb(n)])]))})}async function ku(e,t){if(!t.name)throw new y("ai keys create needs --name: what the key is for, shown in ai keys");if(t.expiresDays!==void 0&&t.expiresDays<=0)throw new y("--expires is a number of days, 1 or more");let n=t.models?.split(",").map(s=>s.trim()).filter(Boolean),r=await Ee(e).createKey({name:t.name,...n&&n.length>0?{models:n}:{},...t.expiresDays!==void 0?{expires_in:t.expiresDays*86400}:{}}),o=await go(e);b({...r,endpoint:o.ai.endpoint},()=>{process.stdout.write(`${r.key}
746
+ `),d(""),d(`${L(r.name)}: ${r.models?`for ${r.models.join(", ")}`:"for every model"}, ${r.expires_at?`until ${fo(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 _u(e,t){if(!t)throw new y("ai keys revoke needs the key's id (see `snoutdata ai keys`)");await Ee(e).revokeKey(t),b({ref:e,revoked:t},()=>d(`Revoked ${t}. A call with it is refused from now on.`))}async function vu(e){let t=await Ee(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(ee([["ID","FOR","BUDGET"],...t.map(n=>[n.id,iu(n),Ea(n)])]))})}function Lb(e){if(e==="project"||e==="users")return e;throw new y("ai budgets set takes project (the whole project) or users (each signed-in user)")}function Db(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 $u(e,t,n){let r=Lb(t),o=Number(n.amount);if(n.amount===void 0||!Number.isFinite(o)||o<0)throw new y("ai budgets set needs --amount, in dollars, zero or more");let s=n.period??"day";if(s!=="day"&&s!=="month")throw new y("--period is day or month");let a=Ee(e),c=Db(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"}, ${Ea(i)}.`))}async function Pu(e,t){if(!t)throw new y("ai budgets remove needs the budget's id (see `snoutdata ai budgets`)");await Ee(e).deleteBudget(t),b({ref:e,removed:t},()=>d(`Removed the budget ${t}.`))}var su=["day","model","provider","operation","key","user","group"];async function xu(e,t){let n=t.by??"model";if(!su.includes(n))throw new y(`--by is one of ${su.join("|")}`);let r=t.days??30;if(r<1||r>366)throw new y("--days is 1 to 366");let o=await Ee(e).usage({by:n,from:lu(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+(Zn(c.cost)??0),0);d(ee([[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),Eb(a.cost)+(a.unpriced>0?` (+${a.unpriced} unpriced)`:"")])])),d(`${o.from} to ${o.to}: $${s.toFixed(2)}.`)})}async function Eu(e,t){let n=await go(e);if(!n.ai.enabled)throw new C("not-found",`AI is off for ${e}. Switch it on first: snoutdata ai enable.`);let r=Ee(e),s=(await r.models()).filter(A=>A.served&&A.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 A=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=A.status;let E=await A.text();try{l=E?JSON.parse(E):{}}catch{l={error:{message:qr(A.status,A.statusText,E)}}}}finally{await r.revokeKey(c.id).catch(()=>{})}let m=Date.now()-i;if(u<200||u>=300){let A=l.error?.message??`the gateway answered ${u}`;throw new C(yt(u,l.error?.code??null),`${a} through ${e}'s gateway: ${A}`,{model:a,status:u,gatewayCode:l.error?.code??null})}let h=l.choices?.[0]?.message?.content??"";return b({ref:e,model:a,reply:h,usage:l.usage??null,ms:m},()=>{process.stdout.write(`${h}
747
+ `),d(v(`${a}, ${m} ms, ${l.usage?.prompt_tokens??"?"} tokens in, ${l.usage?.completion_tokens??"?"} out, through ${n.ai.endpoint}`))}),0}fe();Te();pe();import{readFile as Ub}from"node:fs/promises";var Fb="api.snoutdata.com";function Tu(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}.${Fb}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function Bb(e){return Tu(e,await T("cloud-project-auth",{ref:e,action:"status"}))}async function ho(e,t){return Tu(e,await T("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Aa(e){let t=e.google,n="off";t.ready?n=L("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(`${ee([["auth",e.enabled?L("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?L("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
748
+ `),e.enabled||d(v(` snoutdata products enable auth --ref ${e.ref}`))}async function Hb(){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 Ou(e,t,n,r){if(t===void 0||t==="show"){let o=await Bb(e);b(o,()=>Aa(o));return}if(t==="google"){if(n[0]==="off"){let a=await ho(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 y('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await Hb();if(!a)throw new y("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await ho(e,o);b(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Aa(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 y('auth anonymous takes "on" or "off"');let s=await ho(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 y("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await ho(e,o);b(s,()=>Aa(s));return}if(t==="templates"){let o=await T("cloud-project-auth",{ref:e,action:"templates"});b(o,()=>Ru(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Au.includes(o))throw new y(`auth template needs a kind: ${Au.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new y(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let c=await Ub(r.file,"utf8"),i=await T("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 T("cloud-project-auth",{ref:e,action:"template",template:s});b(a,()=>{Ru(a.templates),d(v(" Your auth service restarts with it within about a minute."))});return}throw new y(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Au=["confirmation","recovery","magic_link","invite","email_change"];function Ru(e){process.stdout.write(`${ee([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?L("yours"):"ours",t.subject])])}
749
+ `)}fe();pe();ke();import{createHash as lS}from"node:crypto";import{existsSync as cS,readdirSync as dS,readFileSync as uS,statSync as pS}from"node:fs";import{isAbsolute as mS,join as Ia,relative as fS,resolve as gS}from"node:path";function Gb(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function yo(e,t,n={}){let r=Gb(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 fn(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Ra,createHmac as wo,pbkdf2Sync as Kb,randomBytes as qb,timingSafeEqual as zb}from"node:crypto";import{readFileSync as Wb}from"node:fs";import{homedir as Jb}from"node:os";import{connect as Vb,isIP as Yb}from"node:net";import{connect as Xb}from"node:tls";var Ct=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)}},we=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},Zb=196608,Qb=80877103;function Nu(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function It(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function eS(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 Iu(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 Ta=class{constructor(t,n=qb(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(E=>[E.slice(0,1),E.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 we("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new we("the server answered the password exchange with no salt or iteration count");let a=Kb(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),c=wo("sha256",a).update("Client Key").digest(),i=Ra("sha256").update(c).digest(),u=`c=biws,r=${r}`,l=`${this.firstBare},${t},${u}`,m=wo("sha256",i).update(l).digest(),h=Buffer.alloc(c.length);for(let E=0;E<h.length;E++)h[E]=c[E]^m[E];let A=wo("sha256",a).update("Server Key").digest();return this.serverSignature=wo("sha256",A).update(l).digest(),`${u},p=${h.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new we(`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||!zb(o,this.serverSignature))throw new we("the server could not prove it knows the password, so this is not the database it claims to be")}},Oa=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 we(n.message))),t.on("close",()=>this.fail(new we("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 we("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 tS(e){return new Promise((t,n)=>{let r=c=>{if(a(),c.length!==1){n(new we("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 we(c.message))},s=()=>{a(),n(new we("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 nS(e,t){return new Promise((n,r)=>{let o=Vb({host:e,port:t}),s=a=>r(new we(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function rS(e,t,n,r){let o=await nS(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(Qb,4),o.write(s),await tS(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new we(`${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=Xb({socket:o,host:e.host,...Yb(e.host)===0?{servername:e.host}:{},rejectUnauthorized:c,...c&&n?{ca:Wb(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(l),l.once("secureConnect",()=>{l.off("error",m),i(l)});let m=h=>u(new we(`TLS to ${e.host} failed: ${h.message}`));l.once("error",m)})}var bo=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=Vn(t.ssl,r,n.home??Jb()),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,A)=>{l=setTimeout(()=>{u(),A(new we(`${t.host}:${t.port} did not answer within ${Math.round(c/1e3)} seconds`))},c)});try{return await Promise.race([(async()=>{let h=await rS(t,a,s,E=>i.push(E)),A=new e(h,new Oa(h));return await A.startup(t),A})(),m])}catch(h){throw u(),h}finally{clearTimeout(l)}}send(t,n){this.socket.write(Nu(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[It(s),It(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(Zb,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",It(t.password));else if(a===5){let c=Ra("md5").update(t.password+t.user).digest("hex"),i=Ra("md5").update(Buffer.concat([Buffer.from(c),s.body.subarray(4,8)])).digest("hex");this.send("p",It(`md5${i}`))}else if(a===10){let c=eS(s.body,4);if(!c.includes("SCRAM-SHA-256"))throw new we(`the server asked for ${c.join(", ")||"a password exchange"}, which this client does not speak`);o=new Ta(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([It("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 we(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new Ct(Iu(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",It(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 Ct(Iu(a.body));break;case"G":n===void 0?this.send("f",It("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",It("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(Nu("X",Buffer.alloc(0)))}};function Cu(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===`
582
750
  `).length,a=t.split(`
583
751
  `)[s]??"",c=o.length-(o.lastIndexOf(`
584
752
  `)+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(`
585
- `)}var xb=/[A-Za-z0-9_$\u0080-￿]/,Eb=/[A-Za-z_\u0080-￿]/,Ab=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Rb(e){let t=pa(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function ou(e){let t=pa(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 pa(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function su(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},N=V=>{let $e=Math.min(r,o+V);for(let ne=o;ne<$e;ne++)e.charCodeAt(ne)===10&&s++;o=$e},oe=V=>N(V<0?r-o:V-o),Z=null,le=()=>{let V=a>=0?e.slice(a,o).trim():"";if(pa(V).replace(/;/g,"").trim()!=="")if(!Rb(V))t.push({sql:V,line:c});else{let $e=e.indexOf(`
586
- `,o),ne=[];if($e>=0){let fe=$e+1,kn=s+1;for(;fe<r;){let Gt=e.indexOf(`
587
- `,fe),nr=e.slice(fe,Gt<0?r:Gt).replace(/\r$/,"");if(fe=Gt<0?r:Gt+1,kn++,nr==="\\.")break;ne.push(`${nr}
588
- `)}Z={from:$e+1,to:fe,line:kn}}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],$e=e[o+1];if(V===`
589
- `||V===" "||V===" "||V==="\r"||V==="\f"||V==="\v"){N(1);continue}if(V==="-"&&$e==="-"){oe(e.indexOf(`
590
- `,o));continue}if(V==="/"&&$e==="*"){y();let ne=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")ne++,N(2);else if(e[o]==="*"&&e[o+1]==="/"){if(ne--,N(2),ne===0)break}else N(1);continue}if(V==="'"){y(),G(!1);continue}if(V==='"'){for(y(),N(1);;){let ne=e.indexOf('"',o);if(ne<0){oe(-1);break}if(oe(ne+1),e[o]==='"'){N(1);continue}break}continue}if(V==="$"){y();let ne=Ab.exec(e.slice(o,o+64))?.[0];if(ne){N(ne.length);let fe=e.indexOf(ne,o);oe(fe<0?-1:fe+ne.length)}else N(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++,N(1);continue}if(V===")"){y(),i>0&&i--,N(1);continue}if(V===";"){y(),N(1),i===0&&u===0&&le();continue}if(Eb.test(V)){y();let ne=o+1;for(;ne<r&&xb.test(e[ne]);)ne++;let fe=e.slice(o,ne);if(N(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++;N(ne-o);continue}y(),N(1)}return le(),{statements:t,metaCommands:n};function G(V){for(N(1);o<r;){let $e=e[o];if(V&&$e==="\\"){N(2);continue}if($e==="'"){if(N(1),e[o]==="'"){N(1);continue}return}N(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 iu(e){let t=Db(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var ao="_snoutdata_migrations",jb=`create table if not exists ${ao} (
753
+ `)}var oS=/[A-Za-z0-9_$\u0080-￿]/,sS=/[A-Za-z_\u0080-￿]/,aS=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function iS(e){let t=Na(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function Mu(e){let t=Na(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 Na(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function Lu(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)},A=()=>{a=-1,i=0,u=0,l=[],m=0},E=G=>{let oe=Math.min(r,o+G);for(let Z=o;Z<oe;Z++)e.charCodeAt(Z)===10&&s++;o=oe},se=G=>E(G<0?r-o:G-o),F=null,H=()=>{let G=a>=0?e.slice(a,o).trim():"";if(Na(G).replace(/;/g,"").trim()!=="")if(!iS(G))t.push({sql:G,line:c});else{let oe=e.indexOf(`
754
+ `,o),Z=[];if(oe>=0){let me=oe+1,rt=s+1;for(;me<r;){let ht=e.indexOf(`
755
+ `,me),Je=e.slice(me,ht<0?r:ht).replace(/\r$/,"");if(me=ht<0?r:ht+1,rt++,Je==="\\.")break;Z.push(`${Je}
756
+ `)}F={from:oe+1,to:me,line:rt}}t.push({sql:G,line:c,copyData:Z.join("")})}A()};for(;o<r;){if(F&&o>=F.from){o=F.to,s=F.line,F=null;continue}let G=e[o],oe=e[o+1];if(G===`
757
+ `||G===" "||G===" "||G==="\r"||G==="\f"||G==="\v"){E(1);continue}if(G==="-"&&oe==="-"){se(e.indexOf(`
758
+ `,o));continue}if(G==="/"&&oe==="*"){h();let Z=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")Z++,E(2);else if(e[o]==="*"&&e[o+1]==="/"){if(Z--,E(2),Z===0)break}else E(1);continue}if(G==="'"){h(),M(!1);continue}if(G==='"'){for(h(),E(1);;){let Z=e.indexOf('"',o);if(Z<0){se(-1);break}if(se(Z+1),e[o]==='"'){E(1);continue}break}continue}if(G==="$"){h();let Z=aS.exec(e.slice(o,o+64))?.[0];if(Z){E(Z.length);let me=e.indexOf(Z,o);se(me<0?-1:me+Z.length)}else E(1);continue}if(G==="\\"){let Z=e.indexOf(`
759
+ `,o);n.push({line:s,text:e.slice(o,Z<0?r:Z).replace(/\r$/,"").trimEnd()}),se(Z);continue}if(G==="("){h(),i++,E(1);continue}if(G===")"){h(),i>0&&i--,E(1);continue}if(G===";"){h(),E(1),i===0&&u===0&&H();continue}if(sS.test(G)){h();let Z=o+1;for(;Z<r&&oS.test(e[Z]);)Z++;let me=e.slice(o,Z);if(E(Z-o),e[o]==="'"){M(me==="e"||me==="E");continue}de(me.toLowerCase());continue}if(G>="0"&&G<="9"){h();let Z=o+1;for(;Z<r&&/[0-9A-Za-z_.]/.test(e[Z]);)Z++;E(Z-o);continue}h(),E(1)}return H(),{statements:t,metaCommands:n};function M(G){for(E(1);o<r;){let oe=e[o];if(G&&oe==="\\"){E(2);continue}if(oe==="'"){if(E(1),e[o]==="'"){E(1);continue}return}E(1)}}function de(G){m<4&&["create","function","procedure","or","replace"].includes(G)&&(l[m]=G[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)&&(G==="begin"?u++:G==="case"?u>=1&&u++:G==="end"&&u>0&&u--)}}function ju(e){let t=fS(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var So="_snoutdata_migrations",hS=`create table if not exists ${So} (
592
760
  name text primary key,
593
761
  checksum text not null,
594
762
  applied_at timestamptz not null default now()
595
- );`;function Ub(e){if(!Ob(e)||!Cb(e).isDirectory())throw new Error(`no migrations folder at ${iu(e)}. Put your .sql files there, or pass --dir.`);return Nb(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=ma(e,t),r=Ib(n,"utf8");return{name:t,path:n,sql:r,checksum:Tb("sha256").update(r,"utf8").digest("hex")}})}var Fb={connection:e=>Fe(e),open:e=>so.open(e)};function lu(e,t){throw e instanceof ye?new C("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Et?new C("failed",`${t}'s database refused the connection: ${e.message}`):e}async function cu(e,t,n){try{return await e.open(t)}catch(r){return lu(r,n)}}async function Bb(e,t,n){let r=await cu(e,t,n);try{await r.query(jb);let{rows:o}=await r.query(`select name, checksum from ${ao};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Et)throw new C("failed",`Could not read ${ao} on ${n}: ${o.message}`);return lu(o,n)}finally{r.close()}}async function Hb(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Et)return`${r.line>0?`${n}:${r.line}: `:""}${ru(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 au(e){return`'${e.replace(/'/g,"''")}'`}async function du(e,t={}){let n=await fa(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 fa(e,t={},n=()=>{},r=Fb){let o=Lb(t.dir===void 0?ma(process.cwd(),"migrations"):Mb(t.dir)?t.dir:ma(process.cwd(),t.dir)),s=[];function a(G){s.push(G),n(G)}let c=Ub(o);if(c.length===0)return a(`No .sql files in ${iu(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 Bb(r,u,e),m=ro(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)),N=new Map(T.map(G=>[G.name,su(G.sql)])),oe=T.flatMap(G=>N.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=>on(G.sql)).flatMap(G=>N.get(G.name).statements.flatMap(ue=>{let V=ou(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)on(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 ${ao} (name, checksum) values (${au(G.name)}, ${au(G.checksum)});`,V=on(G.sql);V||a(`${G.name} runs outside a transaction, so an interruption can leave it half applied.`);let $e=[...V?[{sql:"begin;",line:0}]:[],...N.get(G.name).statements,{sql:ue,line:0},...V?[{sql:"commit;",line:0}]:[]],ne=await cu(r,u,e),fe;try{fe=await Hb(ne,$e,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();Te();de();import{readFile as uu}from"node:fs/promises";function ga(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 Gb(e){let t=await E("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function pu(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 uu(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function mu(e){if(!e)throw new h("push credentials set fcm needs --file service-account.json");return{service_account:await uu(e,"utf8")}}async function Kb(e,t,n){let r=await E("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function qb(e,t){return await E("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function fu(e){let t=await Gb(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($(` 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 gu(e,t,n){let r=await Kb(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 hu(e,t){let n=await qb(e,t);b(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}pe();be();de();import{spawn as sS}from"node:child_process";import{writeFileSync as aS}from"node:fs";import{isAbsolute as iS,resolve as lS}from"node:path";var zb=" ";function Q(e){return zb.repeat(e)}function Oe(e){return JSON.stringify(e)}function ot(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:Oe(e)}function Dt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Jb(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 Vb={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"},Yb={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Xb(e,t){let n=Yb[e]?.[t];return n||(Vb[t]??"unknown")}function io(e,t){let{base:n,array:r}=Jb(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Xb(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Zb(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function ha(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${Q(n)}${ot(t.name)}?: never`;let s=io(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${Q(n)}${ot(t.name)}${r?"?":""}: ${a}`}function Qb(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 eS(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: ${Qb(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 yu(e,t,n){let r=Dt(t.columns),o=[];o.push(`${Q(n)}${ot(t.name)}: {`),o.push(`${Q(n+1)}Row: {`);for(let s of r)o.push(ha(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(ha(e,s,n+2,Zb(s),!0));o.push(`${Q(n+1)}}`),o.push(`${Q(n+1)}Update: {`);for(let s of r)o.push(ha(e,s,n+2,!0,!0));o.push(`${Q(n+1)}}`)}return o.push(...eS(t,n+1)),o.push(`${Q(n)}}`),o}function Gn(e,t){return[`${Q(t)}${e}: {`,`${Q(t+1)}[_ in never]: never`,`${Q(t)}}`]}function tS(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}[]`:`${io(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??io(e,n)}function nS(e,t,n){let r=[],o=Dt((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)}${ot(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)}${ot(s.name)}: ${io(e,s.dataType)}`);r.push(`${Q(n+1)}}`)}return r.push(`${Q(n+1)}Returns: ${tS(e,t)}`),r.push(`${Q(n)}}`),r}function sn(e,t){return e.schema&&e.schema.length>0?e.schema:t}function wu(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=sn(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(sn(m,n));for(let m of e.routines??[])i.add(sn(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=Dt(e.tables.filter(Z=>sn(Z,n)===m&&Z.kind!=="view")),T=Dt(e.tables.filter(Z=>sn(Z,n)===m&&Z.kind==="view")),N=Dt((e.routines??[]).filter(Z=>sn(Z,n)===m)),oe=Dt(o.filter(Z=>(Z.schema&&Z.schema.length>0?Z.schema:n)===m));if(l.push(`${Q(1)}${ot(m)}: {`),y.length===0)l.push(...Gn("Tables",2));else{l.push(`${Q(2)}Tables: {`);for(let Z of y)l.push(...yu(c,Z,3));l.push(`${Q(2)}}`)}if(T.length===0)l.push(...Gn("Views",2));else{l.push(`${Q(2)}Views: {`);for(let Z of T)l.push(...yu(c,Z,3));l.push(`${Q(2)}}`)}if(N.length===0)l.push(...Gn("Functions",2));else{l.push(`${Q(2)}Functions: {`);for(let Z of N)l.push(...nS(c,Z,3));l.push(`${Q(2)}}`)}if(oe.length===0)l.push(...Gn("Enums",2));else{l.push(`${Q(2)}Enums: {`);for(let Z of oe)l.push(`${Q(3)}${ot(Z.name)}: ${Z.values.map(Oe).join(" | ")}`);l.push(`${Q(2)}}`)}l.push(...Gn("CompositeTypes",2)),l.push(`${Q(1)}}`)}return l.push("}"),t.helpers!==!1&&(l.push(""),l.push(oS(n))),t.constants!==!1&&(l.push(""),l.push(rS(u,o,n))),`${l.join(`
763
+ );`;function yS(e){if(!cS(e)||!pS(e).isDirectory())throw new Error(`no migrations folder at ${ju(e)}. Put your .sql files there, or pass --dir.`);return dS(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Ia(e,t),r=uS(n,"utf8");return{name:t,path:n,sql:r,checksum:lS("sha256").update(r,"utf8").digest("hex")}})}var wS={connection:e=>qe(e),open:e=>bo.open(e)};function Uu(e,t){throw e instanceof we?new C("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Ct?new C("failed",`${t}'s database refused the connection: ${e.message}`):e}async function Fu(e,t,n){try{return await e.open(t)}catch(r){return Uu(r,n)}}async function bS(e,t,n){let r=await Fu(e,t,n);try{await r.query(hS);let{rows:o}=await r.query(`select name, checksum from ${So};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Ct)throw new C("failed",`Could not read ${So} on ${n}: ${o.message}`);return Uu(o,n)}finally{r.close()}}async function SS(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Ct)return`${r.line>0?`${n}:${r.line}: `:""}${Cu(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof we)return`the connection was lost: ${o.message}`;throw o}return null}function Du(e){return`'${e.replace(/'/g,"''")}'`}async function Bu(e,t={}){let n=await Ca(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
764
+ `);n.problems.every(o=>o.code==="psql-command"||o.code==="ends-transaction")&&re("failed",`Refused, and nothing ran:
765
+ ${r}`,{ref:e,problems:n.problems}),re("conflict",`Refused. This folder and ${e} do not agree:
766
+ ${r}`,{ref:e,problems:n.problems})}re("failed",`${n.failed} failed, and nothing after it was tried.
767
+ ${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 Ca(e,t={},n=()=>{},r=wS){let o=gS(t.dir===void 0?Ia(process.cwd(),"migrations"):mS(t.dir)?t.dir:Ia(process.cwd(),t.dir)),s=[];function a(M){s.push(M),n(M)}let c=yS(o);if(c.length===0)return a(`No .sql files in ${ju(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 bS(r,u,e),m=yo(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(M=>[M.name,M])),A=m.pending.map(M=>h.get(M.name)),E=new Map(A.map(M=>[M.name,Lu(M.sql)])),se=A.flatMap(M=>E.get(M.name).metaCommands.map(de=>({code:"psql-command",name:M.name,reason:`${M.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.`}))),F=A.filter(M=>fn(M.sql)).flatMap(M=>E.get(M.name).statements.flatMap(de=>{let G=Mu(de.sql);return G?[{code:"ends-transaction",name:M.name,reason:`${M.name} line ${de.line} ends the transaction itself (${G}). db push already runs each file in one transaction and records it there, so a ${G} 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(se.length>0||F.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:[...se,...F],notes:s};if(A.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 M of A)fn(M.sql)||a(`${M.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:A.map(M=>M.name),notes:s}}let H=[];for(let M of A){let de=`insert into ${So} (name, checksum) values (${Du(M.name)}, ${Du(M.checksum)});`,G=fn(M.sql);G||a(`${M.name} runs outside a transaction, so an interruption can leave it half applied.`);let oe=[...G?[{sql:"begin;",line:0}]:[],...E.get(M.name).statements,{sql:de,line:0},...G?[{sql:"commit;",line:0}]:[]],Z=await Fu(r,u,e),me;try{me=await SS(Z,oe,M.name)}finally{Z.close()}if(me!==null)return G&&(a("It ran inside a transaction, so nothing from it is in the database."),/cannot run inside a transaction block/i.test(me)&&a("Put -- snoutdata:no-transaction at the top of the file to run it outside one.")),{ref:e,directory:o,ok:!1,applied:H,failed:M.name,error:me,notes:s};H.push(M.name),a(` applied ${M.name}`)}return{ref:e,directory:o,ok:!0,applied:H,notes:s}}fe();Te();pe();import{readFile as Hu}from"node:fs/promises";function Ma(e){if(e==="apns"||e==="fcm")return e;throw new y("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function kS(e){let t=await T("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function Gu(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new y("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new y("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await Hu(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function Ku(e){if(!e)throw new y("push credentials set fcm needs --file service-account.json");return{service_account:await Hu(e,"utf8")}}async function _S(e,t,n){let r=await T("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function vS(e,t){return await T("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function qu(e){let t=await kS(e);b(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${ee([["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}`:""]])}
768
+ `),(!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 zu(e,t,n){let r=await _S(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 Wu(e,t){let n=await vS(e,t);b(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}fe();ke();pe();import{spawn as DS}from"node:child_process";import{writeFileSync as jS}from"node:fs";import{isAbsolute as US,resolve as FS}from"node:path";var PS=" ";function te(e){return PS.repeat(e)}function Ie(e){return JSON.stringify(e)}function ut(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:Ie(e)}function Kt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function xS(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 ES={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"},AS={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function RS(e,t){let n=AS[e]?.[t];return n||(ES[t]??"unknown")}function ko(e,t){let{base:n,array:r}=xS(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=RS(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function TS(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function La(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${te(n)}${ut(t.name)}?: never`;let s=ko(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${te(n)}${ut(t.name)}${r?"?":""}: ${a}`}function OS(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 NS(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(`${te(t)}Relationships: []`),n;n.push(`${te(t)}Relationships: [`);for(let o of r)n.push(`${te(t+1)}{`),n.push(`${te(t+2)}foreignKeyName: ${Ie(o.constraintName)}`),n.push(`${te(t+2)}columns: [${o.columns.map(Ie).join(", ")}]`),n.push(`${te(t+2)}isOneToOne: ${OS(e,o)?"true":"false"}`),n.push(`${te(t+2)}referencedRelation: ${Ie(o.referencedTable)}`),n.push(`${te(t+2)}referencedColumns: [${o.referencedColumns.map(Ie).join(", ")}]`),n.push(`${te(t+1)}},`);return n.push(`${te(t)}]`),n}function Ju(e,t,n){let r=Kt(t.columns),o=[];o.push(`${te(n)}${ut(t.name)}: {`),o.push(`${te(n+1)}Row: {`);for(let s of r)o.push(La(e,s,n+2,!1));if(o.push(`${te(n+1)}}`),t.kind!=="view"){o.push(`${te(n+1)}Insert: {`);for(let s of r)o.push(La(e,s,n+2,TS(s),!0));o.push(`${te(n+1)}}`),o.push(`${te(n+1)}Update: {`);for(let s of r)o.push(La(e,s,n+2,!0,!0));o.push(`${te(n+1)}}`)}return o.push(...NS(t,n+1)),o.push(`${te(n)}}`),o}function Qn(e,t){return[`${te(t)}${e}: {`,`${te(t+1)}[_ in never]: never`,`${te(t)}}`]}function IS(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}[]`:`${ko(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??ko(e,n)}function CS(e,t,n){let r=[],o=Kt((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${te(n)}${ut(t.name)}: {`),o.length===0)r.push(`${te(n+1)}Args: never`);else{r.push(`${te(n+1)}Args: {`);for(let s of o)r.push(`${te(n+2)}${ut(s.name)}: ${ko(e,s.dataType)}`);r.push(`${te(n+1)}}`)}return r.push(`${te(n+1)}Returns: ${IS(e,t)}`),r.push(`${te(n)}}`),r}function gn(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Vu(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let h=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${Ie(h)}][${Ie("Enums")}][${Ie(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=gn(m,n),A=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${Ie(h)}][${Ie(A)}][${Ie(m.name)}][${Ie("Row")}]`)}let c={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(gn(m,n));for(let m of e.routines??[])i.add(gn(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=Kt(e.tables.filter(F=>gn(F,n)===m&&F.kind!=="view")),A=Kt(e.tables.filter(F=>gn(F,n)===m&&F.kind==="view")),E=Kt((e.routines??[]).filter(F=>gn(F,n)===m)),se=Kt(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(l.push(`${te(1)}${ut(m)}: {`),h.length===0)l.push(...Qn("Tables",2));else{l.push(`${te(2)}Tables: {`);for(let F of h)l.push(...Ju(c,F,3));l.push(`${te(2)}}`)}if(A.length===0)l.push(...Qn("Views",2));else{l.push(`${te(2)}Views: {`);for(let F of A)l.push(...Ju(c,F,3));l.push(`${te(2)}}`)}if(E.length===0)l.push(...Qn("Functions",2));else{l.push(`${te(2)}Functions: {`);for(let F of E)l.push(...CS(c,F,3));l.push(`${te(2)}}`)}if(se.length===0)l.push(...Qn("Enums",2));else{l.push(`${te(2)}Enums: {`);for(let F of se)l.push(`${te(3)}${ut(F.name)}: ${F.values.map(Ie).join(" | ")}`);l.push(`${te(2)}}`)}l.push(...Qn("CompositeTypes",2)),l.push(`${te(1)}}`)}return l.push("}"),t.helpers!==!1&&(l.push(""),l.push(LS(n))),t.constants!==!1&&(l.push(""),l.push(MS(u,o,n))),`${l.join(`
601
769
  `)}
602
- `}function rS(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=Dt(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${Q(1)}${ot(o)}: {`),r.push(`${Q(2)}Enums: {},`),r.push(`${Q(1)}},`);continue}r.push(`${Q(1)}${ot(o)}: {`),r.push(`${Q(2)}Enums: {`);for(let a of s)r.push(`${Q(3)}${ot(a.name)}: [${a.values.map(Oe).join(", ")}],`);r.push(`${Q(2)}},`),r.push(`${Q(1)}},`)}return r.push("} as const"),r.join(`
603
- `)}function oS(e){return`type DefaultSchemaName = Extract<keyof Database, ${Oe(e)}>
770
+ `}function MS(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=Kt(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${te(1)}${ut(o)}: {`),r.push(`${te(2)}Enums: {},`),r.push(`${te(1)}},`);continue}r.push(`${te(1)}${ut(o)}: {`),r.push(`${te(2)}Enums: {`);for(let a of s)r.push(`${te(3)}${ut(a.name)}: [${a.values.map(Ie).join(", ")}],`);r.push(`${te(2)}},`),r.push(`${te(1)}},`)}return r.push("} as const"),r.join(`
771
+ `)}function LS(e){return`type DefaultSchemaName = Extract<keyof Database, ${Ie(e)}>
604
772
 
605
773
  type InSchema = { schema: keyof Database }
606
774
 
@@ -637,7 +805,7 @@ export type Enums<
637
805
  export type CompositeTypes<
638
806
  Choice extends keyof Database[DefaultSchemaName]["CompositeTypes"] | InSchema,
639
807
  Name extends Choice extends InSchema ? keyof Database[Choice["schema"]]["CompositeTypes"] : never = never,
640
- > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function cS(e){let t=Un(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 lo(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 ya(e,t){return new Promise(n=>{let r=sS("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 dS=`
808
+ > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function BS(e){let t=Vn(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 _o(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 Da(e,t){return new Promise(n=>{let r=DS("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 HS=`
641
809
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
642
810
  select json_build_object(
643
811
  'tables', coalesce((
@@ -741,55 +909,59 @@ select json_build_object(
741
909
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
742
910
  ), '[]'::json)
743
911
  )::text
744
- `;function uS(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function pS(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 mS=`select exists (
912
+ `;function GS(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function KS(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 qS=`select exists (
745
913
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
746
914
  where n.nspname = 'lepis' and c.relname = 'relation'
747
- )::text`,fS=`select coalesce(json_agg(json_build_object(
915
+ )::text`,zS=`select coalesce(json_agg(json_build_object(
748
916
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
749
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function gS(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:hS(t)}function hS(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 yS(e,t){let n=await e(mS);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(fS);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 wS(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?lo(e.dbUrl):cS(await Fe(bS(e))),r=!1,o=async N=>{if(e.sql)return e.sql(N);if(r&&e.ref)return bu(e.ref,N);let oe=await ya(n,N);return oe.code===127&&!e.dbUrl&&e.ref?(r=!0,bu(e.ref,N)):oe},s=dS.replace("$SCHEMAS$",uS(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=gS(c);i||te("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await yS(o,t),{schema:l,enums:m}=pS(i,n.database,Date.now()),y=wu(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),T=null;return e.out&&(T=iS(e.out)?e.out:lS(process.cwd(),e.out),aS(T,y,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(N=>N.kind!=="view").length,views:l.tables.filter(N=>N.kind==="view").length,functions:(l.routines??[]).length,enums:m.length,path:T,distribution:u,types:y}}async function bu(e,t,n=E){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 bS(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 Su(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 wS(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 v_}from"node:crypto";import{existsSync as wi,mkdirSync as P_,readFileSync as bi,readdirSync as x_,statSync as E_,writeFileSync as A_}from"node:fs";import{dirname as R_,isAbsolute as of,join as er,resolve as sf}from"node:path";import{pathToFileURL as T_}from"node:url";de();function O_(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:of(e)||e.startsWith("./")||e.startsWith("../")?T_(sf(e)).href:e}function N_(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(O_(e)):Promise.resolve().then(()=>(ef(),Qm))}function I_(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 tf="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 nf(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function tr(){let e;try{e=await N_()}catch(t){te("tool-missing",I_(t))}return{ready:async t=>{let n=await nf(e,t);return typeof n=="string"?await e.podmanReady(tf)??n:e.podmanReady(tf,n.name)},ensureImage:async(t,n)=>{let r=await nf(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 af(e){return er(e,".snoutdata","local.json")}function Go(e){let t=af(e);if(!wi(t))return null;try{let n=JSON.parse(bi(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 C_(e,t){let n=af(e);return P_(R_(n),{recursive:!0}),A_(n,`${JSON.stringify(t,null,2)}
750
- `,{encoding:"utf8",mode:384}),n}var M_=54322;async function lf(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 cf(e,t,n,r=ya){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 D_(e,t={}){let n=t.cwd??process.cwd(),r=Go(n);await lf(e,r?.ref??null);let o=r??e.mint(t.port??M_),s=r?null:C_(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=cf(e,o,lo(c)),u=await L_(i,n,t),l=await j_(i,n,u.length>0||!!s);return{status:a,uri:c,applied:u,seeded:l,created:s}}async function L_(e,t,n){let r=sf(n.dir===void 0?er(t,"migrations"):of(n.dir)?n.dir:er(t,n.dir));if(!wi(r)||!E_(r).isDirectory())return[];let o=x_(r).filter(m=>m.endsWith(".sql")).map(m=>{let y=bi(er(r,m),"utf8");return{name:m,sql:y,checksum:v_("sha256").update(y,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(U_);s.code!==0&&te(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${Qn}`);let a=await e(`select name || E'\\t' || checksum from ${Qn};`);a.code!==0&&te("failed",a.err.trim()||`could not read ${Qn}`);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=ro(o,c,{outOfOrder:n.outOfOrder});i.ok||te("conflict",`Refused. This folder and the local database do not agree:
917
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function WS(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:JS(t)}function JS(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 VS(e,t){let n=await e(qS);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(zS);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 YS(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?_o(e.dbUrl):BS(await qe(XS(e))),r=!1,o=async E=>{if(e.sql)return e.sql(E);if(r&&e.ref)return Yu(e.ref,E);let se=await Da(n,E);return se.code===127&&!e.dbUrl&&e.ref?(r=!0,Yu(e.ref,E)):se},s=HS.replace("$SCHEMAS$",GS(t)),a=await o(s);a.code===127&&re("tool-missing",a.err.trim()),a.code!==0&&re("failed",a.err.trim()||`could not read the schema of ${n.label}`);let c=a.out.trim();c||re("failed",`${n.label} returned nothing for the schema query`);let i=WS(c);i||re("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await VS(o,t),{schema:l,enums:m}=KS(i,n.database,Date.now()),h=Vu(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),A=null;return e.out&&(A=US(e.out)?e.out:FS(process.cwd(),e.out),jS(A,h,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(E=>E.kind!=="view").length,views:l.tables.filter(E=>E.kind==="view").length,functions:(l.routines??[]).length,enums:m.length,path:A,distribution:u,types:h}}async function Yu(e,t,n=T){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 XS(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 Xu(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&re("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await YS(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}ke();import{createHash as nv}from"node:crypto";import{existsSync as ji,mkdirSync as rv,readFileSync as Ui,readdirSync as ov,statSync as sv,writeFileSync as av}from"node:fs";import{dirname as iv,isAbsolute as Lf,join as dr,resolve as Df}from"node:path";import{pathToFileURL as lv}from"node:url";pe();function cv(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:Lf(e)||e.startsWith("./")||e.startsWith("../")?lv(Df(e)).href:e}function dv(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(cv(e)):Promise.resolve().then(()=>(Nf(),Of))}function uv(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 If="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 Cf(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function ur(){let e;try{e=await dv()}catch(t){re("tool-missing",uv(t))}return{ready:async t=>{let n=await Cf(e,t);return typeof n=="string"?await e.podmanReady(If)??n:e.podmanReady(If,n.name)},ensureImage:async(t,n)=>{let r=await Cf(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 jf(e){return dr(e,".snoutdata","local.json")}function ns(e){let t=jf(e);if(!ji(t))return null;try{let n=JSON.parse(Ui(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 pv(e,t){let n=jf(e);return rv(iv(n),{recursive:!0}),av(n,`${JSON.stringify(t,null,2)}
918
+ `,{encoding:"utf8",mode:384}),n}var mv=54322;async function Uf(e,t){let n=await e.ensureImage(t,o=>d(o));n&&re("tool-missing",n);let r=await e.ready(t);r&&re("tool-missing",r),await e.prepare()}function Ff(e,t,n,r=Da){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 fv(e,t={}){let n=t.cwd??process.cwd(),r=ns(n);await Uf(e,r?.ref??null);let o=r??e.mint(t.port??mv),s=r?null:pv(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=Ff(e,o,_o(c)),u=await gv(i,n,t),l=await hv(i,n,u.length>0||!!s);return{status:a,uri:c,applied:u,seeded:l,created:s}}async function gv(e,t,n){let r=Df(n.dir===void 0?dr(t,"migrations"):Lf(n.dir)?n.dir:dr(t,n.dir));if(!ji(r)||!sv(r).isDirectory())return[];let o=ov(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Ui(dr(r,m),"utf8");return{name:m,sql:h,checksum:nv("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(yv);s.code!==0&&re(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${cr}`);let a=await e(`select name || E'\\t' || checksum from ${cr};`);a.code!==0&&re("failed",a.err.trim()||`could not read ${cr}`);let c=a.out.split(`
919
+ `).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=yo(o,c,{outOfOrder:n.outOfOrder});i.ok||re("conflict",`Refused. This folder and the local database do not agree:
752
920
  ${i.problems.map(m=>` ${m.reason}`).join(`
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 ${Qn} (name, checksum) values (${rf(y.name)}, ${rf(y.checksum)});`,N=on(y.sql);N||d(`${y.name} runs outside a transaction, so an interruption can leave it half applied.`);let oe=N?`begin;
754
- ${y.sql}
755
- ${T}
921
+ `)}`,{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),A=`insert into ${cr} (name, checksum) values (${Mf(h.name)}, ${Mf(h.checksum)});`,E=fn(h.sql);E||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let se=E?`begin;
922
+ ${h.sql}
923
+ ${A}
756
924
  commit;
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 j_(e,t,n){let r=er(t,"seed.sql");if(!n||!wi(r))return!1;let o=await e(bi(r,"utf8"));return o.code!==0&&te("failed",`seed.sql failed.
761
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var Qn="_snoutdata_migrations",U_=`create table if not exists ${Qn} (
925
+ `:`${h.sql}
926
+ ${A}
927
+ `,F=await e(se);F.code!==0&&re("failed",`${h.name} failed, and nothing after it was tried.
928
+ ${F.err.trim()}`,{failed:h.name,applied:l}),l.push(h.name),d(` applied ${h.name}`)}return l}async function hv(e,t,n){let r=dr(t,"seed.sql");if(!n||!ji(r))return!1;let o=await e(Ui(r,"utf8"));return o.code!==0&&re("failed",`seed.sql failed.
929
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var cr="_snoutdata_migrations",yv=`create table if not exists ${cr} (
762
930
  name text primary key,
763
931
  checksum text not null,
764
932
  applied_at timestamptz not null default now()
765
- );`;function rf(e){return`'${e.replace(/'/g,"''")}'`}function F_(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 df(e,t={}){let n=await D_(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($(" snoutdata gen types typescript --local > database.types.ts")),d($(" snoutdata stop"))}),0}async function uf(e,t={}){let n=t.cwd??process.cwd(),r=Go(n);r||te("not-found","there is no local database for this folder. `snoutdata start` makes one."),await lf(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 pf(e,t={}){let n=t.cwd??process.cwd(),r=Go(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")} ${F_(s.databaseBytes)}`),s.services.length>0&&d(` ${M("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
767
- `)}),0}async function mf(e,t=process.cwd()){let n=Go(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:cf(e,n,lo(r))}}pe();de();hr();$n();wt();import{resolve as ff}from"node:path";function B_(){let e=n=>mt(n),t;return{shards:n=>{if(n!==void 0)return Cn({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??=Cn({}),t},whoami:()=>It(),listProjects:async()=>({...await qe(),local:H_()}),localProject:n=>e(n),createProject:async n=>{let r=await E("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Tn(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)=>E("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>Zt(n),requestExport:n=>An(n),resetPassword:n=>E("cloud-project-reset-password",{ref:n}),listTeams:()=>E("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?ks(s,r,o):As(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Mr(r):Rs(n)},deleteFunction:(n,r)=>{let o=e(n);return o?$s(o,r):Ts(n,r)},sizeFunction:(n,r,o)=>Os(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?Lr(r):E("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>E("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>en(n),deployContainer:(n,r,o)=>Ls(n,r,o),restartContainer:(n,r)=>Us(n,r),deleteContainer:(n,r)=>js(n,r),containerLogs:(n,r)=>Ur(n,r),containerEgress:async n=>{let r=await en(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await Fr(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>E("cloud-token-list",{}),createToken:(n,r,o)=>E("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>E("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:Mr(r).functions.map(o=>o.name),secretNames:Lr(r).secrets.map(o=>o.name)}:ta(n)},getProducts:n=>nn(n),setProduct:(n,r,o)=>Xs(n,r,o),listDomains:n=>zr(n),domainAction:(n,r,o)=>Zs(n,r,o),restoreWindow:n=>Qs(n),restoreTo:(n,r,o)=>ea(n,r,o),setProjectState:async(n,r)=>{let o=await E(`cloud-project-${n}`,{ref:r});try{let s=await Rn(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)=>E("cloud-project-spread",o?{ref:n,instances:r}:{ref:n,preview:!0,...r===void 0?{}:{instances:r}}),connection:n=>Fe(n),pushMigrations:(n,r)=>fa(n,{dir:r.dir,dryRun:r.dryRun})}}function H_(){let e=cr().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Wt(process.cwd())?.local;if(t&&!e.some(n=>ff(n.folder)===ff(t)))try{let n=St(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var gf="app_";async function G_(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await fr();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await il(t)).map(o=>({name:`${gf}${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 hf(e){En(!0);let{tools:t,config:n}=await G_(),r=B_();n&&(r.callBorrowed=(i,u)=>ll(n,i.slice(gf.length),u));let o={...e,borrowed:t};ce(t.length>0?`snoutdata mcp: ${jn.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 It();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=Qc(s+u);s=l.rest;for(let m of l.messages)c(K_(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 K_(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 Zc(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
933
+ );`;function Mf(e){return`'${e.replace(/'/g,"''")}'`}function wv(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 Bf(e,t={}){let n=await fv(e,t);return b(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${L("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${L("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${L("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
934
+ `),d(v(" snoutdata gen types typescript --local > database.types.ts")),d(v(" snoutdata stop"))}),0}async function Hf(e,t={}){let n=t.cwd??process.cwd(),r=ns(n);r||re("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Uf(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 Gf(e,t={}){let n=t.cwd??process.cwd(),r=ns(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(` ${L("Project")} ${s.ref}`),d(` ${L("State")} ${s.ready?"running":s.status}`),d(` ${L("Port")} ${s.port}`),d(` ${L("Size")} ${wv(s.databaseBytes)}`),s.services.length>0&&d(` ${L("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
935
+ `)}),0}async function Kf(e,t=process.cwd()){let n=ns(t);n||re("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Ff(e,n,_o(r))}}fe();pe();xr();Nn();Et();import{resolve as qf}from"node:path";function bv(){let e=n=>kt(n),t;return{shards:n=>{if(n!==void 0)return Kn({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??=Kn({}),t},whoami:()=>Bt(),listProjects:async()=>({...await Xe(),local:Sv()}),localProject:n=>e(n),createProject:async n=>{let r=await T("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Fn(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)=>T("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>ln(n),requestExport:n=>jn(n),resetPassword:n=>T("cloud-project-reset-password",{ref:n}),listTeams:()=>T("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Bs(s,r,o):Js(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Jr(r):Vs(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Gs(o,r):Ys(n,r)},sizeFunction:(n,r,o)=>Xs(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?Yr(r):T("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>T("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>dn(n),deployContainer:(n,r,o)=>ra(n,r,o),restartContainer:(n,r)=>sa(n,r),deleteContainer:(n,r)=>oa(n,r),containerLogs:(n,r)=>Zr(n,r),containerEgress:async n=>{let r=await dn(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await Qr(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>T("cloud-token-list",{}),createToken:(n,r,o)=>T("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>T("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:Jr(r).functions.map(o=>o.name),secretNames:Yr(r).secrets.map(o=>o.name)}:ka(n)},getProducts:n=>pn(n),setProduct:(n,r,o)=>ya(n,r,o),listDomains:n=>ao(n),domainAction:(n,r,o)=>wa(n,r,o),restoreWindow:n=>ba(n),restoreTo:(n,r,o)=>Sa(n,r,o),setProjectState:async(n,r)=>{let o=await T(`cloud-project-${n}`,{ref:r});try{let s=await Un(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)=>T("cloud-project-spread",o?{ref:n,instances:r}:{ref:n,preview:!0,...r===void 0?{}:{instances:r}}),connection:n=>qe(n),pushMigrations:(n,r)=>Ca(n,{dir:r.dir,dryRun:r.dryRun})}}function Sv(){let e=br().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Ft(process.cwd())?.local;if(t&&!e.some(n=>qf(n.folder)===qf(t)))try{let n=Rt(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var zf="app_";async function kv(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await $r();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await El(t)).map(o=>({name:`${zf}${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 Wf(e){Ln(!0);let{tools:t,config:n}=await kv(),r=bv();n&&(r.callBorrowed=(i,u)=>Al(n,i.slice(zf.length),u));let o={...e,borrowed:t};ue(t.length>0?`snoutdata mcp: ${Jn.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 Bt();ue(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(ue(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return ue("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;ue(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}ue(`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=Td(s+u);s=l.rest;for(let m of l.messages)c(_v(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 _v(e,t,n){let r;try{r=JSON.parse(e)}catch{ue(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Rd(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
768
936
  `)}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)}})}
769
- `)}}$n();pe();de();function q_(e){return e>=1?"over":e>=.8?"near":"ok"}function yf(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:q_(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Si(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 wf(e,t={}){let n=await E("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=yf(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=`${gt(r.storage.usedBytes)} of ${gt(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(`${gt(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"}: ${Si(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d($(`Peak in that window was ${gt(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?"-":gt(o.dbBytes),o.repoBytes===null?"-":gt(o.repoBytes),Si(o.computeSeconds),String(o.connections)])])),d($("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}Te();be();kt();de();import{createInterface as W_}from"node:readline/promises";var z_=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"]),ki={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"},J_="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global|auto, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function bf(e,t,n=!1){let[r,o,...s]=e,a=l=>A(t,l),c=(l,m)=>{if(l===void 0||l==="")throw new h(m);return l},i,u=`${r??""}${o!==void 0&&V_(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:z(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=lt(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(ki))Object.assign(i,lt(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"),...lt(t,"port","port"),...Nt(t,"dbname","dbname"),...Nt(t,"sslmode","sslmode"),...Nt(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:Wo(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, ...)"),...lt(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Y_(a("seed"))}:{},...a("nodes")?{nodes:Wo(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!=="")}:{},...lt(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=Ko(s[0],t);i={op:"range.split",keyspace:l.keyspace,range:l.lo,...a("at")!==void 0?{at:_i(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let l=Ko(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=Ko(s[0],t),m=Ko(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",...Nt(t,"keyspace","keyspace")};break;case"scale":{let l=a("add")?Wo(a("add")).map(y=>X_(y,t)):[],m=lt(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",...Nt(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Nt(t,"node","node")};break;default:throw new h(`unknown operation: cluster ${e.join(" ")}. One of ${J_}`)}for(let[l,m]of Object.entries(ki)){let y=lt(t,m,l);Object.assign(i,y)}return{body:i,destructive:z_.has(i.op)}}function V_(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function Wo(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Nt(e,t,n){let r=A(e,t);return r===void 0?{}:{[n]:r}}function lt(e,t,n){let r=A(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 _i(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 Y_(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 Ko(e,t){let n=A(t,"keyspace"),r=A(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:_i(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:_i(o,"--range")}}function X_(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])}:{},...Nt(t,"dbname","dbname"),...Nt(t,"sslmode","sslmode")}}function qo(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 $f(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=A(e,"project")??A(e,"ref"),c=a!==void 0,i=()=>t.client??Cn({project:a,admin:A(e,"admin"),token:A(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 nt))throw new h("cluster enable needs --project <ref>");if(z(e,"plan")){let y=await l.enable(!0);return b(y,()=>ze(y,process.stdout)),0}if(!z(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(ze(await l.enable(!0),process.stderr),!await(t.confirm??Ht)(`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(Pt(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 nt))throw new h("cluster disable needs --project <ref>");if(z(e,"plan")){let y=await l.disable(!0);return b(y,()=>ze(y,process.stdout)),0}if(!z(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(ze(await l.disable(!0),process.stderr),!await(t.confirm??Ht)(`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(Pt(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 nt))throw new h("cluster spread needs --project <ref>");let m={table:o??Sn(A(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --distribution-key C --key-type T"),column:Sn(A(e,"distribution-key")??A(e,"column"),"cluster spread needs --distribution-key, the column to spread the rows by"),key_type:Sn(A(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(z(e,"plan")){let T=await l.spread(m,!0);return b(T,()=>ze(T,process.stdout)),0}if(!z(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(ze(await l.spread(m,!0),process.stderr),!await(t.confirm??Ht)(`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(Pt(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 nt))throw new h("cluster retry needs --project <ref>");let m=z(e,"queue")?await l.retry({queue:!0}):await l.retry({node:Sn(o??A(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return b(m,()=>d(Pt(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 nt))throw new h("cluster replica needs --project <ref>");let m=o==="remove",y=Sn(s??A(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(z(e,"plan")){let N=await l.replica(y,m,!0);return b(N,()=>ze(N,process.stdout)),0}if(!z(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(ze(await l.replica(y,m,!0),process.stderr),!await(t.confirm??Ht)(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(Pt(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 nt))throw new h("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await l.alerts();return b({alerts:m},()=>xf(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 h(`not an alert id: ${m} (as cluster alerts lists it)`);let y=await l.acknowledge(m.toLowerCase());return b(y,()=>d(Pt(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,()=>a$(l)),0}case"ops":{let l=await i().ops();return b(l,()=>{process.stdout.write(`${l.ops.join(`
937
+ `)}}Nn();fe();pe();function vv(e){return e>=1?"over":e>=.8?"near":"ok"}function Jf(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:vv(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Fi(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 Vf(e,t={}){let n=await T("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Jf(n.days,n.limit);return b({...n,summary:r},()=>{if(d(L(`${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=`${vt(r.storage.usedBytes)} of ${vt(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(`${vt(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"}: ${Fi(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 ${vt(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(ee([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":vt(o.dbBytes),o.repoBytes===null?"-":vt(o.repoBytes),Fi(o.computeSeconds),String(o.connections)])])),d(v("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}Te();ke();Tt();pe();import{createInterface as $v}from"node:readline/promises";var Pv=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"]),Bi={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"},xv="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global|auto, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Yf(e,t,n=!1){let[r,o,...s]=e,a=l=>R(t,l),c=(l,m)=>{if(l===void 0||l==="")throw new y(m);return l},i,u=`${r??""}${o!==void 0&&Ev(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new y("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:W(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new y("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 y("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let l=gt(t,"nodes","nodes");if(!l.nodes)throw new y("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(Bi))Object.assign(i,gt(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"),...gt(t,"port","port"),...Ut(t,"dbname","dbname"),...Ut(t,"sslmode","sslmode"),...Ut(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:ss(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, ...)"),...gt(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Av(a("seed"))}:{},...a("nodes")?{nodes:ss(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 y("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!=="")}:{},...gt(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=rs(s[0],t);i={op:"range.split",keyspace:l.keyspace,range:l.lo,...a("at")!==void 0?{at:Hi(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let l=rs(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 y("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let l=rs(s[0],t),m=rs(s[1],t);if(l.keyspace!==m.keyspace)throw new y(`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",...Ut(t,"keyspace","keyspace")};break;case"scale":{let l=a("add")?ss(a("add")).map(h=>Rv(h,t)):[],m=gt(t,"remove","remove");if(l.length===0&&!m.remove)throw new y("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",...Ut(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Ut(t,"node","node")};break;default:throw new y(`unknown operation: cluster ${e.join(" ")}. One of ${xv}`)}for(let[l,m]of Object.entries(Bi)){let h=gt(t,m,l);Object.assign(i,h)}return{body:i,destructive:Pv.has(i.op)}}function Ev(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function ss(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Ut(e,t,n){let r=R(e,t);return r===void 0?{}:{[n]:r}}function gt(e,t,n){let r=R(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new y(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Hi(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new y(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function Av(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new y(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function rs(e,t){let n=R(t,"keyspace"),r=R(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new y(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Hi(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new y("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Hi(o,"--range")}}function Rv(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new y(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Ut(t,"dbname","dbname"),...Ut(t,"sslmode","sslmode")}}function os(e,t){if(e===void 0||!/^\d+$/.test(e))throw new y(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function eg(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=R(e,"project")??R(e,"ref"),c=a!==void 0,i=()=>t.client??Kn({project:a,admin:R(e,"admin"),token:R(e,"token")});switch(r){case"enable":{if(!c)throw new y("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 ct))throw new y("cluster enable needs --project <ref>");if(W(e,"plan")){let h=await l.enable(!0);return b(h,()=>Qe(h,process.stdout)),0}if(!W(e,"yes")){if(!t.confirm&&!_e())throw new y("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(Qe(await l.enable(!0),process.stderr),!await(t.confirm??Yt)(`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(Nt(m))),0}case"disable":{if(!c)throw new y("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 ct))throw new y("cluster disable needs --project <ref>");if(W(e,"plan")){let h=await l.disable(!0);return b(h,()=>Qe(h,process.stdout)),0}if(!W(e,"yes")){if(!t.confirm&&!_e())throw new y("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(Qe(await l.disable(!0),process.stderr),!await(t.confirm??Yt)(`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(Nt(m))),0}case"spread":{if(!c)throw new y("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 ct))throw new y("cluster spread needs --project <ref>");let m={table:o??Rn(R(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --distribution-key C --key-type T"),column:Rn(R(e,"distribution-key")??R(e,"column"),"cluster spread needs --distribution-key, the column to spread the rows by"),key_type:Rn(R(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(W(e,"plan")){let A=await l.spread(m,!0);return b(A,()=>Qe(A,process.stdout)),0}if(!W(e,"yes")){if(!t.confirm&&!_e())throw new y("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(Qe(await l.spread(m,!0),process.stderr),!await(t.confirm??Yt)(`Spread ${m.table} over ${l.target}?`))throw new C("failed","Not spread. Nothing was changed.")}let h=await l.spread(m,!1);return b(h,()=>d(Nt(h))),0}case"retry":{if(!c)throw new y("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 ct))throw new y("cluster retry needs --project <ref>");let m=W(e,"queue")?await l.retry({queue:!0}):await l.retry({node:Rn(o??R(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return b(m,()=>d(Nt(m))),0}case"replica":{if(!c)throw new y("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new y("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let l=i();if(!(l instanceof ct))throw new y("cluster replica needs --project <ref>");let m=o==="remove",h=Rn(s??R(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(W(e,"plan")){let E=await l.replica(h,m,!0);return b(E,()=>Qe(E,process.stdout)),0}if(!W(e,"yes")){if(!t.confirm&&!_e())throw new y(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(Qe(await l.replica(h,m,!0),process.stderr),!await(t.confirm??Yt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new C("failed","Nothing was changed.")}let A=await l.replica(h,m,!1);return b(A,()=>d(Nt(A))),0}case"alerts":{if(!c)throw new y("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let l=i();if(!(l instanceof ct))throw new y("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await l.alerts();return b({alerts:m},()=>rg(m,process.stdout,!0,l.ref)),0}if(o==="seen"||o==="acknowledge"){let m=Rn(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new y(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await l.acknowledge(m.toLowerCase());return b(h,()=>d(Nt(h))),0}throw new y(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let l=await i().status();return b(l,()=>jv(l)),0}case"ops":{let l=await i().ops();return b(l,()=>{process.stdout.write(`${l.ops.join(`
770
938
  `)}
771
- `),d($(`Default settings: ${Object.entries(l.settings).map(([m,y])=>`${m}=${y}`).join(", ")}`)),l.refusals&&l.refusals.length>0&&d($(`Refusal kinds: ${l.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return e$(e,i);case"autopilot":return n$(o,e,i,t);case"advise":case"advice":{let l=lt(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(l);return b(m,()=>s$(m,process.stdout)),0}case"check":{let l=await i().check();return b(l,()=>o$(l,process.stdout)),l.summary.errors>0?1:0}case"jobs":return r$(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??[]},()=>Pf(l)),0}break;case"plan":{let l=bf(n.slice(1),e,c),m=await i().plan(l.body);return b(m,()=>ze(m,process.stdout)),0}}let u=bf(n,e,c);if(z(e,"plan")){let l=await i().plan(u.body);return b(l,()=>ze(l,process.stdout)),0}return Z_(u,e,i,t)}async function Z_(e,t,n,r){let o=z(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(ze(u,process.stderr),!await(r.confirm??Ht)(`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(z(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 $i(a,c.job,r);return vi(i)}function Sn(e,t){if(e===void 0||e==="")throw new h(t);return e}async function Ht(e){let t=W_({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 Q_(e){let t=[...Ks,...Wc],n={};for(let r of Wo(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 e$(e,t){let n={};for(let s of Ks)Object.assign(n,lt(e,ki[s],s));let r=A(e,"set");if(r!==void 0&&Object.assign(n,Q_(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 zo(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 Sf(e){return e?.op?` ${e.op}${e.keyspace?` in ${String(e.keyspace)}`:""}`:""}function t$(e){let t=e.cloud;if(t?.mode==="auto"){let o=[`${zo("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=Sf(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[zo("off",e.interval_ms)];let n=[zo(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=Sf(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 n$(e,t,n,r){let o=lt(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 t$(c))d(i)}),0}if(!zc.includes(e))throw new h(`cluster autopilot [off|advise|auto] [--interval-ms N], not "${e}"`);let s=e;if(s==="auto"&&!z(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??Ht)("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(zo(s,o.autopilot_interval_ms??6e4))),0}async function r$(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return b({jobs:s},()=>p$(s)),0}case"show":{let s=await r.job(qo(t,"show"));return b(s,()=>m$(s)),0}case"watch":{let s=await $i(r,qo(t,"watch"),o);return vi(s)}case"cancel":{let s=qo(t,"cancel");if(!z(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??Ht)(`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=qo(t,"resume"),a=await r.resume(s);return z(n,"no-wait")?(b(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):vi(await $i(r,s,o))}default:throw new h(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function $i(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($(` step ${c.n+1}/${a.steps.length} ${c.kind}: ${u}`)))}if(In(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 vi(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 K(e,t=""){e.write(`${t}
775
- `)}function Pi(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 ze(e,t){if(K(t,`${M(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&K(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")}`,Pi(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),K(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Pi(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"};K(t,X([[" TABLE","CHOICE","WHY"],...e.choices.map(r=>[` ${r.table}`,r.choice==="shard"?`${n.shard} ${r.column}`:n[r.choice],r.why])]))}K(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)K(t,` warning: ${n}`);e.assumptions&&K(t,$(` ${e.assumptions}`))}function Qe(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function vf(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Qe(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Qe(t.at)}`:""}${t.to!==void 0?` --to ${Qe(t.to)}`:""}`;case"range.move":return`${n} range move ${Qe(`${t.keyspace}:${t.range}`)} --to ${Qe(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Qe(t.keyspace)} --value ${Qe(t.value)}${t.node!==void 0?` --node ${Qe(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Qe(t.name)} --host ${t.host?Qe(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Qe(t.keyspace)}`:""}`;default:return null}}function o$(e,t){let{errors:n,warnings:r,info:o,tables:s}=e.summary;K(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(K(t),K(t,`${M(`${i+1}. ${c.table}`)} (${a[c.severity]}, ${c.code})`),K(t,` ${c.message}`),K(t,` ${c.fix}`),c.sql)for(let u of c.sql.split(`
776
- `))K(t,` sql: ${u}`);c.operation&&K(t,` run: ${vf(c.operation)??JSON.stringify(c.operation)}`)})}function s$(e,t){K(t,e.summary),e.advice.forEach((n,r)=>{K(t),K(t,`${M(`${r+1}. ${n.op}`)} (${n.metric})`),K(t,` ${n.reason}`);let o=vf(n.request);if(K(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;K(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Pi(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])K(t,` warning: ${a}`)}else n.needs.length>0?K(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&K(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(K(t),K(t,$(e.assumptions)))}function kf(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Pf(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}K(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 a$(e){let t=process.stdout;if(e.cloud&&(u$(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){K(t,"The home node has no Lepis catalog yet (schema lepis).");return}K(t,`${M("Cluster")} at epoch ${e.epoch}`),K(t),Pf(e);for(let s of e.keyspaces??[]){K(t),K(t,`${M(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),K(t,X([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,kf(e,a.node)])]));for(let a of s.pins)K(t,` pinned: ${a.value} on ${kf(e,a.node)}`)}let n=e.tables??[];n.length>0&&(K(t),K(t,X([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(K(t),K(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&&K(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d($(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function i$(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 l$(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 _f(e){return e.severity.toUpperCase()}function xf(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)K(t,`${M(_f(a))} ${a.message}${a.acknowledged?$(" (seen)"):""}`);o.length>0&&K(t,$(` 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)K(t,`${M(_f(a))} ${a.message}`),K(t,$(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&K(t),K(t,"Resolved in the last day:");for(let a of s)K(t,` ${a.message}${a.resolution?` ${$(a.resolution)}`:""}`)}}function c$(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function d$(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 u$(e,t){let n=d$(e);if(!e.sharded){K(t,`${M(e.ref)} is not a cluster (${n}).`),c$(e)>0&&K(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database keeps running).`);return}xf(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(K(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);K(t,X([[" POD","STATE","NEXT",...c?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,i$(e,i),...c?[l$(i)]:[]])]))}e.router==="starting"?K(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&K(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&K(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?K(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&K(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),K(t)}function p$(e){if(e.length===0){d("No jobs yet.");return}K(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 m$(e){let t=process.stdout;K(t,`${M(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),K(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)&&(K(t),ze(e.plan,t))}be();de();Pn();import{spawn as f$}from"node:child_process";import{createHash as g$}from"node:crypto";import{chmodSync as h$,renameSync as Jo,rmSync as Ef,writeFileSync as y$}from"node:fs";import{dirname as w$,join as b$}from"node:path";var Vo=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",S$="https://github.com/snoutdata/studio/releases/download";function k$(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 _$(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 $$(){let e;try{e=await fetch(`${Vo}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new C("network",`could not reach ${Vo} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new C("server",`${Vo} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new C("server",`${Vo} did not say which version is newest`);return t.version}async function Af(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 v$(e,t){let n=b$(w$(e),`.snoutdata-upgrade-${process.pid}`);y$(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Ef(r,{force:!0}),Jo(e,r);try{Jo(n,e)}catch(o){throw Jo(r,e),o}}else h$(n,493),Jo(n,e)}catch(r){Ef(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"?ut:vn}`):r}}function P$(){return new Promise((e,t)=>{let n=f$(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: ${ut}`))),n.on("exit",r=>e(r??1))})}async function Rf(e={}){let t=e.kind??kr(),n=await $$(),r=pl(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"?ut:vn}`);case"npm":{d(`Upgrading snoutdata ${ke} to ${n} with npm\u2026`);let s=await P$();if(s!==0)throw new C("failed",`npm exited ${s}. Run it yourself: ${ut}`);return b({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=k$(process.platform,process.arch);if(!s)throw new C("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${ut}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${S$}/cli-v${n}`,[c,i]=await Promise.all([Af(`${a}/${s}`),Af(`${a}/SHA256SUMS`)]),u=_$(i.toString("utf8"),s);if(!u)throw new C("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let l=g$("sha256").update(c).digest("hex");if(l!==u)throw new C("failed",`checksum mismatch for ${s}. Refusing to install.
939
+ `),d(v(`Default settings: ${Object.entries(l.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),l.refusals&&l.refusals.length>0&&d(v(`Refusal kinds: ${l.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return Nv(e,i);case"autopilot":return Cv(o,e,i,t);case"advise":case"advice":{let l=gt(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(l);return b(m,()=>Dv(m,process.stdout)),0}case"check":{let l=await i().check();return b(l,()=>Lv(l,process.stdout)),l.summary.errors>0?1:0}case"jobs":return Mv(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??[]},()=>ng(l)),0}break;case"plan":{let l=Yf(n.slice(1),e,c),m=await i().plan(l.body);return b(m,()=>Qe(m,process.stdout)),0}}let u=Yf(n,e,c);if(W(e,"plan")){let l=await i().plan(u.body);return b(l,()=>Qe(l,process.stdout)),0}return Tv(u,e,i,t)}async function Tv(e,t,n,r){let o=W(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!_e())throw new y(`${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(Qe(u,process.stderr),!await(r.confirm??Yt)(`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(W(t,"no-wait"))return b({job:c.job,state:"pending",plan:c.plan},()=>{process.stdout.write(`${c.job}
940
+ `),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 Gi(a,c.job,r);return Ki(i)}function Rn(e,t){if(e===void 0||e==="")throw new y(t);return e}async function Yt(e){let t=$v({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 Ov(e){let t=[...da,...vd],n={};for(let r of ss(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 y(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let c=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(c))throw new y(`--set ${s} must be a whole number, not "${a}"`);n[s]=c}return n}async function Nv(e,t){let n={};for(let s of da)Object.assign(n,gt(e,Bi[s],s));let r=R(e,"set");if(r!==void 0&&Object.assign(n,Ov(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return b({settings:a},()=>process.stdout.write(`${ee(Object.entries(a).map(([c,i])=>[c,String(i)]))}
941
+ `)),0}let o=await t().settings(n);return b(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}function as(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 Xf(e){return e?.op?` ${e.op}${e.keyspace?` in ${String(e.keyspace)}`:""}`:""}function Iv(e){let t=e.cloud;if(t?.mode==="auto"){let o=[`${as("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=Xf(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[as("off",e.interval_ms)];let n=[as(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=Xf(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 Cv(e,t,n,r){let o=gt(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 Iv(c))d(i)}),0}if(!$d.includes(e))throw new y(`cluster autopilot [off|advise|auto] [--interval-ms N], not "${e}"`);let s=e;if(s==="auto"&&!W(t,"yes")){if(!r.confirm&&!_e())throw new y("autopilot auto lets the router move data on its own, and nothing was asked: pass --yes to turn it on");if(!await(r.confirm??Yt)("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(as(s,o.autopilot_interval_ms??6e4))),0}async function Mv(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return b({jobs:s},()=>Kv(s)),0}case"show":{let s=await r.job(os(t,"show"));return b(s,()=>qv(s)),0}case"watch":{let s=await Gi(r,os(t,"watch"),o);return Ki(s)}case"cancel":{let s=os(t,"cancel");if(!W(n,"yes")){if(!o.confirm&&!_e())throw new y(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??Yt)(`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=os(t,"resume"),a=await r.resume(s);return W(n,"no-wait")?(b(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):Ki(await Gi(r,s,o))}default:throw new y(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Gi(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(Gn(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 Ki(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"}.
942
+ `)),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 V(e,t=""){e.write(`${t}
943
+ `)}function qi(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 Qe(e,t){if(V(t,`${L(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&V(t,ee([[" 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")}`,qi(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),V(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${qi(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"};V(t,ee([[" TABLE","CHOICE","WHY"],...e.choices.map(r=>[` ${r.table}`,r.choice==="shard"?`${n.shard} ${r.column}`:n[r.choice],r.why])]))}V(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)V(t,` warning: ${n}`);e.assumptions&&V(t,v(` ${e.assumptions}`))}function nt(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function tg(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${nt(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${nt(t.at)}`:""}${t.to!==void 0?` --to ${nt(t.to)}`:""}`;case"range.move":return`${n} range move ${nt(`${t.keyspace}:${t.range}`)} --to ${nt(t.to)}`;case"tenant.pin":return`${n} tenant pin ${nt(t.keyspace)} --value ${nt(t.value)}${t.node!==void 0?` --node ${nt(t.node)}`:""}`;case"node.add":return`${n} nodes add ${nt(t.name)} --host ${t.host?nt(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${nt(t.keyspace)}`:""}`;default:return null}}function Lv(e,t){let{errors:n,warnings:r,info:o,tables:s}=e.summary;V(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(V(t),V(t,`${L(`${i+1}. ${c.table}`)} (${a[c.severity]}, ${c.code})`),V(t,` ${c.message}`),V(t,` ${c.fix}`),c.sql)for(let u of c.sql.split(`
944
+ `))V(t,` sql: ${u}`);c.operation&&V(t,` run: ${tg(c.operation)??JSON.stringify(c.operation)}`)})}function Dv(e,t){V(t,e.summary),e.advice.forEach((n,r)=>{V(t),V(t,`${L(`${r+1}. ${n.op}`)} (${n.metric})`),V(t,` ${n.reason}`);let o=tg(n.request);if(V(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;V(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${qi(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])V(t,` warning: ${a}`)}else n.needs.length>0?V(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&V(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(V(t),V(t,v(e.assumptions)))}function Zf(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function ng(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}V(process.stdout,ee([["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 jv(e){let t=process.stdout;if(e.cloud&&(Gv(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){V(t,"The home node has no Lepis catalog yet (schema lepis).");return}V(t,`${L("Cluster")} at epoch ${e.epoch}`),V(t),ng(e);for(let s of e.keyspaces??[]){V(t),V(t,`${L(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),V(t,ee([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Zf(e,a.node)])]));for(let a of s.pins)V(t,` pinned: ${a.value} on ${Zf(e,a.node)}`)}let n=e.tables??[];n.length>0&&(V(t),V(t,ee([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(V(t),V(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&&V(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 Uv(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 Fv(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 Qf(e){return e.severity.toUpperCase()}function rg(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)V(t,`${L(Qf(a))} ${a.message}${a.acknowledged?v(" (seen)"):""}`);o.length>0&&V(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)V(t,`${L(Qf(a))} ${a.message}`),V(t,v(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&V(t),V(t,"Resolved in the last day:");for(let a of s)V(t,` ${a.message}${a.resolution?` ${v(a.resolution)}`:""}`)}}function Bv(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function Hv(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 Gv(e,t){let n=Hv(e);if(!e.sharded){V(t,`${L(e.ref)} is not a cluster (${n}).`),Bv(e)>0&&V(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database keeps running).`);return}rg(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(V(t,`${L(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);V(t,ee([[" POD","STATE","NEXT",...c?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,Uv(e,i),...c?[Fv(i)]:[]])]))}e.router==="starting"?V(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&V(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&V(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?V(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&V(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),V(t)}function Kv(e){if(e.length===0){d("No jobs yet.");return}V(process.stdout,ee([["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 qv(e){let t=process.stdout;V(t,`${L(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),V(t,ee([[" 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)&&(V(t),Qe(e.plan,t))}ke();pe();Cn();import{spawn as zv}from"node:child_process";import{createHash as Wv}from"node:crypto";import{chmodSync as Jv,renameSync as is,rmSync as og,writeFileSync as Vv}from"node:fs";import{dirname as Yv,join as Xv}from"node:path";var ls=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Zv="https://github.com/snoutdata/studio/releases/download";function Qv(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 e$(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 t$(){let e;try{e=await fetch(`${ls}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new C("network",`could not reach ${ls} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new C("server",`${ls} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new C("server",`${ls} did not say which version is newest`);return t.version}async function sg(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 n$(e,t){let n=Xv(Yv(e),`.snoutdata-upgrade-${process.pid}`);Vv(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;og(r,{force:!0}),is(e,r);try{is(n,e)}catch(o){throw is(r,e),o}}else Jv(n,493),is(n,e)}catch(r){og(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"?wt:In}`):r}}function r$(){return new Promise((e,t)=>{let n=zv(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: ${wt}`))),n.on("exit",r=>e(r??1))})}async function ag(e={}){let t=e.kind??Or(),n=await t$(),r=Nl(ve,n),o={installed:ve,latest:n,upToDate:!r,install:t};if(!r||e.check)return b({...o,upgraded:!1},()=>{d(r?`snoutdata ${ve} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ve} is the newest version.`)}),0;switch(t){case"npx":return b({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ve}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new C("usage",`this snoutdata (${ve}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?wt:In}`);case"npm":{d(`Upgrading snoutdata ${ve} to ${n} with npm\u2026`);let s=await r$();if(s!==0)throw new C("failed",`npm exited ${s}. Run it yourself: ${wt}`);return b({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Qv(process.platform,process.arch);if(!s)throw new C("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${wt}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Zv}/cli-v${n}`,[c,i]=await Promise.all([sg(`${a}/${s}`),sg(`${a}/SHA256SUMS`)]),u=e$(i.toString("utf8"),s);if(!u)throw new C("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let l=Wv("sha256").update(c).digest("hex");if(l!==u)throw new C("failed",`checksum mismatch for ${s}. Refusing to install.
777
945
  expected ${u}
778
- actual ${l}`);return v$(process.execPath,c),b({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Rr();wt();Pn();var Tf=`snoutdata ${ke} \u2014 hosted Postgres, from a terminal or an agent
946
+ actual ${l}`);return n$(process.execPath,c),b({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Hr();Et();Cn();var ig=`snoutdata ${ve} \u2014 hosted Postgres, from a terminal or an agent
779
947
 
780
948
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
781
949
  --env writes DATABASE_URL into ./.env (and adds
782
950
  .env to .gitignore) instead of printing it
783
- snoutdata login [--provider github] sign in through a browser
784
- snoutdata login --email you@x.com sign in as that account: the browser offers it,
785
- and a different account is reported at once
951
+ snoutdata login sign in through a browser: a page with Google,
952
+ GitHub and your work email (SSO, or a code by email)
953
+ snoutdata login --provider google|github straight to that provider
954
+ snoutdata login --email you@x.com sign in as that account: the page and Google offer
955
+ it, and a different account is reported at once
786
956
  snoutdata login --sso [--domain D] sign in with your company's identity provider;
787
957
  D is a work email or a domain, and it is asked
788
958
  for when not given. Only the domain is sent
789
959
  snoutdata login --device sign in by typing a code into a browser anywhere
790
960
  snoutdata login --no-browser print the URL instead of opening one
791
- snoutdata logout
792
- snoutdata whoami
961
+ snoutdata login --profile acme sign in as a second account, kept beside the first;
962
+ any command takes --profile to run as it
963
+ snoutdata logout [--all] forget this profile's session (--all: every profile's)
964
+ snoutdata whoami [--all] whose session this is; --all lists every profile here
793
965
  snoutdata upgrade [--check] install the newest CLI, the way this one was
794
966
 
795
967
  snoutdata tokens create --name ci [--expires DAYS] [--project REF] a credential for CI, shown once
@@ -797,7 +969,7 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Qn="_snoutdata_migrations",U_=`crea
797
969
  snoutdata tokens revoke <id|sdt_prefix>
798
970
 
799
971
  snoutdata projects list
800
- snoutdata projects create --name X [--region R] [--no-wait] [--show-url]
972
+ snoutdata projects create --name X [--region R] [--servers dedicated --size S [--max-size M]] [--no-wait] [--show-url]
801
973
  prints the ref; the connection string (it holds
802
974
  the password) only with --show-url, or from db url
803
975
  snoutdata projects pause|resume|delete [--ref R] [--no-wait]
@@ -931,7 +1103,7 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Qn="_snoutdata_migrations",U_=`crea
931
1103
  (plus SnoutData Studio's own tools, when it is
932
1104
  running here; SNOUTDATA_NO_DESKTOP opts out)
933
1105
 
934
- Every command takes --json, --quiet and --help, and anything that waits takes --timeout
1106
+ Every command takes --json, --quiet, --help and --profile NAME, and anything that waits takes --timeout
935
1107
  SECONDS. A failure in --json mode is {"ok":false,"code","error"} on stdout, and the exit
936
1108
  code says the same thing more coarsely: 2 the command was wrong, 3 the credential, 4 not
937
1109
  ready yet, 5 forbidden, 6 not found, 7 conflict, 8 quota, 9 network, 10 timed out, 11 this
@@ -945,8 +1117,11 @@ SNOUTDATA_NO_INTERACTIVE) nothing is asked and it exits 3 at once.
945
1117
 
946
1118
  A project comes from --ref, then SNOUTDATA_PROJECT, then
947
1119
  .snoutdata/project.json in this folder or a parent. A token comes from
948
- SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes renews
949
- itself as it is used; CI, which has no browser to sign in with, wants "tokens create".
1120
+ SNOUTDATA_ACCESS_TOKEN, then the profile's session: ~/.snoutdata/auth.json, or
1121
+ ~/.snoutdata/auth.NAME.json for a profile. The profile comes from --profile, then
1122
+ SNOUTDATA_PROFILE, then the folder's link (link and init record it), then the default.
1123
+ The session that login writes renews itself as it is used, in its own profile; CI, which
1124
+ has no browser to sign in with, wants "tokens create".
950
1125
 
951
1126
  psql, pg_dump and pg_restore run by this CLI verify the database's certificate
952
1127
  (sslmode=verify-full). SNOUTDATA_DB_SSLMODE=require turns that check off, for a network
@@ -955,11 +1130,12 @@ that re-signs Postgres traffic.
955
1130
  Docs: https://docs.snoutdata.com/developers/cli
956
1131
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
957
1132
  Source: https://github.com/snoutdata/snout-cli
958
- `,x$=["yes","token"];async function E$(e){let[t,n,...r]=e.command;if(z(e,"version")||t==="version")return b({version:ke},()=>process.stdout.write(`${ke}
959
- `)),0;if(z(e,"help")&&t&&t!=="help"){let i=Fi(t,n);if(i.length>0)return b({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(l=>[l,ns(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}
1133
+ `,o$=["yes","token"];async function s$(e){let[t,n,...r]=e.command;if(W(e,"version")||t==="version")return b({version:ve},()=>process.stdout.write(`${ve}
1134
+ `)),0;if(W(e,"help")&&t&&t!=="help"){let i=al(t,n);if(i.length>0)return b({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(l=>[l,hs(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}
960
1135
  ${u.summary}
961
- `);let y=Math.max(0,...u.flags.map(T=>T.length));for(let T of u.flags)process.stdout.write(` ${T.padEnd(y)} ${ns(u.name,T)}
1136
+ `);let h=Math.max(0,...u.flags.map(A=>A.length));for(let A of u.flags)process.stdout.write(` ${A.padEnd(h)} ${hs(u.name,A)}
962
1137
  `)}process.stdout.write(`
963
- Every command also takes --json, --quiet and --help.
964
- `)}),0}let o=z(e,"help")||t==="help";if(o||!t)return b({version:ke,usage:Tf.trim(),commands:ts},()=>process.stdout.write(Tf)),o?0:ct.usage;let s=()=>{let i=je(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=lr({flag:A(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=lr({flag:A(e,"ref")});return i?mt(i):null};for(let i of x$)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 $f(e,{timeoutMs:s()});case"login":{if(z(e,"device")){let m=await _r({name:gr(),platform:process.platform});b({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d($("Waiting for that to be approved\u2026"))});let y=await $r(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});et({accessToken:y.token});let T=A(e,"email"),N=T?(await It()).email??void 0:void 0;return T&&ys(T,{accessToken:y.token,email:N}),b({ok:!0,name:y.name??null,email:N??null},()=>d(`Signed in${N?` as ${N}`:""}.`)),0}let i=A(e,"domain"),u=z(e,"sso")||i!==void 0;if(u&&A(e,"provider"))throw new h("--sso and --provider are two different doors: pass one or the other");let l=await Ar({provider:A(e,"provider"),sso:u,domain:i,noBrowser:z(e,"no-browser")||!Se(),timeoutMs:s(),email:A(e,"email")});return b({email:l.email??null},()=>d(`Signed in${l.email?` as ${l.email}`:""}.`)),0}case"logout":{Wi();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return b({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d($(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await It(),u=ir()?.expectedEmail,l=!!(u&&i.email&&!ws(u,i.email));return l&&ce(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),b({...i,...u?{expectedEmail:u,matchesExpected:!l}:{}},()=>{process.stdout.write(`${i.email??i.id}
965
- `),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 tc(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new h("tokens create needs --name (what is it for, and where does it live)");return await ec({name:i,expiresInDays:je(e,"expires"),project:A(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new h("tokens revoke needs the token's id or its sdt_ prefix");return await nc(i),0}default:throw new h(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await zl(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new h("projects create needs --name");let u=A(e,"team");return await Ps({teamId:u?await Xl(u):void 0,name:i,region:A(e,"region"),wait:!z(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:z(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Jl(n,A(e,"ref")??r[0]??a(),{wait:!z(e,"no-wait"),timeoutMs:s()}),0;case"scale":{let i=A(e,"instances");return await Ql(A(e,"ref")??r[0]??a(),{instances:i===void 0?void 0:Zl(i),yes:z(e,"yes")}),0}case"show":{let i=A(e,"ref")??r[0]??a(),u=mt(i);u?Ol(u):await id(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 Fl(i,r[0],{dir:A(e,"dir"),verifyJwt:!z(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Bl(i),0;if((n==="delete"||n==="remove")&&r[0])return await Hl(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 cc(a(),u,{dir:A(e,"dir"),entrypoint:A(e,"entrypoint"),verifyJwt:!z(e,"no-verify-jwt")}),0}case void 0:case"list":return await pc(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 uc(a(),u,{memoryMb:je(e,"memory"),concurrency:je(e,"concurrency"),reset:z(e,"reset")}),0}case"logs":return await mc(a(),r[0],{errorsOnly:z(e,"errors"),limit:je(e,"tail"),follow:z(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new h("functions delete needs a name");return await fc(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=A(e,"dockerfile");if(i&&l){if(u)throw new h("containers deploy takes an image or --dockerfile, not both");if(A(e,"registry-user")||z(e,"clear-registry"))throw new h("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Tc(a(),i,{path:l,port:je(e,"port"),wait:!z(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=A(e,"registry-user");if(m&&!z(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 xc(a(),i,{image:u,port:je(e,"port"),registryUser:m,registryPassword:m?await tn():void 0,clearCredential:z(e,"clear-registry"),wait:!z(e,"no-wait")}),0}case void 0:case"list":return await Oc(a()),0;case"logs":return await Cc(a(),r[0],{errorsOnly:z(e,"errors"),limit:je(e,"tail"),follow:z(e,"follow")}),0;case"restart":{if(!r[0])throw new h("containers restart needs a name");return await Nc(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new h("containers delete needs a name");return await Ic(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await Hr(a(),{}),0;if(i==="clear")return await Hr(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!z(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&&z(e,"none"))throw new h("containers egress set takes destinations or --none, not both");return await Hr(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 Kl(i,await Lc(r,{stdin:z(e,"stdin")})),0;if(n===void 0||n==="list")return ql(i),0;if((n==="unset"||n==="remove")&&r[0])return await Wl(i,r[0]),0}switch(n){case"set":return await jc(a(),r,{stdin:z(e,"stdin")}),0;case void 0:case"list":return await Fc(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new h("secrets unset needs a name");return await Uc(a(),u),0}default:throw new h(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await rc(a()),0;case"rotate":return await oc(a(),{force:z(e,"force")}),0;default:throw new h(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await nd(a()),0;if(n==="enable"||n==="disable")return await rd(a(),td(r[0]),n==="enable"),0;throw new h(`unknown command: products ${n}. products [enable|disable ${Wr.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 fu(a()),0;if(i==="set"){let u=ga(r[1]),l=u==="apns"?await pu({p8:A(e,"p8"),keyId:A(e,"key-id"),teamId:A(e,"team-id"),topic:A(e,"topic"),environment:A(e,"environment")}):await mu(A(e,"file"));return await gu(a(),u,l),0}if(i==="remove"||i==="unset")return await hu(a(),ga(r[1])),0;throw new h(`unknown command: push credentials ${i}`)}case"auth":return await eu(a(),n,r,{clientId:A(e,"client-id"),stdin:z(e,"stdin"),siteUrl:A(e,"site-url"),allow:A(e,"allow"),file:A(e,"file"),subject:A(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return kd(a(),{channel:A(e,"channel"),watch:z(e,"watch")});if(n==="logs")return _d(a(),{channel:A(e,"channel"),since:A(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 Nd(a()),0;case"enable":case"disable":return await Id(a(),n==="enable"),0;case"models":if(i===void 0||i==="list")return await Md(a()),0;if(i==="add")return await jd(a(),r[1],{source:A(e,"source"),upstream:A(e,"model"),provider:A(e,"provider"),baseUrl:A(e,"base-url"),region:A(e,"region"),priceIn:A(e,"price-in"),priceOut:A(e,"price-out"),accessKeyId:A(e,"access-key-id"),authHeader:A(e,"auth-header"),stdin:z(e,"stdin")}),0;if(i==="remove"||i==="delete")return await Ud(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 Dd(a()),0;if(i==="remove"||i==="delete")return await Ld(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 Fd(a()),0;if(i==="set")return await Bd(a(),r[1],{models:A(e,"models"),sort:A(e,"sort")}),0;if(i==="remove"||i==="delete")return await Hd(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 Gd(a()),0;if(i==="create")return await Kd(a(),{name:A(e,"name"),models:A(e,"models"),expiresDays:je(e,"expires")}),0;if(i==="revoke")return await qd(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 Wd(a()),0;if(i==="set")return await zd(a(),r[1],{amount:A(e,"amount"),period:A(e,"period"),soft:z(e,"soft")}),0;if(i==="remove"||i==="delete")return await Jd(a(),r[1]),0;throw new h(`unknown command: ai budgets ${i}. ai budgets [set|remove]`);case"usage":return await Vd(a(),{days:je(e,"days"),by:A(e,"by")}),0;case"test":return Yd(a(),{model:A(e,"model"),prompt:A(e,"prompt")});default:throw new h(`unknown command: ai ${n}. ai [enable|disable|models|providers|keys|budgets|usage|test]`)}}case"domains":return await od(a(),n,r[0]),0;case"teams":return await Yl(),0;case"link":{if(z(e,"local")){let l;try{l=Xi(n)}catch(y){throw new C("not-found",y instanceof Error?y.message:String(y))}let m=zt(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=A(e,"ref")??n;if(!i)throw new h("link needs --ref");let u=zt(process.cwd(),{ref:i});return b({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await oa(a()),0;case"psql":return fd(a(),r);case"reset-password":return await Vl(a()),0;case"access":return await wd(a(),r,{level:A(e,"level")}),0;case"push":return du(a(),{dir:A(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return z(e,"window")?(await sd(a()),0):A(e,"at")?(await ad(a(),A(e,"at"),A(e,"name")),0):yd(a(),{file:A(e,"file"),force:!!e.flags.force});case"export":return hd(a(),{timeoutMs:s(),out:A(e,"out"),statusOnly:!!e.flags.status});default:throw new h(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new h(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=z(e,"local")?await mf(await tr()):null,u=i?i.dbUrl:A(e,"db-url");return Su(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(A(e,"schema")??"public").split(",").map(l=>l.trim()).filter(Boolean),out:A(e,"out"),sql:i?.sql})}case"start":{let i=c();if(i)return Rl(i)}return df(await tr(),{port:je(e,"port"),dir:A(e,"dir"),noMigrations:z(e,"no-migrations"),outOfOrder:z(e,"out-of-order")});case"stop":{let i=c();return i?Tl(i):uf(await tr())}case"status":{let i=c();return i?Al(i):pf(await tr())}case"usage":return wf(a(),{days:je(e,"days"),history:!!e.flags.history});case"mcp":return hf({version:ke,allowDelete:!!e.flags["allow-delete"]});case"init":return A$(e);case"upgrade":return Rf({check:z(e,"check")});default:throw new h(`unknown command: ${t}`)}}async function A$(e){let t=Vi({flag:A(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.`),z(e,"env")?await Of(o):await oa(o),t.kind==="link"){let s=mt(o),a=zt(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=A(e,"name")??R$(process.cwd());d(`Creating a project called "${n}"\u2026`),await Ps({name:n,region:A(e,"region"),wait:!0,link:process.cwd(),showUrl:!z(e,"env")});let r=lr({cwd:process.cwd()});return r&&z(e,"env")&&await Of(r),0}async function Of(e){let{connection:t}=await Promise.resolve().then(()=>(pe(),Ze)),n=await t(e),r=gd(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 R$(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function T$(e){if(e instanceof C)return $t(e.code,e.message,e.details),e.exitCode;if(e instanceof h)return $t("usage",e.message),ct.usage;if(e instanceof vt)return $t("not-signed-in",e.message),ct["not-signed-in"];if(e instanceof Ae){let r=dt(e.status,e.serverCode);return $t(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),ct[r]}let t=el(e);if(t)return $t("usage",t),ct.usage;let n=ur(e);return $t(n,e instanceof Error?e.message:String(e)),ct[n]}function xi(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function O$(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function N$(){En(O$(process.argv.slice(2)));let e;try{e=ji(process.argv.slice(2))}catch(t){$t("usage",String(t instanceof Error?t.message:t)),xi(ct.usage);return}En(e.json),gl(e.flags.quiet===!0),tl(pr({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{xi(await E$(e))}catch(t){xi(T$(t))}}N$();
1138
+ Every command also takes --json, --quiet, --help and --profile.
1139
+ `)}),0}let o=W(e,"help")||t==="help";if(o||!t)return b({version:ve,usage:ig.trim(),commands:gs},()=>process.stdout.write(ig)),o?0:st.usage;let s=()=>{let i=Fe(e,"timeout");if(i!==void 0){if(!(i>0))throw new y("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=wr({flag:R(e,"ref")});if(!i)throw new y("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},c=()=>{let i=wr({flag:R(e,"ref")});return i?kt(i):null};for(let i of o$)if(!(i==="yes"&&t==="projects"&&n==="scale")&&e.flags[i]!==void 0&&t!=="cluster")throw new y(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return eg(e,{timeoutMs:s()});case"login":{if(W(e,"device")){let m=await Nr({name:Pr(),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 h=await Ir(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ot({accessToken:h.token});let A=R(e,"email"),E=A?(await Bt()).email??void 0:void 0;return A&&Ds(A,{accessToken:h.token,email:E}),b({ok:!0,name:h.name??null,email:E??null,profile:Se()},()=>d(`Signed in${E?` as ${E}`:""}${zi()}.`)),0}let i=R(e,"domain"),u=W(e,"sso")||i!==void 0;if(u&&R(e,"provider"))throw new y("--sso and --provider are two different doors: pass one or the other");let l=await Br({provider:R(e,"provider"),sso:u,domain:i,noBrowser:W(e,"no-browser")||!_e(),timeoutMs:s(),email:R(e,"email")});return b({email:l.email??null,profile:Se()},()=>d(`Signed in${l.email?` as ${l.email}`:""}${zi()}.`)),0}case"logout":{if(W(e,"all")){let u=Ss();for(let l of u)_s(void 0,l);return b({ok:!0,profiles:u},()=>d(u.length>0?`Signed out of ${u.join(", ")}.`:"No profile was signed in.")),0}_s();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return b({ok:!0,profile:Se(),environmentTokenStillSet:i},()=>{d(`Signed out${zi()}.`),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(W(e,"all")){let h=Ss().map(A=>{let E=ks(void 0,A);return{profile:A,email:E?.email??E?.expectedEmail??null,active:A===Se()}});return b({profiles:h,environmentToken:!!process.env.SNOUTDATA_ACCESS_TOKEN},()=>{h.length===0&&d('No profile is signed in on this machine. Run "snoutdata login [--profile NAME]".');let A=Math.max(0,...h.map(E=>E.profile.length));for(let E of h)process.stdout.write(`${E.active?"*":" "} ${E.profile.padEnd(A)} ${E.email??v("(email not stored)")}
1140
+ `);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 Bt(),u=yr()?.expectedEmail,l=!!(u&&i.email&&!js(u,i.email));l&&ue(`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:Se(),...u?{expectedEmail:u,matchesExpected:!l}:{}},()=>{process.stdout.write(`${i.email??i.id}
1141
+ `),!m&&Se()!==xe&&d(`profile ${Se()}.`),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 Nc(),0;case"create":{let i=R(e,"name")??r[0];if(!i)throw new y("tokens create needs --name (what is it for, and where does it live)");return await Oc({name:i,expiresInDays:Fe(e,"expires"),project:R(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new y("tokens revoke needs the token's id or its sdt_ prefix");return await Ic(i),0}default:throw new y(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await $c(),0;case"create":{let i=R(e,"name")??r[0];if(!i)throw new y("projects create needs --name");let u=R(e,"team");return await qs({teamId:u?await Ac(u):void 0,name:i,region:R(e,"region"),servers:R(e,"servers"),size:R(e,"size"),maxSize:R(e,"max-size"),wait:!W(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:W(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Pc(n,R(e,"ref")??r[0]??a(),{wait:!W(e,"no-wait"),timeoutMs:s()}),0;case"scale":{let i=R(e,"instances");return await Tc(R(e,"ref")??r[0]??a(),{instances:i===void 0?void 0:Rc(i),yes:W(e,"yes")}),0}case"show":{let i=R(e,"ref")??r[0]??a(),u=kt(i);u?lc(u):await jd(i)}return 0;default:throw new y(`unknown command: projects ${n}`)}case"functions":{let i=c();if(i){if(n==="deploy"){if(!r[0])throw new y("functions deploy needs a name: snoutdata functions deploy <name>");return await yc(i,r[0],{dir:R(e,"dir"),verifyJwt:!W(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return wc(i),0;if((n==="delete"||n==="remove")&&r[0])return await bc(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new y("functions deploy needs a name: snoutdata functions deploy <name>");return await Fc(a(),u,{dir:R(e,"dir"),entrypoint:R(e,"entrypoint"),verifyJwt:!W(e,"no-verify-jwt")}),0}case void 0:case"list":return await Gc(a()),0;case"size":{let u=r[0];if(!u)throw new y("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await Hc(a(),u,{memoryMb:Fe(e,"memory"),concurrency:Fe(e,"concurrency"),reset:W(e,"reset")}),0}case"logs":return await Kc(a(),r[0],{errorsOnly:W(e,"errors"),limit:Fe(e,"tail"),follow:W(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new y("functions delete needs a name");return await qc(a(),u),0}default:throw new y(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],l=R(e,"dockerfile");if(i&&l){if(u)throw new y("containers deploy takes an image or --dockerfile, not both");if(R(e,"registry-user")||W(e,"clear-registry"))throw new y("an image built with --dockerfile is uploaded to the project and needs no registry login");return await id(a(),i,{path:l,port:Fe(e,"port"),wait:!W(e,"no-wait")}),0}if(!i||!u)throw new y("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let m=R(e,"registry-user");if(m&&!W(e,"stdin"))throw new y('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await rd(a(),i,{image:u,port:Fe(e,"port"),registryUser:m,registryPassword:m?await un():void 0,clearCredential:W(e,"clear-registry"),wait:!W(e,"no-wait")}),0}case void 0:case"list":return await ld(a()),0;case"logs":return await ud(a(),r[0],{errorsOnly:W(e,"errors"),limit:Fe(e,"tail"),follow:W(e,"follow")}),0;case"restart":{if(!r[0])throw new y("containers restart needs a name");return await cd(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new y("containers delete needs a name");return await dd(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await to(a(),{}),0;if(i==="clear")return await to(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!W(e,"none"))throw new y("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&W(e,"none"))throw new y("containers egress set takes destinations or --none, not both");return await to(a(),{set:u}),0}throw new y(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new y(`unknown command: containers ${n}`)}case"secrets":{let i=c();if(i){if(n==="set")return await kc(i,await fd(r,{stdin:W(e,"stdin")})),0;if(n===void 0||n==="list")return _c(i),0;if((n==="unset"||n==="remove")&&r[0])return await vc(i,r[0]),0}switch(n){case"set":return await gd(a(),r,{stdin:W(e,"stdin")}),0;case void 0:case"list":return await yd(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new y("secrets unset needs a name");return await hd(a(),u),0}default:throw new y(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await Cc(a()),0;case"rotate":return await Mc(a(),{force:W(e,"force")}),0;default:throw new y(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Id(a()),0;if(n==="enable"||n==="disable")return await Cd(a(),Nd(r[0]),n==="enable"),0;throw new y(`unknown command: products ${n}. products [enable|disable ${so.join("|")}]`)}case"push":{if(n!=="credentials")throw new y("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await qu(a()),0;if(i==="set"){let u=Ma(r[1]),l=u==="apns"?await Gu({p8:R(e,"p8"),keyId:R(e,"key-id"),teamId:R(e,"team-id"),topic:R(e,"topic"),environment:R(e,"environment")}):await Ku(R(e,"file"));return await zu(a(),u,l),0}if(i==="remove"||i==="unset")return await Wu(a(),Ma(r[1])),0;throw new y(`unknown command: push credentials ${i}`)}case"auth":return await Ou(a(),n,r,{clientId:R(e,"client-id"),stdin:W(e,"stdin"),siteUrl:R(e,"site-url"),allow:R(e,"allow"),file:R(e,"file"),subject:R(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Zd(a(),{channel:R(e,"channel"),watch:W(e,"watch")});if(n==="logs")return Qd(a(),{channel:R(e,"channel"),since:R(e,"since")});throw new y(`unknown command: realtime ${n}. realtime inspect|logs`)}case"ai":{let i=r[0];switch(n){case void 0:case"status":return await cu(a()),0;case"enable":case"disable":return await du(a(),n==="enable"),0;case"models":if(i===void 0||i==="list")return await pu(a()),0;if(i==="add")return await gu(a(),r[1],{source:R(e,"source"),upstream:R(e,"model"),provider:R(e,"provider"),baseUrl:R(e,"base-url"),region:R(e,"region"),priceIn:R(e,"price-in"),priceOut:R(e,"price-out"),accessKeyId:R(e,"access-key-id"),authHeader:R(e,"auth-header"),stdin:W(e,"stdin")}),0;if(i==="remove"||i==="delete")return await hu(a(),r[1]),0;throw new y(`unknown command: ai models ${i}. ai models [add|remove]`);case"providers":if(i===void 0||i==="list")return await mu(a()),0;if(i==="remove"||i==="delete")return await fu(a(),r[1]),0;throw new y(`unknown command: ai providers ${i}. ai providers [remove NAME]`);case"routes":if(i===void 0||i==="list")return await yu(a()),0;if(i==="set")return await wu(a(),r[1],{models:R(e,"models"),sort:R(e,"sort")}),0;if(i==="remove"||i==="delete")return await bu(a(),r[1]),0;throw new y(`unknown command: ai routes ${i}. ai routes [set NAME --models a,b | remove NAME]`);case"keys":if(i===void 0||i==="list")return await Su(a()),0;if(i==="create")return await ku(a(),{name:R(e,"name"),models:R(e,"models"),expiresDays:Fe(e,"expires")}),0;if(i==="revoke")return await _u(a(),r[1]),0;throw new y(`unknown command: ai keys ${i}. ai keys [create|revoke]`);case"budgets":if(i===void 0||i==="list")return await vu(a()),0;if(i==="set")return await $u(a(),r[1],{amount:R(e,"amount"),period:R(e,"period"),soft:W(e,"soft")}),0;if(i==="remove"||i==="delete")return await Pu(a(),r[1]),0;throw new y(`unknown command: ai budgets ${i}. ai budgets [set|remove]`);case"usage":return await xu(a(),{days:Fe(e,"days"),by:R(e,"by")}),0;case"test":return Eu(a(),{model:R(e,"model"),prompt:R(e,"prompt")});default:throw new y(`unknown command: ai ${n}. ai [enable|disable|models|providers|keys|budgets|usage|test]`)}}case"domains":return await Md(a(),n,r[0]),0;case"teams":return await Ec(),0;case"link":{if(W(e,"local")){let l;try{l=yl(n)}catch(h){throw new C("not-found",h instanceof Error?h.message:String(h))}let m=en(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=R(e,"ref")??n;if(!i)throw new y("link needs --ref");let u=en(process.cwd(),{ref:i});return b({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await $a(a()),0;case"psql":return qd(a(),r);case"reset-password":return await xc(a()),0;case"access":return await Vd(a(),r,{level:R(e,"level")}),0;case"push":return Bu(a(),{dir:R(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return W(e,"window")?(await Ld(a()),0):R(e,"at")?(await Dd(a(),R(e,"at"),R(e,"name")),0):Jd(a(),{file:R(e,"file"),force:!!e.flags.force});case"export":return Wd(a(),{timeoutMs:s(),out:R(e,"out"),statusOnly:!!e.flags.status});default:throw new y(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new y(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=W(e,"local")?await Kf(await ur()):null,u=i?i.dbUrl:R(e,"db-url");return Xu(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(R(e,"schema")??"public").split(",").map(l=>l.trim()).filter(Boolean),out:R(e,"out"),sql:i?.sql})}case"start":{let i=c();if(i)return ac(i)}return Bf(await ur(),{port:Fe(e,"port"),dir:R(e,"dir"),noMigrations:W(e,"no-migrations"),outOfOrder:W(e,"out-of-order")});case"stop":{let i=c();return i?ic(i):Hf(await ur())}case"status":{let i=c();return i?sc(i):Gf(await ur())}case"usage":return Vf(a(),{days:Fe(e,"days"),history:!!e.flags.history});case"mcp":return Wf({version:ve,allowDelete:!!e.flags["allow-delete"]});case"init":return a$(e);case"upgrade":return ag({check:W(e,"check")});default:throw new y(`unknown command: ${t}`)}}async function a$(e){let t=gl({flag:R(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.`),W(e,"env")?await lg(o):await $a(o),t.kind==="link"){let s=kt(o),a=en(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=R(e,"name")??i$(process.cwd());d(`Creating a project called "${n}"\u2026`),await qs({name:n,region:R(e,"region"),wait:!0,link:process.cwd(),showUrl:!W(e,"env")});let r=wr({cwd:process.cwd()});return r&&W(e,"env")&&await lg(r),0}async function lg(e){let{connection:t}=await Promise.resolve().then(()=>(fe(),tt)),n=await t(e),r=zd(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 i$(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function l$(e){if(e instanceof C)return bt(e.code,e.message,e.details),e.exitCode;if(e instanceof y)return bt("usage",e.message),st.usage;if(e instanceof Ot)return bt("not-signed-in",e.message),st["not-signed-in"];if(e instanceof Oe){let r=yt(e.status,e.serverCode);return bt(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),st[r]}let t=Sl(e);if(t)return bt("usage",t),st.usage;let n=kr(e);return bt(n,e instanceof Error?e.message:String(e)),st[n]}function cs(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function c$(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}function zi(){return Se()===xe?"":` (profile ${Se()})`}async function d$(){Ln(c$(process.argv.slice(2)));let e;try{e=ol(process.argv.slice(2))}catch(t){bt("usage",String(t instanceof Error?t.message:t)),cs(st.usage);return}Ln(e.json),Fl(e.flags.quiet===!0),kl(_r({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{ul(pl({flag:R(e,"profile")}).name)}catch(t){bt("usage",String(t instanceof Error?t.message:t)),cs(st.usage);return}try{cs(await s$(e))}catch(t){cs(l$(t))}}d$();