snoutdata 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/snoutdata.mjs +134 -134
  2. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- var sd=Object.defineProperty;var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var ss=(e,t)=>{for(var n in t)sd(e,n,{get:t[n],enumerable:!0})};function as(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]=cd(o);if(ad.has(s)){let l=a??e[++r];if(l===void 0)throw new f(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new f(`--${s} does not take a value`);if(!id.has(s))throw new f(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function cd(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function $(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function E(e,t){return e.flags[t]===!0}function be(e,t){let n=$(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new f(`--${t} must be a whole number, not "${n}"`);return r}var f,ad,id,le=C(()=>{"use strict";f=class extends Error{},ad=new Set(["name","at","region","ref","file","provider","domain","expires","project","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint","memory","concurrency","tail","registry-user","dockerfile","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level","admin","token","host","dbname","sslmode","peer-host","to","key-type","ranges","seed","nodes","column","keyspace","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms"]);id=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","clear-registry","quiet","yes","plan","status","no-wait","no-join","queue","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as ld,existsSync as ls,mkdirSync as ds,readFileSync as us,rmSync as dd,writeFileSync as ms}from"node:fs";import{homedir as Yt}from"node:os";import{dirname as dr,join as ps,parse as ud,resolve as md}from"node:path";function ur(e=Yt()){return ps(e,".snoutdata","auth.json")}function Xt(e=Yt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=ur(e);if(!ls(n))return null;try{let r=JSON.parse(us(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ve(e,t=Yt()){let n=ur(t);ds(dr(n),{recursive:!0}),ms(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),ld(n,384)}function fs(e=Yt()){dd(ur(e),{force:!0})}function gs(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function hs(e){return ps(e,".snoutdata","project.json")}function rt(e){let t=md(e),n=ud(t).root;for(;;){let r=hs(t);if(ls(r))try{let o=JSON.parse(us(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=dr(t)}}function ot(e,t){let n=hs(e);return ds(dr(n),{recursive:!0}),ms(n,`${JSON.stringify(t,null,2)}
4
- `),n}function Zt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:rt(e.cwd??process.cwd())?.ref??null}function ys(e){let t=e.environment??process.env,n=rt(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 Ie=C(()=>{"use strict"});import{existsSync as mr,readFileSync as ws}from"node:fs";import{homedir as pd}from"node:os";import{isAbsolute as fd,join as Me,resolve as st}from"node:path";function gd(e=process.env,t=process.platform,n=pd()){let r;return t==="win32"?r=e.APPDATA??Me(n,"AppData","Roaming"):t==="darwin"?r=Me(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Me(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Me(r,o,"localProjects.json"))}function Qt(e=gd()){let t=[];for(let n of e)if(mr(n))try{let r=JSON.parse(ws(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 en(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 bt(e){return mr(Me(e,"compose.yaml"))&&mr(Me(e,".env"))}function Le(e,t=null){let n=st(e);if(!bt(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=en(ws(Me(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Me(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function bs(e,t=process.cwd(),n=Qt()){if(!e){if(bt(t))return Le(t);let[i]=n;if(n.length===1&&i)return Le(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=fd(e)?e:st(t,e);if(bt(r)){let i=n.find(u=>st(u.folder)===st(r));return Le(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 Le(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function _s(e){let t=`${e.ref}_owner`;return{ref:e.ref,host:"127.0.0.1",port:e.dbPort,database:e.ref,user:t,password:e.ownerPassword,ssl:"disable",uri:`postgresql://${t}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.dbPort}/${e.ref}`,appKey:e.appKey,serverKey:e.serverKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function Ss(e,t,n=Qt()){if(t&&bt(t))try{let r=n.find(s=>st(s.folder)===st(t)),o=Le(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&bt(r.folder))return Le(r.folder,r.name);return null}var _t=C(()=>{"use strict"});function I(e,t,n){throw new k(e,t,n)}function We(e,t){return t==="no-capacity"?"no-capacity":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function ks(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 tn(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(hd.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 Oe,k,hd,oe=C(()=>{"use strict";Oe={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},k=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Oe[this.code]}};hd=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function nn(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 yd){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 at(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 vs(e){pr=e}function se(){return(pr??nn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function it(){return(pr??nn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var yd,pr,Ve=C(()=>{"use strict";yd=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];pr=null});import{createHmac as wd,randomBytes as bd,timingSafeEqual as _d}from"node:crypto";import{existsSync as Sd,readFileSync as kd}from"node:fs";import{homedir as fr,hostname as vd}from"node:os";import{join as ct}from"node:path";function xd(e,t,n){if(t==="win32"){let r=n.APPDATA??ct(fr(),"AppData","Roaming");return ct(r,e)}return t==="darwin"?ct(fr(),"Library","Application Support",e):ct(n.XDG_CONFIG_HOME??ct(fr(),".config"),e)}function $s(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>ct(xd(n,e,t),"mcp.json"))}function $d(e=process.platform,t=process.env){return $s(e,t)[0]}function Pd(e=$d()){if(!Sd(e))return null;try{let t=JSON.parse(kd(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Ed(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Rd(e,t,n){return wd("sha256",e).update(`${Ad}
2
+ var md=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var is=(e,t)=>{for(var n in t)md(e,n,{get:t[n],enumerable:!0})};function cs(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]=gd(o);if(pd.has(s)){let l=a??e[++r];if(l===void 0)throw new f(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new f(`--${s} does not take a value`);if(!fd.has(s))throw new f(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function gd(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function $(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function A(e,t){return e.flags[t]===!0}function be(e,t){let n=$(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new f(`--${t} must be a whole number, not "${n}"`);return r}var f,pd,fd,ue=j(()=>{"use strict";f=class extends Error{},pd=new Set(["name","at","region","ref","file","provider","domain","expires","project","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint","memory","concurrency","tail","registry-user","dockerfile","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level","admin","token","host","dbname","sslmode","peer-host","to","key-type","ranges","seed","nodes","column","keyspace","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms"]);fd=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","clear-registry","quiet","yes","plan","status","no-wait","no-join","queue","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as hd,existsSync as us,mkdirSync as ms,readFileSync as ps,rmSync as yd,writeFileSync as fs}from"node:fs";import{homedir as Xt}from"node:os";import{dirname as mr,join as gs,parse as wd,resolve as bd}from"node:path";function pr(e=Xt()){return gs(e,".snoutdata","auth.json")}function Zt(e=Xt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=pr(e);if(!us(n))return null;try{let r=JSON.parse(ps(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function xe(e,t=Xt()){let n=pr(t);ms(mr(n),{recursive:!0}),fs(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),hd(n,384)}function hs(e=Xt()){yd(pr(e),{force:!0})}function ys(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function ws(e){return gs(e,".snoutdata","project.json")}function ot(e){let t=bd(e),n=wd(t).root;for(;;){let r=ws(t);if(us(r))try{let o=JSON.parse(ps(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=mr(t)}}function st(e,t){let n=ws(e);return ms(mr(n),{recursive:!0}),fs(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function Qt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:ot(e.cwd??process.cwd())?.ref??null}function bs(e){let t=e.environment??process.env,n=ot(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Me=j(()=>{"use strict"});import{existsSync as fr,readFileSync as _s}from"node:fs";import{homedir as _d}from"node:os";import{isAbsolute as Sd,join as Le,resolve as at}from"node:path";function kd(e=process.env,t=process.platform,n=_d()){let r;return t==="win32"?r=e.APPDATA??Le(n,"AppData","Roaming"):t==="darwin"?r=Le(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Le(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Le(r,o,"localProjects.json"))}function en(e=kd()){let t=[];for(let n of e)if(fr(n))try{let r=JSON.parse(_s(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 tn(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function _t(e){return fr(Le(e,"compose.yaml"))&&fr(Le(e,".env"))}function Ue(e,t=null){let n=at(e);if(!_t(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=tn(_s(Le(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Le(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function Ss(e,t=process.cwd(),n=en()){if(!e){if(_t(t))return Ue(t);let[i]=n;if(n.length===1&&i)return Ue(i.folder,i.name);throw n.length===0?new Error("there is no local project to link: set one up in Studio (Projects, Local), or pass the folder of a stack you made with snout-stack"):new Error(`there are ${n.length} local projects; name one: ${n.map(u=>u.name??u.folder).join(", ")}`)}let r=Sd(e)?e:at(t,e);if(_t(r)){let i=n.find(u=>at(u.folder)===at(r));return Ue(r,i?.name??null)}let o=e.toLowerCase(),s=n.filter(i=>i.name?.toLowerCase()===o||i.ref===e),[a]=s;if(s.length===1&&a)return Ue(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function ks(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 vs(e,t,n=en()){if(t&&_t(t))try{let r=n.find(s=>at(s.folder)===at(t)),o=Ue(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&_t(r.folder))return Ue(r.folder,r.name);return null}var St=j(()=>{"use strict"});function I(e,t,n){throw new v(e,t,n)}function Ve(e,t){return t==="no-capacity"?"no-capacity":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function xs(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 nn(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(vd.has(o))return"network";if(o==="ABORT_ERR"||o==="ETIMEDOUT")return"timeout"}return e instanceof Error&&/fetch failed|network|socket hang up/i.test(e.message)?"network":"failed"}var Ne,v,vd,se=j(()=>{"use strict";Ne={ok:0,failed:1,usage:2,"not-signed-in":3,"not-ready":4,forbidden:5,"not-found":6,conflict:7,quota:8,network:9,timeout:10,outdated:11,"no-capacity":12,"tool-missing":127,server:1},v=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ne[this.code]}};vd=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function rn(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 xd){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function it(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function $s(e){gr=e}function ae(){return(gr??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function ct(){return(gr??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var xd,gr,Ye=j(()=>{"use strict";xd=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];gr=null});import{createHmac as $d,randomBytes as Pd,timingSafeEqual as Ed}from"node:crypto";import{existsSync as Ad,readFileSync as Rd}from"node:fs";import{homedir as hr,hostname as Td}from"node:os";import{join as lt}from"node:path";function Od(e,t,n){if(t==="win32"){let r=n.APPDATA??lt(hr(),"AppData","Roaming");return lt(r,e)}return t==="darwin"?lt(hr(),"Library","Application Support",e):lt(n.XDG_CONFIG_HOME??lt(hr(),".config"),e)}function Es(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>lt(Od(n,e,t),"mcp.json"))}function Nd(e=process.platform,t=process.env){return Es(e,t)[0]}function jd(e=Nd()){if(!Ad(e))return null;try{let t=JSON.parse(Rd(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Cd(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Dd(e,t,n){return $d("sha256",e).update(`${Id}
5
5
  ${t}
6
- ${n}`).digest("base64url")}function Td(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&_d(n,r)}async function Es(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=bd(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(Ps)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&Td(o.proof,Rd(e.token,t,n))}catch{return!1}}async function hr(e,t,n,r=Es){if(!await r(e).catch(()=>!1))throw new rn(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function on(e,t=Es){if(e!==void 0)return xs(e,t);let n="no-config";for(let r of $s()){let o=Pd(r);if(!o)continue;let s=await xs(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 xs(e,t){let n=Ed(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await hr(r,"/cli/whoami",{signal:AbortSignal.timeout(Ps)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof rn?{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 As(e,t){let n=await hr(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 sn(){return vd()}async function gr(e,t,n,r){let o=await hr(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 Rs(e){return await gr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await gr(e,"tools/list",{},3e3)).tools??[]}async function Ts(e,t,n){return gr(e,"tools/call",{name:t,arguments:n},12e4)}var Ps,Ad,rn,an=C(()=>{"use strict";Ps=1500,Ad="snoutdata-studio-proof-v1";rn=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 yr(e){try{return new URL(e).host}catch{return e}}function ln(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function Ue(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function jd(e){return cn.has(yr(e))}function Cd(e,t,n){return t===void 0?e:{...e,[n?Nd:Od]:t}}async function xe(e,t,n={},r=fetch){let o=yr(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:Cd(s,n.key,u),body:n.body})}if(cn.has(o))return a(!0);let l=await a(!1);if(l.status!==404)return l;let i=await a(!0);return i.status===404?l:(cn.add(o),i)}async function Ns(e,t,n,r=fetch){if(!cn.has(yr(e)))try{await xe(e,Ue("/health","/health"),{key:n},r)}catch{}return`${e}${jd(e)?t.oldPath:t.path}`}var Od,Nd,cn,dn=C(()=>{"use strict";Od="Snout-Key",Nd="apikey",cn=new Set});import{basename as Dd}from"node:path";function mn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Dd(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 Id(e=mn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?Ne:St}}function wr(e={}){let t=e.kind??mn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?Ne:`${St} (or ${Ne})`;return`snoutdata ${Q} is installed and ${r}. Run \`${Id(t,n)}\`, or install again: ${o}`}function js(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 Q,un,St,Ne,kt=C(()=>{"use strict";Q="0.17.0",un=`snoutdata-cli/${Q}`,St="curl -fsSL https://snoutdata.com/install.sh | sh",Ne="npm install -g snoutdata@latest"});async function Cs(e,t){let n=await xe($e,ln(e),{method:"POST",key:Pe,headers:{"Content-Type":"application/json","x-client-info":un},body:JSON.stringify(t)});gn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw hn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function pn(e={}){let{status:t,body:n}=await Cs("cloud-device-start",{client:e});return t===429&&I("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&I("not-found","this SnoutData does not have the pairing flow (cloud-device-start is not deployed). Use `snoutdata login` to sign in with a browser, or set SNOUTDATA_ACCESS_TOKEN."),(t!==200||!n.code||!n.secret)&&I("server",n.error??`could not start a pairing (${t})`),n}async function fn(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&I("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(a=>setTimeout(a,n));let{status:o,body:s}=await Cs("cloud-device-poll",{secret:e});switch(o===404&&I("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&I("usage",s.error??"that is not a pairing secret"),o!==200&&I("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":I("forbidden",s.error??"that pairing was refused in the browser");case"used":I("conflict",s.error??"that pairing has already been completed");case"expired":I("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var br=C(()=>{"use strict";Y();dn();oe();kt()});function xt(e){vt=e}function Is(e){Ds=e}function Ms(){return vt}function y(e){return Ls?`\x1B[2m${e}\x1B[0m`:e}function S(e){return Ls?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(vt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
- `);return}t()}function d(e){!vt&&!Ds&&process.stderr.write(`${e}
8
- `)}function Fe(e,t,n){process.stderr.write(`${t}
9
- `),vt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
6
+ ${n}`).digest("base64url")}function Md(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&Ed(n,r)}async function Rs(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=Pd(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(As)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&Md(o.proof,Dd(e.token,t,n))}catch{return!1}}async function wr(e,t,n,r=Rs){if(!await r(e).catch(()=>!1))throw new on(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function sn(e,t=Rs){if(e!==void 0)return Ps(e,t);let n="no-config";for(let r of Es()){let o=jd(r);if(!o)continue;let s=await Ps(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 Ps(e,t){let n=Cd(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await wr(r,"/cli/whoami",{signal:AbortSignal.timeout(As)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof on?{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 Ts(e,t){let n=await wr(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 an(){return Td()}async function yr(e,t,n,r){let o=await wr(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 Os(e){return await yr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await yr(e,"tools/list",{},3e3)).tools??[]}async function Ns(e,t,n){return yr(e,"tools/call",{name:t,arguments:n},12e4)}var As,Id,on,cn=j(()=>{"use strict";As=1500,Id="snoutdata-studio-proof-v1";on=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 br(e){try{return new URL(e).host}catch{return e}}function dn(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function Fe(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function Fd(e){return ln.has(br(e))}function Bd(e,t,n){return t===void 0?e:{...e,[n?Ud:Ld]:t}}async function $e(e,t,n={},r=fetch){let o=br(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:Bd(s,n.key,u),body:n.body})}if(ln.has(o))return a(!0);let l=await a(!1);if(l.status!==404)return l;let i=await a(!0);return i.status===404?l:(ln.add(o),i)}async function Cs(e,t,n,r=fetch){if(!ln.has(br(e)))try{await $e(e,Fe("/health","/health"),{key:n},r)}catch{}return`${e}${Fd(e)?t.oldPath:t.path}`}var Ld,Ud,ln,un=j(()=>{"use strict";Ld="Snout-Key",Ud="apikey",ln=new Set});import{basename as Hd}from"node:path";function pn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Hd(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 Gd(e=pn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?je:kt}}function _r(e={}){let t=e.kind??pn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?je:`${kt} (or ${je})`;return`snoutdata ${ne} is installed and ${r}. Run \`${Gd(t,n)}\`, or install again: ${o}`}function Is(e,t){if(e==="dev"||t==="dev")return!1;let n=e.split("-")[0].split(".").map(Number),r=t.split("-")[0].split(".").map(Number);for(let o=0;o<Math.max(n.length,r.length);o+=1){let s=n[o]??0,a=r[o]??0;if(Number.isNaN(s)||Number.isNaN(a))return!1;if(s!==a)return s<a}return!1}var ne,mn,kt,je,vt=j(()=>{"use strict";ne="0.17.2",mn=`snoutdata-cli/${ne}`,kt="curl -fsSL https://snoutdata.com/install.sh | sh",je="npm install -g snoutdata@latest"});async function Ds(e,t){let n=await $e(Pe,dn(e),{method:"POST",key:Ee,headers:{"Content-Type":"application/json","x-client-info":mn},body:JSON.stringify(t)});hn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw yn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function fn(e={}){let{status:t,body:n}=await Ds("cloud-device-start",{client:e});return t===429&&I("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&I("not-found","this SnoutData does not have the pairing flow (cloud-device-start is not deployed). Use `snoutdata login` to sign in with a browser, or set SNOUTDATA_ACCESS_TOKEN."),(t!==200||!n.code||!n.secret)&&I("server",n.error??`could not start a pairing (${t})`),n}async function gn(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&I("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(a=>setTimeout(a,n));let{status:o,body:s}=await Ds("cloud-device-poll",{secret:e});switch(o===404&&I("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&I("usage",s.error??"that is not a pairing secret"),o!==200&&I("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":I("forbidden",s.error??"that pairing was refused in the browser");case"used":I("conflict",s.error??"that pairing has already been completed");case"expired":I("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var Sr=j(()=>{"use strict";Y();un();se();vt()});function $t(e){xt=e}function Ls(e){Ms=e}function wn(){return xt}function w(e){return Us?`\x1B[2m${e}\x1B[0m`:e}function k(e){return Us?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(xt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
+ `);return}t()}function d(e){!xt&&!Ms&&process.stderr.write(`${e}
8
+ `)}function Be(e,t,n){process.stderr.write(`${t}
9
+ `),xt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
10
  `)}function X(e){process.stderr.write(`${e}
11
- `)}function L(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 je(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var vt,Ds,Ls,W=C(()=>{"use strict";vt=!1,Ds=!1;Ls=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Bs,randomBytes as _r}from"node:crypto";import{createServer as Ld}from"node:http";import{createInterface as Ud}from"node:readline/promises";import{spawn as Fd}from"node:child_process";function yn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Us(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.":ru(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
11
+ `)}function M(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
12
+ `)}function Ce(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var xt,Ms,Us,W=j(()=>{"use strict";xt=!1,Ms=!1;Us=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Hs,randomBytes as kr}from"node:crypto";import{createServer as Kd}from"node:http";import{createInterface as Jd}from"node:readline/promises";import{spawn as zd}from"node:child_process";function bn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Fs(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.":du(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
13
13
  <meta name="viewport" content="width=device-width,initial-scale=1"></head>
14
14
  <body style="font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;background:#050403;color:#e9e4dc;display:grid;place-items:center;min-height:100vh;margin:0">
15
15
  <div style="text-align:center;max-width:460px;padding:40px 24px">
@@ -21,24 +21,24 @@ ${n}`).digest("base64url")}function Td(e,t){let n=Buffer.from(e),r=Buffer.from(t
21
21
  <p style="color:#8f887e;margin:0;font-size:15px;line-height:1.5">${o}</p>
22
22
  ${e==="ok"?"":'<p style="color:#8f887e;margin:14px 0 0;font-size:14px">Run <code style="background:#1b1917;border:1px solid #262320;border-radius:6px;padding:2px 6px;font-size:13px">snoutdata login</code> to try again.</p>'}
23
23
  </div>
24
- </body></html>`}function Sr(){let e=yn(_r(32)),t=yn(Bs("sha256").update(e).digest());return{verifier:e,challenge:t}}function Bd(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 Hd(e){let{command:t,args:n,verbatim:r}=Bd(process.platform,e),o=Fd(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function kr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Kd(){return"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"}function Jd(e){let t=e??yn(_r(32));return{raw:t,hashed:Bs("sha256").update(t).digest("hex")}}function zd(e){return`${Gd}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:kr(),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 Wd(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 vr(e){return new Promise((t,n)=>{let r="",o=Ld((a,l)=>{let i=Wd(a.url??"/",e.state);if(i.kind==="ignore"){l.writeHead(404).end();return}l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(i.kind==="ok"?Us("ok"):Us("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."),Hd(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 Hs(e,t){let n=await xe($e,Ue("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Pe,headers:{"Content-Type":"application/json"},body:JSON.stringify({auth_code:e,code_verifier:t})});if(!n.ok)throw new Error(`could not exchange the code: ${(await n.text()).slice(0,200)}`);return Gs(await n.json())}async function Vd(e,t,n){let r=await fetch(qd,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:kr(),client_secret:Kd(),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 Yd(e){if(!kr())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}=Sr(),{raw:r,hashed:o}=Jd(),s=yn(_r(16)),{code:a,redirectUri:l}=await vr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>zd({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Vd(a,t,l),u=await xe($e,Ue("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Pe,headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:i,nonce:r})});if(!u.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await u.text()).slice(0,200)}`);return Gs(await u.json())}function Gs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ve(n),n}function Fs(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 Xd(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 Zd(e,t,n){let r=Xd(t);return e===404||/no sso provider/i.test(r)?new k("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 k("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new k(We(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Qd(e){let t=await xe($e,Ue("/sso/start","/sso"),{method:"POST",key:Pe,headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:e.domain,redirect_to:e.redirectTo,skip_http_redirect:!0,code_challenge:e.challenge,code_challenge_method:"s256"})}),n=await t.text();if(!t.ok)throw Zd(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new k("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 eu(e){if(e!==void 0){let r=Fs(e);if(!r)throw new f(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!se()){let r=it();throw new f(`login --sso needs --domain <your work email or company domain>${r?`, because ${at(r)}`:""}`)}let t=Ud({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new f("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=Fs(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 f("no domain to sign in with: pass --domain <your work email or company domain>")}async function tu(e){let t=await eu(e.domain),{verifier:n,challenge:r}=Sr();d(`Signing in with the identity provider for ${S(t)}.`);let{code:o}=await vr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Qd({domain:t,redirectTo:s,challenge:r})});return Hs(o,n)}async function wn(e={}){return xr(e.email,await nu(e))}function xr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ve(n),t.email&&!$r(t.email,e)&&X(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function $r(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function nu(e){if(e.sso)return tu({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return Yd(e);let{verifier:n,challenge:r}=Sr(),{code:o}=await vr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Ns($e,Ue("/provider/start","/authorize"),Pe));return a.searchParams.set("provider",t),a.searchParams.set("redirect_to",s),a.searchParams.set("code_challenge",r),a.searchParams.set("code_challenge_method","s256"),a.searchParams.set("prompt","select_account"),a.toString()}});return Hs(o,n)}function ru(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Gd,qd,bn=C(()=>{"use strict";Y();dn();le();Ie();oe();Ve();W();Gd="https://accounts.google.com/o/oauth2/v2/auth",qd="https://oauth2.googleapis.com/token"});import{createInterface as ou}from"node:readline/promises";async function Pr(e){let t=ou({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 su(){let e=await on();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(y("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 ${S(t)}.`),d(y("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(y("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Pr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await As(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 ve(r),d(`Signed in with "${n.name}".`),r}async function au(){if(!await Pr("Sign in with a browser?"))return null;try{let e=await wn({});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 iu(){if(!await Pr("Sign in by typing a code into a browser on another machine?"))return null;let e=await pn({name:sn(),platform:process.platform});d(""),d(` Open ${S(e.verifyUrl)}`),d(` Type ${S(e.code)}`),d(""),d(y("The code is typed rather than in the link on purpose: it is what proves the person")),d(y("at the browser is the person at this terminal. Waiting\u2026"));let t=await fn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ve(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function qs(){if(!se())return null;let e=await su();if(e)return e;let t=await au();return t||iu()}var Ks=C(()=>{"use strict";Ie();an();br();Ve();bn();W()});var Ee={};ss(Ee,{ACCOUNTS_URL:()=>$e,APP_KEY:()=>Pe,ApiError:()=>ge,NotSignedIn:()=>Be,call:()=>b,connection:()=>ue,exportStatus:()=>lt,isSettled:()=>zs,listProjects:()=>he,listTeams:()=>Sn,localStack:()=>Ce,noticeFrom:()=>gn,outdated:()=>hn,requestExport:()=>$t,waitForReady:()=>Et,waitForSettled:()=>Pt,whoami:()=>Ye,withKeyNames:()=>_n});function cu(){let e=it();return e?`not signed in, and ${at(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 lu(){let e=Xt();if(e||(e=await qs()),!e)throw new Be;if(!gs(e)||!e.refreshToken)return e.accessToken;let t=await xe($e,Ue("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Pe,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Be;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ve(r),r.accessToken}async function b(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Ce(r);if(u)throw new k("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 lu(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await xe($e,ln(e,s),{method:n,key:Pe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":un},body:n==="GET"?void 0:JSON.stringify(t)});gn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;if(a.status===401)throw new Be(u);if(a.status===410)throw hn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new ge(a.status,u,c,i)}return i}function gn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Js||(Js=!0,X(`${t} ${wr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function hn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new k("outdated",`${t.replace(/\s+$/,"")} ${wr({minimum:n})}`,{status:410,installed:Q,minimum:n??null})}async function Ye(){return b("cloud-whoami")}async function he(){return b("cloud-project-list")}async function ue(e){let t=Ce(e);return t?_s(t):_n(await b("cloud-project-connection",{ref:e}))}function _n(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Ce(e){return Ss(e,rt(process.cwd())?.local??null)}async function Sn(){return b("cloud-team-list")}async function $t(e){return b("cloud-project-export",{ref:e})}async function lt(e){return b("cloud-project-export",{ref:e},"GET")}async function Pt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await he(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),zs(t,a))return s;if(Date.now()>=r)throw new ge(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function zs(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Et(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await he(),o=r.find(s=>s.ref===e);if(!o)throw new ge(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ge(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var $e,Pe,ge,Be,Js,Y=C(()=>{"use strict";Ie();_t();oe();Ve();Ks();W();kt();dn();$e=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Pe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",ge=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Be=class extends Error{constructor(t){super(t??cu())}};Js=!1});function _o(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Xi(e,t,n){let r="";for(;r.length<n;)r+=_o(e,t);return r}var bo,Yi,Zi=C(()=>{"use strict";bo="0123456789abcdefghjkmnpqrstvwxyz",Yi="abcdefghjkmnpqrstvwxyz"});function tc(e){return _o(e,Qi)+Xi(e,bo,ec-1)}function ff(e){return pf.test(e)}function re(e){if(!ff(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function B(e,t=gf){return re(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Qi,ec,pf,gf,_e=C(()=>{"use strict";Zi();Qi=Yi,ec=13,pf=new RegExp(`^[${Qi}][${bo}]{${ec-1}}$`);gf="db.snoutdata.com"});var nc=C(()=>{"use strict";_e()});function oc(e,t){let n=rc(e,7),r=rc(t,24);return Math.max(2,Math.ceil(n*24/r))}function rc(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var So=C(()=>{"use strict"});function ko(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var vo=C(()=>{"use strict"});var sc=C(()=>{"use strict"});function ic(e){let n=(e.trim().split(`
26
- `)[0]??"").split("|");if(n.length!==4||n[0]!=="t"&&n[0]!=="f")return null;let r=Number(n[1]);return!Number.isFinite(r)||r<0?null:{inRecovery:n[0]==="t",checksumFailures:r,syncNames:n[2],streaming:n[3]===""?[]:n[3].split(",")}}function cc(e){if(e!==""&&!/^"[a-z0-9-]+"$/.test(e))throw new Error(`not a synchronous_standby_names value: ${e}`);return[e===""?"alter system reset synchronous_standby_names":`alter system set synchronous_standby_names = '${e}'`,"select pg_reload_conf()"]}var ac,xo=C(()=>{"use strict";_e();ac="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'), '')"});var Yb,lc=C(()=>{"use strict";_e();Yb=7*24});var Zb,dc=C(()=>{"use strict";Zb=7*24*60*60*1e3});var hf,uc=C(()=>{"use strict";dc();hf=60*60*1e3});var mc=C(()=>{"use strict"});var $o,s_,pc=C(()=>{"use strict";$o="snoutdata_realtime",s_=`do $snoutpod$
24
+ </body></html>`}function vr(){let e=bn(kr(32)),t=bn(Hs("sha256").update(e).digest());return{verifier:e,challenge:t}}function Wd(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 Vd(e){let{command:t,args:n,verbatim:r}=Wd(process.platform,e),o=zd(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function xr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Zd(){return"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"}function Qd(e){let t=e??bn(kr(32));return{raw:t,hashed:Hs("sha256").update(t).digest("hex")}}function eu(e){return`${Yd}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:xr(),redirect_uri:e.redirectUri,response_type:"code",scope:"openid email profile",code_challenge:e.challenge,code_challenge_method:"S256",nonce:e.hashedNonce,state:e.state,prompt:"select_account"}).toString()}`}function tu(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 $r(e){return new Promise((t,n)=>{let r="",o=Kd((a,l)=>{let i=tu(a.url??"/",e.state);if(i.kind==="ignore"){l.writeHead(404).end();return}l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(i.kind==="ok"?Fs("ok"):Fs("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."),Vd(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 Gs(e,t){let n=await $e(Pe,Fe("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Ee,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 qs(await n.json())}async function nu(e,t,n){let r=await fetch(Xd,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:xr(),client_secret:Zd(),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 ru(e){if(!xr())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}=vr(),{raw:r,hashed:o}=Qd(),s=bn(kr(16)),{code:a,redirectUri:l}=await $r({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>eu({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await nu(a,t,l),u=await $e(Pe,Fe("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Ee,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 qs(await u.json())}function qs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return xe(n),n}function Bs(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 ou(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 su(e,t,n){let r=ou(t);return e===404||/no sso provider/i.test(r)?new v("not-found",`No single sign-on is set up for ${n}. Check the address, or ask whoever administers your team to connect your identity provider.`,{domain:n}):/saml.*(disabled|not enabled)/i.test(r)?new v("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new v(Ve(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function au(e){let t=await $e(Pe,Fe("/sso/start","/sso"),{method:"POST",key:Ee,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 su(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new v("server",`Single sign-on for ${e.domain} was accepted but came back without somewhere to send the browser.`,{domain:e.domain});return r}async function iu(e){if(e!==void 0){let r=Bs(e);if(!r)throw new f(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!ae()){let r=ct();throw new f(`login --sso needs --domain <your work email or company domain>${r?`, because ${it(r)}`:""}`)}let t=Jd({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new f("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=Bs(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 f("no domain to sign in with: pass --domain <your work email or company domain>")}async function cu(e){let t=await iu(e.domain),{verifier:n,challenge:r}=vr();d(`Signing in with the identity provider for ${k(t)}.`);let{code:o}=await $r({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>au({domain:t,redirectTo:s,challenge:r})});return Gs(o,n)}async function _n(e={}){return Pr(e.email,await lu(e))}function Pr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return xe(n),t.email&&!Er(t.email,e)&&X(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Er(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function lu(e){if(e.sso)return cu({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return ru(e);let{verifier:n,challenge:r}=vr(),{code:o}=await $r({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Cs(Pe,Fe("/provider/start","/authorize"),Ee));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 Gs(o,n)}function du(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Yd,Xd,Sn=j(()=>{"use strict";Y();un();ue();Me();se();Ye();W();Yd="https://accounts.google.com/o/oauth2/v2/auth",Xd="https://oauth2.googleapis.com/token"});import{createInterface as uu}from"node:readline/promises";async function Ar(e){let t=uu({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 mu(){let e=await sn();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(w("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(d(""),d(`SnoutData Studio is open here, signed in as ${k(t)}.`),d(w("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(w("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Ar("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await Ts(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return d(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return xe(r),d(`Signed in with "${n.name}".`),r}async function pu(){if(!await Ar("Sign in with a browser?"))return null;try{let e=await _n({});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 fu(){if(!await Ar("Sign in by typing a code into a browser on another machine?"))return null;let e=await fn({name:an(),platform:process.platform});d(""),d(` Open ${k(e.verifyUrl)}`),d(` Type ${k(e.code)}`),d(""),d(w("The code is typed rather than in the link on purpose: it is what proves the person")),d(w("at the browser is the person at this terminal. Waiting\u2026"));let t=await gn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return xe(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Ks(){if(!ae())return null;let e=await mu();if(e)return e;let t=await pu();return t||fu()}var Js=j(()=>{"use strict";Me();cn();Sr();Ye();Sn();W()});var Ae={};is(Ae,{ACCOUNTS_URL:()=>Pe,APP_KEY:()=>Ee,ApiError:()=>ge,NotSignedIn:()=>He,call:()=>b,connection:()=>pe,exportStatus:()=>dt,isSettled:()=>Ws,listProjects:()=>he,listTeams:()=>vn,localStack:()=>Ie,noticeFrom:()=>hn,outdated:()=>yn,requestExport:()=>Pt,waitForReady:()=>At,waitForSettled:()=>Et,whoami:()=>Xe,withKeyNames:()=>kn});function gu(){let e=ct();return e?`not signed in, and ${it(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function hu(){let e=Zt();if(e||(e=await Ks()),!e)throw new He;if(!ys(e)||!e.refreshToken)return e.accessToken;let t=await $e(Pe,Fe("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Ee,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new He;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return xe(r),r.accessToken}async function b(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Ie(r);if(u)throw new v("usage",`${r} is a local project (${u.folder}), and this command is about SnoutData Cloud. Locally: db url, db psql, db push, gen types, keys, status, start, stop, functions, secrets.`,{ref:r,local:u.folder})}let o=await hu(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await $e(Pe,dn(e,s),{method:n,key:Ee,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":mn},body:n==="GET"?void 0:JSON.stringify(t)});hn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;if(a.status===401)throw new He(u);if(a.status===410)throw yn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new ge(a.status,u,c,i)}return i}function hn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||zs||(zs=!0,X(`${t} ${_r({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function yn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new v("outdated",`${t.replace(/\s+$/,"")} ${_r({minimum:n})}`,{status:410,installed:ne,minimum:n??null})}async function Xe(){return b("cloud-whoami")}async function he(){return b("cloud-project-list")}async function pe(e){let t=Ie(e);return t?ks(t):kn(await b("cloud-project-connection",{ref:e}))}function kn(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Ie(e){return vs(e,ot(process.cwd())?.local??null)}async function vn(){return b("cloud-team-list")}async function Pt(e){return b("cloud-project-export",{ref:e})}async function dt(e){return b("cloud-project-export",{ref:e},"GET")}async function Et(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await he(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ws(t,a))return s;if(Date.now()>=r)throw new ge(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function Ws(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function At(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await he(),o=r.find(s=>s.ref===e);if(!o)throw new ge(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ge(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var Pe,Ee,ge,He,zs,Y=j(()=>{"use strict";Me();St();se();Ye();Js();W();vt();un();Pe=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Ee=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",ge=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},He=class extends Error{constructor(t){super(t??gu())}};zs=!1});function ko(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Qi(e,t,n){let r="";for(;r.length<n;)r+=ko(e,t);return r}var So,Zi,ec=j(()=>{"use strict";So="0123456789abcdefghjkmnpqrstvwxyz",Zi="abcdefghjkmnpqrstvwxyz"});function rc(e){return ko(e,tc)+Qi(e,So,nc-1)}function _f(e){return bf.test(e)}function re(e){if(!_f(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function U(e,t=Sf){return re(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var tc,nc,bf,Sf,_e=j(()=>{"use strict";ec();tc=Zi,nc=13,bf=new RegExp(`^[${tc}][${So}]{${nc-1}}$`);Sf="db.snoutdata.com"});var oc=j(()=>{"use strict";_e()});function ac(e,t){let n=sc(e,7),r=sc(t,24);return Math.max(2,Math.ceil(n*24/r))}function sc(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var vo=j(()=>{"use strict"});function xo(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var $o=j(()=>{"use strict"});var ic=j(()=>{"use strict"});function lc(e){let n=(e.trim().split(`
26
+ `)[0]??"").split("|");if(n.length!==4||n[0]!=="t"&&n[0]!=="f")return null;let r=Number(n[1]);return!Number.isFinite(r)||r<0?null:{inRecovery:n[0]==="t",checksumFailures:r,syncNames:n[2],streaming:n[3]===""?[]:n[3].split(",")}}function dc(e){if(e!==""&&!/^"[a-z0-9-]+"$/.test(e))throw new Error(`not a synchronous_standby_names value: ${e}`);return[e===""?"alter system reset synchronous_standby_names":`alter system set synchronous_standby_names = '${e}'`,"select pg_reload_conf()"]}var cc,Po=j(()=>{"use strict";_e();cc="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'), '')"});var c_,uc=j(()=>{"use strict";_e();c_=7*24});var d_,mc=j(()=>{"use strict";d_=7*24*60*60*1e3});var kf,pc=j(()=>{"use strict";mc();kf=60*60*1e3});var fc=j(()=>{"use strict"});var Eo,y_,gc=j(()=>{"use strict";Eo="snoutdata_realtime",y_=`do $snoutpod$
27
27
  begin
28
- if exists (select 1 from pg_publication where pubname = '${$o}') then
28
+ if exists (select 1 from pg_publication where pubname = '${Eo}') then
29
29
  return;
30
30
  end if;
31
- create publication ${$o};
32
- execute format('alter publication ${$o} owner to %I',
31
+ create publication ${Eo};
32
+ execute format('alter publication ${Eo} owner to %I',
33
33
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
34
34
  end
35
35
  $snoutpod$;
36
- `});var i_,c_,fc=C(()=>{"use strict";i_=2*1024*1024,c_=64*1024});var gc=C(()=>{"use strict"});function Po(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function qn(e){let t=["network","create"],n=e.allow,r=e.deny;if(n!=null&&r!=null)throw new Error("a network is policed by an allow-list or by a deny-list, not by both");if(r!=null)return wf(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 wf(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(Po("the subnet",n,hc),Po("the sink",r,yf),!wc(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!wc(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",`${Po("a denied prefix",s,hc)},${r}`);return o.push(e.name),o}function yc(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function wc(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(yc(e)&s)>>>0===(yc(n??"")&s)>>>0}function bc(e){return e?.internal===!0}var hc,yf,Eo=C(()=>{"use strict";hc=/^((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])$/,yf=/^((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 bf,Kn=C(()=>{"use strict";bf=7*24*60*60});var _c=C(()=>{"use strict";_e();fc();gc();Eo();Kn()});var Sc=C(()=>{"use strict"});import{createHash as Sf}from"node:crypto";function Jn(e){return`${B(e).pod}-lepis-tls`}function vf(e){return Sf("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function $c(e,t){let n={...e},r=n[Ao],o=n[Ro];delete n[Ao],delete n[Ro];for(let[s,a,l]of[[Ao,r,t],[Ro,o,vf(t)]])if(a!==void 0){if(!kf.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var Ft,Ao,Ro,kc,vc,To,xc,kf,Oo=C(()=>{"use strict";_e();Sc();Ft="lepis",Ao="SNOUTPOD_ADMIN_PASSWORD_ENV",Ro="SNOUTPOD_ADMIN_TOKEN_ENV",kc=7433,vc=1e3;To="/tls",xc=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
37
- `),kf=/^[A-Z][A-Z0-9_]{0,63}$/});var M_,L_,Pc=C(()=>{"use strict";_e();Kn();M_=6*60*60,L_=5*60});var Ec=C(()=>{"use strict"});var Ac=C(()=>{"use strict"});var H_,Rc=C(()=>{"use strict";H_=2*1024**3});var Tc=C(()=>{"use strict";Ac();Rc()});function Nc(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"||!$f.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!xf.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 jc(e,t){return`${e}.nspname !~ '^pg_'
38
- and ${e}.nspname <> all (array[${Oc.map(n=>`'${n}'`).join(", ")}]::text[])
36
+ `});var b_,__,hc=j(()=>{"use strict";b_=2*1024*1024,__=64*1024});var yc=j(()=>{"use strict"});function Ao(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Jn(e){let t=["network","create"],n=e.allow,r=e.deny;if(n!=null&&r!=null)throw new Error("a network is policed by an allow-list or by a deny-list, not by both");if(r!=null)return xf(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 xf(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(Ao("the subnet",n,wc),Ao("the sink",r,vf),!_c(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!_c(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",`${Ao("a denied prefix",s,wc)},${r}`);return o.push(e.name),o}function bc(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function _c(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(bc(e)&s)>>>0===(bc(n??"")&s)>>>0}function kc(e){return e?.internal===!0}var Sc,wc,vf,Ro=j(()=>{"use strict";Sc=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],wc=/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\/(3[0-2]|[12]?[0-9])$/,vf=/^((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 $f,zn=j(()=>{"use strict";$f=7*24*60*60});function Rf(e,t){let[n,r]=e.split("/");if(r!=="24"||!n?.endsWith(".0")||!t.startsWith(n.slice(0,-1)))throw new Error(`${t} is not an address of the /24 ${e}`);let o=n.slice(0,-1),s=Number(t.slice(o.length)),a=[];for(let l=32;l>=25;l--){let i=2**(32-l);a.push(`${o}${(s^i)&~(i-1)&255}/${l}`)}return a}var Ef,Af,M_,vc=j(()=>{"use strict";_e();hc();yc();Ro();zn();Ef="10.93.0.0/24",Af="10.93.0.1",M_=[...Sc,"100.64.0.0/10",...Rf(Ef,Af)]});var xc=j(()=>{"use strict"});import{createHash as Tf}from"node:crypto";function Wn(e){return`${U(e).pod}-lepis-tls`}function Nf(e){return Tf("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Ac(e,t){let n={...e},r=n[To],o=n[Oo];delete n[To],delete n[Oo];for(let[s,a,l]of[[To,r,t],[Oo,o,Nf(t)]])if(a!==void 0){if(!Of.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var Bt,To,Oo,$c,Pc,No,Ec,Of,jo=j(()=>{"use strict";_e();xc();Bt="lepis",To="SNOUTPOD_ADMIN_PASSWORD_ENV",Oo="SNOUTPOD_ADMIN_TOKEN_ENV",$c=7433,Pc=1e3;No="/tls",Ec=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
37
+ `),Of=/^[A-Z][A-Z0-9_]{0,63}$/});var V_,Y_,Rc=j(()=>{"use strict";_e();zn();V_=6*60*60,Y_=5*60});var Tc=j(()=>{"use strict"});var Oc=j(()=>{"use strict"});var eS,Nc=j(()=>{"use strict";eS=2*1024**3});var jc=j(()=>{"use strict";Oc();Nc()});function Ic(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"||!Cf.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!jf.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 Dc(e,t){return`${e}.nspname !~ '^pg_'
38
+ and ${e}.nspname <> all (array[${Cc.map(n=>`'${n}'`).join(", ")}]::text[])
39
39
  and not ${t}.rolsuper
40
- and ${t}.rolname !~ '${Pf.source}'`}function Dc(e){let t=[];for(let n of e.split(`
41
- `)){let r=/^(created-group|created|applied|dropped-group|dropped|refused|ignored) (\S+)$/.exec(n.trim());r&&t.push({action:r[1],role:r[2]})}return t}function Ic(e,t){return re(e),JSON.stringify(Nc(t).map(n=>({role:n.role,level:n.level})))}var xf,ft,$f,J,Oc,Pf,Wn,Ef,zn,Af,Cc,No=C(()=>{"use strict";_e();xf=["full","read"],ft="snout_oauth",$f=/^oauth_[0-9a-f]{32}$/,J="snout_oauth_read",Oc=["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"],Pf=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;Wn="SNOUTPOD_DB_ACCESS",Ef=/^[a-z_][a-z0-9_]*$/;for(let e of Oc)if(!Ef.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);zn="snoutpod_oauth_read_new_schema",Af=`do $home$
40
+ and ${t}.rolname !~ '${If.source}'`}function Lc(e){let t=[];for(let n of e.split(`
41
+ `)){let r=/^(created-group|created|applied|dropped-group|dropped|refused|ignored) (\S+)$/.exec(n.trim());r&&t.push({action:r[1],role:r[2]})}return t}function Uc(e,t){return re(e),JSON.stringify(Ic(t).map(n=>({role:n.role,level:n.level})))}var jf,gt,Cf,J,Cc,If,Yn,Df,Vn,Mf,Mc,Co=j(()=>{"use strict";_e();jf=["full","read"],gt="snout_oauth",Cf=/^oauth_[0-9a-f]{32}$/,J="snout_oauth_read",Cc=["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"],If=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;Yn="SNOUTPOD_DB_ACCESS",Df=/^[a-z_][a-z0-9_]*$/;for(let e of Cc)if(!Df.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Vn="snoutpod_oauth_read_new_schema",Mf=`do $home$
42
42
  declare
43
43
  -- snout_ext in the own layout, extensions in a database not yet renamed (docs/cloud/OWN-LAYOUT.md).
44
44
  home text := case
@@ -47,7 +47,7 @@ declare
47
47
  end;
48
48
  begin
49
49
  execute pg_catalog.format('create schema if not exists %I', home);
50
- execute pg_catalog.replace($fn$create or replace function @home@.${zn}() returns event_trigger
50
+ execute pg_catalog.replace($fn$create or replace function @home@.${Vn}() returns event_trigger
51
51
  language plpgsql
52
52
  security invoker
53
53
  set search_path = pg_catalog, pg_temp
@@ -67,7 +67,7 @@ begin
67
67
  where c.command_tag = 'CREATE SCHEMA'
68
68
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
69
69
  and not c.in_extension
70
- and ${jc("n","r")}
70
+ and ${Dc("n","r")}
71
71
  loop
72
72
  begin
73
73
  execute pg_catalog.format('grant usage on schema %I to ${J}', created.nspname);
@@ -81,15 +81,15 @@ begin
81
81
  end loop;
82
82
  end
83
83
  $snoutpod$$fn$, '@home@', pg_catalog.quote_ident(home));
84
- if not exists (select from pg_catalog.pg_event_trigger where evtname = '${zn}') then
85
- execute pg_catalog.format('create event trigger ${zn} on ddl_command_end
84
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${Vn}') then
85
+ execute pg_catalog.format('create event trigger ${Vn} on ddl_command_end
86
86
  when tag in (''CREATE SCHEMA'')
87
- execute function %I.${zn}()', home);
87
+ execute function %I.${Vn}()', home);
88
88
  end if;
89
89
  end
90
90
  $home$;
91
- `,Cc=`\\getenv db_access ${Wn}
92
- ${Af}create function pg_temp.snoutpod_member(member_name text, group_name text)
91
+ `,Mc=`\\getenv db_access ${Yn}
92
+ ${Mf}create function pg_temp.snoutpod_member(member_name text, group_name text)
93
93
  returns boolean
94
94
  language sql
95
95
  stable
@@ -110,7 +110,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
110
110
  set search_path = pg_catalog, pg_temp
111
111
  as $snoutpod$
112
112
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
113
- select ${jc("n","r")}
113
+ select ${Dc("n","r")}
114
114
  from pg_namespace n
115
115
  join pg_roles r on r.oid = n.nspowner
116
116
  where n.oid = schema_oid
@@ -145,9 +145,9 @@ begin
145
145
  end if;
146
146
  any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
147
147
 
148
- if not exists (select from pg_roles where rolname = '${ft}') then
149
- create role ${ft} nologin;
150
- change := 'created-group'; role_name := '${ft}'; return next;
148
+ if not exists (select from pg_roles where rolname = '${gt}') then
149
+ create role ${gt} nologin;
150
+ change := 'created-group'; role_name := '${gt}'; return next;
151
151
  end if;
152
152
 
153
153
  -- Taken away: every member of the group the list does not name.
@@ -156,7 +156,7 @@ begin
156
156
  from pg_auth_members am
157
157
  join pg_roles g on g.oid = am.roleid
158
158
  join pg_roles m on m.oid = am.member
159
- where g.rolname = '${ft}'
159
+ where g.rolname = '${gt}'
160
160
  order by 1
161
161
  loop
162
162
  continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
@@ -228,7 +228,7 @@ begin
228
228
  wanted_role := entry->>'role';
229
229
  wanted_level := entry->>'level';
230
230
  if exists (select from pg_roles where rolname = wanted_role) then
231
- if not pg_temp.snoutpod_member(wanted_role, '${ft}') then
231
+ if not pg_temp.snoutpod_member(wanted_role, '${gt}') then
232
232
  -- The name is right and the role is not ours: made by somebody else, who holds
233
233
  -- ADMIN on it. Never adopted (see dbAccess.ts).
234
234
  change := 'refused'; role_name := wanted_role; return next;
@@ -236,7 +236,7 @@ begin
236
236
  end if;
237
237
  change := 'applied';
238
238
  else
239
- execute pg_catalog.format('create role %I login in role ${ft}', wanted_role);
239
+ execute pg_catalog.format('create role %I login in role ${gt}', wanted_role);
240
240
  change := 'created';
241
241
  end if;
242
242
  -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
@@ -299,8 +299,8 @@ begin
299
299
  end
300
300
  $snoutpod$;
301
301
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
302
- `});var Mc,MS,LS,US,FS,BS,Lc=C(()=>{"use strict";nc();So();vo();sc();xo();lc();uc();mc();pc();_c();Oo();Pc();Ec();Tc();Kn();No();Mc=120,MS=2*6e4,LS=15*60*1e3,US=60*60*1e3,FS=5*60*1e3,BS=10*60*1e3});import{execFile as Rf}from"node:child_process";function Uc(){return(e,t,n={})=>new Promise((r,o)=>{let s=Rf(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,l,i)=>{if(a&&typeof a.code=="string"){o(a);return}let u=a&&typeof a.code=="number"?a.code:0;r({code:u,stdout:String(l),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function q(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
303
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Bt=C(()=>{"use strict"});import{readdir as Tf,readFile as JS}from"node:fs/promises";async function Fc(e,t,n={}){let r=n.binary??"podman",o=B(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??Of)(i)!=="empty":!1}async function Of(e){try{return(await Tf(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Bc=C(()=>{"use strict";_e();Bt()});function Hc(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 Kc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Jc(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=${Hc("the access key",e.key,jf)}`),n.push("--env",Gc),e.token&&n.push("--env",qc)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${Hc("the endpoint",e.endpoint,Nf)}`),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 zc(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{[Gc]:e.secret,...e.token?{[qc]:e.token}:{}}}var Gc,qc,Nf,jf,Wc=C(()=>{"use strict";Gc="SNOUTPOD_S3_SECRET",qc="SNOUTPOD_S3_TOKEN",Nf=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,jf=/^[A-Za-z0-9/+=_.-]{1,256}$/});var Vc,Yc,Xc=C(()=>{"use strict";Vc=["snout_gate","authenticator"],Yc=`do $snoutpod$
302
+ `});var Fc,VS,YS,XS,ZS,QS,Bc=j(()=>{"use strict";oc();vo();$o();ic();Po();uc();pc();fc();gc();vc();jo();Rc();Tc();jc();zn();Co();Fc=120,VS=2*6e4,YS=15*60*1e3,XS=60*60*1e3,ZS=5*60*1e3,QS=10*60*1e3});import{execFile as Lf}from"node:child_process";function Hc(){return(e,t,n={})=>new Promise((r,o)=>{let s=Lf(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,l,i)=>{if(a&&typeof a.code=="string"){o(a);return}let u=a&&typeof a.code=="number"?a.code:0;r({code:u,stdout:String(l),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function q(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
303
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Ht=j(()=>{"use strict"});import{readdir as Uf,readFile as ok}from"node:fs/promises";async function Gc(e,t,n={}){let r=n.binary??"podman",o=U(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??Ff)(i)!=="empty":!1}async function Ff(e){try{return(await Uf(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var qc=j(()=>{"use strict";_e();Ht()});import{createHash as Bf,createHmac as Kc,pbkdf2Sync as Hf}from"node:crypto";function Jc(e,t){let n=Gf.exec(e.trim());if(!n)return!1;let[,r,o,s,a]=n,l=Number(r);if(!Number.isInteger(l)||l<1||l>1e6)return!1;let i=Hf(t.normalize("NFKC"),Buffer.from(o,"base64"),l,32,"sha256"),u=Kc("sha256",i).update("Client Key").digest(),c=Bf("sha256").update(u).digest(),m=Kc("sha256",i).update("Server Key").digest();return c.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var Gf,zc=j(()=>{"use strict";Gf=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function Wc(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 Xc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Zc(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=${Wc("the access key",e.key,Kf)}`),n.push("--env",Vc),e.token&&n.push("--env",Yc)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${Wc("the endpoint",e.endpoint,qf)}`),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 Qc(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{[Vc]:e.secret,...e.token?{[Yc]:e.token}:{}}}var Vc,Yc,qf,Kf,el=j(()=>{"use strict";Vc="SNOUTPOD_S3_SECRET",Yc="SNOUTPOD_S3_TOKEN",qf=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Kf=/^[A-Za-z0-9/+=_.-]{1,256}$/});var tl,nl,rl=j(()=>{"use strict";tl=["snout_gate","authenticator"],nl=`do $snoutpod$
304
304
  declare
305
305
  wanted text[];
306
306
  begin
@@ -318,8 +318,8 @@ begin
318
318
  end loop;
319
319
  end
320
320
  $snoutpod$;
321
- `});function Qc(e){let t=[];for(let n of e.split(`
322
- `)){let r=n.split(Co);if(r.length!==4||!jo.includes(r[0]))continue;let[o,s,a,l]=r;t.push(l?{name:o,from:s,error:l}:{name:o,from:s,to:a})}return t}var jo,Co,Zc,el=C(()=>{"use strict";jo=["snouttime"];for(let e of jo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Co="",Zc=`
321
+ `});function sl(e){let t=[];for(let n of e.split(`
322
+ `)){let r=n.split(Do);if(r.length!==4||!Io.includes(r[0]))continue;let[o,s,a,l]=r;t.push(l?{name:o,from:s,error:l}:{name:o,from:s,to:a})}return t}var Io,Do,ol,al=j(()=>{"use strict";Io=["snouttime"];for(let e of Io)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Do="",ol=`
323
323
  SET search_path = pg_catalog, pg_temp;
324
324
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
325
325
  DO $snoutpod$
@@ -333,7 +333,7 @@ BEGIN
333
333
  SELECT e.extname, e.extversion
334
334
  FROM pg_extension e
335
335
  JOIN pg_available_extensions a ON a.name = e.extname
336
- WHERE e.extname = ANY (ARRAY[${jo.map(e=>`'${e}'`).join(", ")}])
336
+ WHERE e.extname = ANY (ARRAY[${Io.map(e=>`'${e}'`).join(", ")}])
337
337
  AND a.default_version IS DISTINCT FROM e.extversion
338
338
  LOOP
339
339
  BEGIN
@@ -364,102 +364,102 @@ BEGIN
364
364
  END
365
365
  $snoutpod$;
366
366
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
367
- `});function nl(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 tl,tk,nk,rl=C(()=>{"use strict";Bt();Do();tl="SNOUTPOD_OAUTH_ISSUER",tk=256*1024,nk=5*6e4});import{createHash as Lo,randomBytes as Cf}from"node:crypto";import{access as Df,constants as If}from"node:fs/promises";import{delimiter as Mf,join as Lf}from"node:path";function Uf(e,t){return Lo("sha256").update(Uo(e)).update(e.smtpSecretEnv&&t?Lo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function Uo(e){if(Array.isArray(e))return`[${e.map(Uo).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)}:${Uo(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Bo(e){if(!Bf.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Gt(e){if(!Hf.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Gf(e,t,n){return`postgres://${Gt(t)}:${n}@127.0.0.1:5432/${re(e)}`}function al(e,t,n,r){let o=$c(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]=Gf(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function il(e,t){return`${B(e).pod}-${Bo(t)}`}function Vn(e){return`snoutpod-host-${Bo(e)}`}function qf(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return Lo("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,t,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null])).digest("hex").slice(0,32)}function Kf(e,t){let n=[],r=Fo(t);for(let o of e??[])if(Bo(o.name),Yn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Yn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===Ft){let s=o.hostPort+vc;Yn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${kc}`)}}return n}function Fo(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 Yn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function ll(e){let t=[];for(let n of Object.keys(e)){if(!Jf.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function zf(e){let t=e.healthPath;if(t==null||t==="")return[];if(!ml.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Yn(e.hostPort,`${e.name}: hostPort`),["--label",`${Ho}=${e.hostPort}:${t}`])}function Wf(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||!ml.test(r)?null:{port:n,path:r}}async function Yf(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Df(o,If.X_OK));for(let o of n.split(Mf))if(o)try{let s=Lf(o,e);return await r(s),s}catch{}return null}function Xf(){return["\\set ON_ERROR_STOP 0","set search_path = pg_catalog, pg_temp;","select 'create extension if not exists pg_prewarm schema public' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","select pg_catalog.format($snoutpod$with candidate as ("," select c.oid, pg_catalog.pg_relation_size(c.oid) as bytes"," from pg_class c join pg_namespace n on n.oid = c.relnamespace"," where c.relkind in ('r', 'i', 'm')"," and c.relpersistence = 'p'"," and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')","), budgeted as ("," select oid, bytes,"," sum(bytes) over (order by bytes, oid rows between unbounded preceding and current row) as running"," from candidate",")","select coalesce(sum(%I.pg_prewarm(oid::pg_catalog.regclass, 'buffer'::pg_catalog.text, 'main'::pg_catalog.text, null::pg_catalog.int8, null::pg_catalog.int8)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4$snoutpod$, n.nspname)","from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_prewarm' \\gexec",""].join(`
368
- `)}function Qf(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function eg(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 Go(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function tg(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 ng(e,t,n,r){return{image:await rg(e,t,r),data:await og(e,t,n)}}async function rg(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 eg(r,n)}async function og(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",B(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?Go(s.stdout):null}catch{return null}}function sg(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:dl;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,dl)}`),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 ag(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Vf;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Yf(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function fl(e){let t=e.binary??"podman",n=e.exec,r=ag(e),o=e.probe??pl;async function s(p){try{await n(t,["exec",...me,"--interactive",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:Zf,stdin:Xf()})}catch{}}async function a(){let p=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]),w=p.trim()?JSON.parse(p):[],v=new Map,D=new Map;for(let A of w){let T=A.Labels?.[Se],ee=A.Labels?.[tt];if(!T||!ee)continue;let te=v.get(T)??[];te.push(ee),v.set(T,te);let ce=Number(A.Labels?.[Mo]??0),O=Number.isInteger(ce)?ce:0;D.set(T,Math.max(D.get(T)??O,O))}let j=w.filter(A=>A.Labels?.[Se]&&!A.Labels?.[tt]),H=j.map(A=>lg(A.State)),G=await cg(j,ig,(A,T)=>H[T]==="running"?Promise.resolve(!0):e.hasData(A.Labels?.[Se]??""));return j.map((A,T)=>{let ee=A.Labels?.[Se]??"",te=Number(A.Labels?.[Io]);return{ref:ee,status:H[T]??"unknown",generation:Number.isInteger(te)?te:null,hasData:G[T]??!1,services:v.get(ee)??[],...D.has(ee)?{servicesGeneration:D.get(ee)}:{}}})}async function l(){let p=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(v=>v.Labels?.[Se]&&!v.Labels?.[tt]).length}async function i(p){let w=await(e.majors??((v,D)=>ng(n,t,v,D)))(p.ref,p.image);return tg(p.ref,w)}async function u(p){let w=B(p.ref),v=await i(p);if(v)throw new Error(v);let D=e.credentials,j=await Kc(e.repository,D?()=>D.forProject(p.ref,p.bucket):null),H={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...zc(j)},G=[...await r(),"run","--detach","--name",w.pod,"--label",`${Se}=${p.ref}`,"--label",`${Io}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...mg(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?Fo(e.podBind).flatMap(A=>["--publish",`${A}:${p.port}:5432`]):[],...Kf(p.services,e.podBind),"--volume",`${w.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"]:[],...ug(p.oauth),...sg(p.tuning).flatMap(A=>["--env",A]),"--env",`SNOUTPOD_RETENTION_FULL=${oc(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...Jc(j),p.image,"snoutpod-postgres"];await q(n,t,G,{env:H})}async function c(p){let w=await q(n,t,["ps","--all","--filter",`label=${Se}=${re(p)}`,"--filter",`label=${tt}`,"--format","json"]),v=w.trim()?JSON.parse(w):[],D=[];for(let j of v){let H=j.Labels?.[tt];if(!H)continue;let G=Number(j.Labels?.[Mo]??0);D.push({name:H,container:il(p,H),spec:j.Labels?.[ol]??"",pod:j.Labels?.[sl]??"",running:j.State==="running",generation:Number.isInteger(G)?G:0})}return D}async function m(p){let w=await n(t,["container","inspect","--format","{{.Id}}",B(p).pod]);return w.code===0?w.stdout.trim():""}async function h(p,w){let v=[];for(let A of w){let T=A.dbUriRole??null;A.dbUriEnv&&T&&!v.includes(T)&&v.push(Gt(T))}let D=new Map;if(v.length===0)return D;let j={},H=[],G=[];return v.some(A=>Vc.includes(A))&&G.push(Yc),v.forEach((A,T)=>{let ee=Cf(24).toString("base64url"),te=`SNOUTPOD_SERVICE_PASSWORD_${T}`;D.set(A,ee),j[te]=ee,H.push("--env",te),G.push(`\\getenv service_password_${T} ${te}`),G.push(`\\set service_role_${T} ${A}`),G.push(`alter role :"service_role_${T}" with password :'service_password_${T}';`)}),G.push(""),await q(n,t,["exec",...me,"--interactive",...H,B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",B(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:j,stdin:G.join(`
369
- `)}),D}async function x(p){let w=Jn(p.ref);await q(n,t,["volume","create","--ignore",w]),await q(n,t,[...await r(),"run","--rm","--network","none","--volume",`${w}:${To}`,"--entrypoint","bash",p.image,"-c",xc])}async function U(p){let w=await r();if((await n(t,[...w,"image","exists",p])).code===0)return;let v=await n(t,[...w,"pull","--quiet",p]);if(v.code===0)return;let D=Qf(p);if(!D||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(v.stderr||v.stdout).trim()}`);await e.registryLogin(D),await q(n,t,[...w,"pull","--quiet",p])}async function V(p){let w=p.services??[],v=await c(p.ref),D;w.some(O=>O.smtpSecretEnv)&&(D=await e.smtpSecret?.().catch(()=>{}));let j=w.length>0?await m(p.ref):"",H=new Map(v.map(O=>[O.name,O])),G=new Map(w.map(O=>[O.name,Uf(O,D)])),A=new Set(w.filter(O=>{let ne=H.get(O.name);return!ne||!ne.running||ne.spec!==G.get(O.name)||j===""||ne.pod!==j}).map(O=>O.name)),T=p.servicesGeneration??0;if(A.size===0&&w.length>0&&!v.some(O=>O.generation>=T))for(let O of w)A.add(O.name);let ee=O=>O.dbUriEnv&&O.dbUriRole?O.dbUriRole:null;for(let O=!0;O;){O=!1;let ne=new Set(w.filter(ze=>A.has(ze.name)).map(ee));for(let ze of w){let os=ee(ze);!A.has(ze.name)&&os!==null&&ne.has(os)&&(A.add(ze.name),O=!0)}}for(let O of new Set(w.filter(ne=>A.has(ne.name)).map(ne=>ne.image)))await U(O);for(let O of v)(A.has(O.name)||!G.has(O.name))&&await q(n,t,["rm","--force","--ignore",O.container]);let te=w.filter(O=>A.has(O.name)),ce=await h(p,te);te.some(O=>O.name===Ft)&&await x(p);for(let O of te){let ne=il(p.ref,O.name),ze=[...await r(),"run","--detach","--name",ne,"--label",`${Se}=${p.ref}`,"--label",`${Io}=${p.generation}`,"--label",`${tt}=${O.name}`,"--label",`${Mo}=${p.servicesGeneration??0}`,"--label",`${ol}=${G.get(O.name)??""}`,"--label",`${sl}=${j}`,"--network",`container:${B(p.ref).pod}`,"--userns",`container:${B(p.ref).pod}`,"--memory",`${O.memoryMb}m`,"--restart","no",...zf(O),...O.name===Ft?["--volume",`${Jn(p.ref)}:${To}:ro`]:[],...ll(al(p,O,ce,D)),O.image];await q(n,t,ze,{env:al(p,O,ce,D)})}}async function F(p){let w=await n(t,["exec",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(w.code!==0)return null;let v=w.stdout.trim();return v==="t"?!0:v==="f"?!1:null}async function pe(p,w){let v=j=>n(t,["exec",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",j]),D=Date.now()+w;do{if((await v("checkpoint")).code!==0)return!1;let H=await v("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(H.code===0&&H.stdout.trim()==="t")return!0;await new Promise(G=>setTimeout(G,1e3))}while(Date.now()<D);return!1}async function Te(p){let w=await n(t,["exec",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",ac]);return w.code===0?ic(w.stdout):null}async function ie(p,w){return(await n(t,["exec",B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...cc(w).flatMap(D=>["-c",D])])).code===0}return{list:a,inRecovery:F,settleTimeline:pe,podHealth:Te,setSyncNames:ie,preflight:i,create:u,createServices:V,async start(p){await q(n,t,["start",B(p).pod])},async startServices(p){let w=(await c(p)).map(v=>v.container);w.length>0&&await q(n,t,["start",...w])},async applyHostServices(p){let w=[],v=await q(n,t,["ps","--all","--filter",`label=${Ht}`,"--format","json"]),D=v.trim()?JSON.parse(v):[],j=new Map;for(let G of D){let A=G.Labels?.[Ht];A&&j.set(A,{spec:G.Labels?.[cl]??"",state:G.State??""})}for(let G of p)try{await H(G)}catch(A){w.push({name:G.name,action:"failed",reason:A instanceof Error?A.message:String(A)})}async function H(G){let A=e.hostServiceLocal?.(G,p),T=A?{...G,...A.env&&Object.keys(A.env).length>0?{env:{...G.env,...A.env}}:{},...A.volumes?{volumes:A.volumes}:{},...A.network?{network:A.network}:{}}:G,ee=Vn(T.name),te=qf(T),ce=j.get(T.name);if(j.delete(T.name),ce&&ce.spec===te){ce.state!=="running"&&(await q(n,t,["start",ee]),w.push({name:T.name,action:"started",reason:ce.state}));return}let O=ce?"the spec changed":"it was not here";await U(T.image),ce&&await q(n,t,["rm","--force","--ignore",ee]),await q(n,t,[...await r(),"run","--detach","--name",ee,"--label",`${Ht}=${T.name}`,"--label",`${cl}=${te}`,...T.healthPath?["--label",`${Ho}=${T.hostPort}:${T.healthPath}`]:[],"--memory",`${T.memoryMb}m`,"--restart","no",...T.network?["--network",T.network]:e.network?["--network",e.network]:[],...(T.volumes??[]).flatMap(ne=>["--volume",ne]),...Fo(e.podBind).flatMap(ne=>["--publish",`${ne}:${T.hostPort}:${T.port}`]),...T.adminPort&&T.adminHostPort?["--publish",`127.0.0.1:${T.adminHostPort}:${T.adminPort}`]:[],...ll(T.env),T.image,...T.command??[]],{env:{...T.env}}),w.push({name:T.name,action:ce?"recreated":"created",reason:O})}for(let G of j.keys())await q(n,t,["rm","--force","--ignore",Vn(G)]),w.push({name:G,action:"removed",reason:"it is no longer on the list"});return w},async hostServiceHealth(p){let w=await q(n,t,["ps","--all","--filter",`label=${Ht}`,"--format","json"]),v=(w.trim()?JSON.parse(w):[]).map(j=>j.Labels?.[Ht]??"").filter(j=>j!=="");if(v.length===0)return[];let D=Date.now()+p;for(;;){let j=[];for(let H of v)j.push(await ul(n,t,H,Vn(H),o));if(j.every(H=>H.ok)||Date.now()>=D)return j;await new Promise(H=>setTimeout(H,500))}},async serviceHealth(p,w){let v=await c(p);if(v.length===0)return[];let D=Date.now()+w;for(;;){let j=await Promise.all(v.map(H=>ul(n,t,H.name,H.container,o)));if(j.every(H=>H.ok)||Date.now()>=D)return j;await new Promise(H=>setTimeout(H,500))}},async stop(p,w){for(let v of await c(p))await q(n,t,["stop","--time",String(Ff),v.container]);await q(n,t,["stop","--time",String(w),B(p).pod])},async remove(p){for(let w of await c(p))await q(n,t,["rm","--force","--ignore",w.container]);await q(n,t,["rm","--force","--ignore","--volumes",B(p).pod])},async removeData(p){await q(n,t,["volume","rm","--force",B(p).pod]);let w=Jn(p);(await n(t,["volume","exists",w])).code===0&&await q(n,t,["volume","rm","--force",w])},async applyOwnerPassword(p){let w=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
370
- `);await q(n,t,["exec",...me,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:p.ownerPassword},stdin:w})},async applyRolePassword(p,w,v){let D=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Gt(w)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
371
- `);await q(n,t,["exec",...me,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",B(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:v},stdin:D})},async updateExtensions(p){let w=B(p),v=await q(n,t,["exec",...me,"--interactive",w.pod,"psql","--username","snoutpod_admin","--dbname",w.database,"--quiet","-tA","-F",Co,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Zc});return Qc(v)},async applyDbAccess(p,w){let v=B(p),D=await q(n,t,["exec",...me,"--interactive","--env",Wn,v.pod,"psql","--username","snoutpod_admin","--dbname",v.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Wn]:Ic(p,w)},stdin:Cc,timeoutMs:6e4});return Dc(D)},async runProjectSql(p,w){let v=B(p);await q(n,t,["exec",...me,"--interactive",v.pod,"psql","--username","snoutpod_admin","--dbname",v.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:w})},async ensureHostServiceDatabase(p,w,v){let D=Vn(p),j=Gt(w),H=Gt(v),G=T=>["exec",D,"psql","--username",j,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",T];(await q(n,t,G(`select 1 from pg_database where datname = '${H}'`))).trim()!=="1"&&await q(n,t,G(`create database "${H}"`))},async renameProject(p,w){w!==p.ref&&await q(n,t,["exec",...me,"--interactive",B(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:dg(w,p.ref)})},async waitReady(p,w){let v=B(p).pod,D=Date.now()+w;for(;;){let j=await n(t,["exec",...me,v,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(j.code===0&&j.stdout.trim()==="t")return s(p),!0;if(Date.now()>=D)return!1;await new Promise(H=>setTimeout(H,500))}},async waitStandby(p,w){let v=Date.now()+w;for(;;){if(await F(p)===!0)return!0;if(Date.now()>=v)return!1;await new Promise(D=>setTimeout(D,500))}},async capacity(){let[p,w]=await Promise.all([l(),e.capacity()]);return{pods:p,...w}},async databaseBytes(p){let w=await n(t,["exec",...me,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return w.code===0?ko(w.stdout):null},async storageBytes(p){let w=await n(t,["exec",...me,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select (xpath('/row/n/text()', query_to_xml(format('select coalesce(sum((metadata->>''size'')::bigint), 0) as n from %s', coalesce(to_regclass('snout_files.files'), to_regclass('storage.objects'))), false, true, '')))[1]::text"]);return w.code===0?ko(w.stdout):null},async setReadOnly(p,w){let v=B(p);await q(n,t,["exec",...me,v.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${v.database} set default_transaction_read_only = ${w?"on":"off"}`])},async archiveState(p){let w=await n(t,["exec",...me,B(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(w.code!==0)return null;let[v="",D=""]=w.stdout.trim().split(`
372
- `)[0]?.split("|")??[],j=Number(D);return{lastArchivedAt:v.trim()||null,failures:Number.isInteger(j)&&j>=0?j:0}}}}async function ul(e,t,n,r,o=pl){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let m=JSON.parse(s.stdout);a=m[0]?.State,l=m[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=Wf(l?.[Ho]);if(!u)return{name:n,ok:!0,detail:"running"};let c=await o(u.port,u.path);return c===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${c}`}}async function cg(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 lg(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function dg(e,t){return[`\\set from_db ${e}`,`\\set to_db ${t}`,`\\set from_role ${e}_owner`,`\\set to_role ${t}_owner`,'alter database :"from_db" rename to :"to_db";','alter role :"from_role" rename to :"to_role";',"alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;",""].join(`
373
- `)}function ug(e){if(!e?.issuer)return[];try{return["--env",`${tl}=${nl(e.issuer)}`]}catch{return[]}}function mg(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Se,Io,tt,Mo,ol,sl,Ff,Bf,Hf,Ht,cl,Jf,ml,Ho,pl,Vf,me,Zf,dl,ig,Do=C(()=>{"use strict";_e();So();xo();Bt();Wc();vo();Xc();el();No();rl();Oo();Se="snoutpod.ref",Io="snoutpod.generation",tt="snoutpod.service",Mo="snoutpod.services-generation",ol="snoutpod.service-spec",sl="snoutpod.service-pod";Ff=10,Bf=/^[a-z][a-z0-9-]{0,30}$/;Hf=/^[a-z][a-z0-9_]{0,62}$/;Ht="snoutpod.host-service",cl="snoutpod.host-service-spec";Jf=/^[A-Za-z_][A-Za-z0-9_]*$/;ml=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Ho="snoutpod.health";pl=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()})},Vf="crun";me=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],Zf=3e4;dl=200;ig=8});var Rl={};ss(Rl,{DEFAULT_LOCAL_MEMORY_MB:()=>bl,DEFAULT_LOCAL_PORT:()=>yg,ENGINE:()=>z,ENGINE_NAME:()=>qo,IMAGE:()=>zo,NETWORK:()=>qe,NEW_PROJECT_MAJOR:()=>Jo,NO_BLACKHOLE_ROUTES:()=>vl,PUBLIC_IMAGE:()=>Sg,PUBLISHED_MAJORS:()=>gt,capacity:()=>kl,chooseLocalImage:()=>Jt,dropIfUnused:()=>El,ensureImage:()=>kg,ensureNetwork:()=>Eg,exec:()=>Z,hasData:()=>er,localImageFor:()=>Sl,localPsql:()=>Tg,localRuntime:()=>Pg,localSpec:()=>Pl,localStatus:()=>Wo,localUri:()=>$l,networkPolicyInPlace:()=>xl,newLocalPassword:()=>bg,newLocalRef:()=>wg,podLog:()=>Al,podName:()=>ht,podmanReady:()=>xg,removeLocal:()=>jg,staleImage:()=>Ko,startLocal:()=>Rg,stopLocal:()=>Ng,volumePgVersion:()=>Xn});import{randomBytes as yl}from"node:crypto";import{existsSync as gl}from"node:fs";import{platform as Qn}from"node:os";import{delimiter as pg,join as fg}from"node:path";function gg(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Qn()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(pg))if(t&&e.some(n=>gl(fg(t,n))))return"podman";return gl("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Jt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Kt(Jo);let n=Number(Go(e));if(!gt.includes(n)){let r=e.trim()||"(empty)",o=gt.map(s=>Kt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${gt.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Kt(n)}function Kt(e){return{name:`${hg}:${e}`,published:`${wl}:${e}`,major:e}}function wg(){return tc(e=>yl(e))}function bg(){return yl(18).toString("base64url")}async function kg(e,t=Z,n=Jt(null)){let r=n.published;if(!r)return null;if((await t(z,["image","exists",n.name])).code===0)return await vg(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(z,["pull",r],{timeoutMs:_l}).catch(a=>({code:1,stdout:"",stderr:a instanceof Error?a.message:String(a)}));if(o.code!==0)return`could not fetch the database image ${r}: ${o.stderr.trim()||o.stdout.trim()||"the pull failed"}`;let s=await t(z,["tag",r,n.name]);return s.code!==0?`fetched ${r} but could not tag it as ${n.name}: ${s.stderr.trim()}`:(e?.("Got it."),null)}async function vg(e,t,n,r){let o=await t(z,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
374
- `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(z,["pull",r],{timeoutMs:_l}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(l));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
375
- `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(z,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(z,["tag",r,n]);if(m.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${m.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function xg(e=_g,t=zo){if(qo!=="podman")return`SNOUTPOD_ENGINE=${qo}: a local project is Podman-only, because a database that works under Docker and not under Podman is a database that works here and not in the cloud`;let n;try{n=await Z(z,["--version"])}catch{return`${z} is not installed. macOS: brew install podman, then podman machine init && podman machine set --rootful=false && podman machine start. Windows: winget install RedHat.Podman, then the same three machine commands. Linux: your distribution's podman package.`}if(n.code!==0)return`${z} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await Z(z,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await Z(z,["machine","inspect","--format","{{.Rootful}}"]);return o.code===0&&o.stdout.trim().startsWith("true")?"the podman machine is ROOTFUL, and the hosts are not: podman machine stop && podman machine set --rootful=false && podman machine start":null}function $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 Z(z,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
376
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function er(e){let t=Qn()==="darwin"||Qn()==="win32";return Fc(Z,e,{binary:z,look:t?$g:void 0})}async function Xn(e,t=Z,n=Qn()){let r=B(e).pod;if((await t(z,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of gt){let l=Kt(a).name;if((await t(z,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${gt.map(a=>Kt(a).published).join(" or ")}).`);o=await t(z,["run","--rm","--pull=never","--network=none","--userns=host","--user=0","--entrypoint=cat","--volume",`${r}:/data:ro`,s,"/data/PG_VERSION"])}else{let s=await t(z,["volume","inspect","--format","{{.Mountpoint}}",r]),a=s.code===0?s.stdout.trim().replace(/\/+$/,""):"";if(!a)throw new Error(`could not find where ${r} is: ${s.stderr.trim()||"no mountpoint"}`);o=await t(z,["unshare","cat",`${a}/PG_VERSION`])}if(o.code===0)return o.stdout;if(/no such file/i.test(o.stderr))return null;throw new Error(`could not read the Postgres version in ${r}: ${o.stderr.trim()||o.stdout.trim()||`exit ${o.code}`}`)}function Sl(e,t=Xn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Jt(null));re(e);let n=t===Xn?Zn.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Jt(o)}catch(s){throw new Error(`${ht(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Xn&&(Zn.set(e,r),r.catch(()=>Zn.delete(e))),r}async function kl(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Pg(e){return fl({exec:Z,binary:z,network:qe,hasData:er,capacity:kl,repository:e})}function ht(e){return B(e).pod}async function Eg(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await Ag()||await Z(z,qn({name:qe}));return}let t=qn({name:qe,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await xl())return;await e?.(),await Z(z,["network","rm","--force",qe]).catch(()=>{});let o=await Z(z,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${vl}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${qe} with egress blocked${n?`: ${n}`:""}`)}async function Ag(){return(await Z(z,["network","inspect",qe,"--format","json"])).code===0}async function xl(){let e=await Z(z,["network","inspect",qe,"--format","json"]);if(e.code!==0)return!1;try{return bc(JSON.parse(e.stdout)?.[0])}catch{return!1}}function $l(e){let t=B(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Pl(e,t={}){return re(e.ref),{ref:e.ref,generation:1,image:t.image??zo,memoryMb:t.memoryMb??bl,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 qt(e,t){e.onProgress?.(t)}async function Rg(e,t,n={}){let r=n.image??(await Sl(t.ref,n.pgVersion)).name,o=Pl(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??er,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??Ko)(t.ref,r))?(qt(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??El)(i)):l.status==="stopped"?(qt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(qt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??Ko)(t.ref,r)&&qt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(qt(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 Og(t.ref,s,n.podLog??Al));return await e.applyOwnerPassword(o),await e.createServices(o),Wo(e,t,n)}function hl(e){return e.trim().replace(/^sha256:/,"")}async function Ko(e,t){let[n,r]=await Promise.all([Z(z,["container","inspect","--format","{{.Image}}",ht(e)]).catch(()=>null),Z(z,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=hl(n.stdout),s=hl(r.stdout);return o&&s&&o!==s?o:null}async function El(e){let t=await Z(z,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await Z(z,["rmi",e]).catch(()=>{})}async function Tg(e,t){let n=B(e.ref);return Z(z,["exec","--interactive","-e","PGPASSWORD",ht(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 Al(e){let t=await Z(z,["logs","--tail","20",ht(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Og(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${ht(e)} never became ready in ${t}ms
367
+ `});function cl(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 il,gk,hk,ll=j(()=>{"use strict";Ht();Mo();il="SNOUTPOD_OAUTH_ISSUER",gk=256*1024,hk=5*6e4});import{createHash as Fo,randomBytes as Jf}from"node:crypto";import{access as zf,constants as Wf}from"node:fs/promises";import{delimiter as Vf,join as Yf}from"node:path";function Xf(e,t){return Fo("sha256").update(Bo(e)).update(e.smtpSecretEnv&&t?Fo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function Bo(e){if(Array.isArray(e))return`[${e.map(Bo).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)}:${Bo(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Go(e){if(!Qf.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function qt(e){if(!eg.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function tg(e,t,n){return`postgres://${qt(t)}:${n}@127.0.0.1:5432/${re(e)}`}function ml(e,t,n,r){let o=Ac(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]=tg(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function pl(e,t){return`${U(e).pod}-${Go(t)}`}function Xn(e){return`snoutpod-host-${Go(e)}`}function ng(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return Fo("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,t,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null])).digest("hex").slice(0,32)}function rg(e,t){let n=[],r=Ho(t);for(let o of e??[])if(Go(o.name),Zn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Zn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===Bt){let s=o.hostPort+Pc;Zn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${$c}`)}}return n}function Ho(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 Zn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function gl(e){let t=[];for(let n of Object.keys(e)){if(!og.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function sg(e){let t=e.healthPath;if(t==null||t==="")return[];if(!bl.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Zn(e.hostPort,`${e.name}: hostPort`),["--label",`${qo}=${e.hostPort}:${t}`])}function ag(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||!bl.test(r)?null:{port:n,path:r}}async function cg(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>zf(o,Wf.X_OK));for(let o of n.split(Vf))if(o)try{let s=Yf(o,e);return await r(s),s}catch{}return null}function lg(){return["\\set ON_ERROR_STOP 0","set search_path = pg_catalog, pg_temp;","select 'create extension if not exists pg_prewarm schema public' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","select pg_catalog.format($snoutpod$with candidate as ("," select c.oid, pg_catalog.pg_relation_size(c.oid) as bytes"," from pg_class c join pg_namespace n on n.oid = c.relnamespace"," where c.relkind in ('r', 'i', 'm')"," and c.relpersistence = 'p'"," and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')","), budgeted as ("," select oid, bytes,"," sum(bytes) over (order by bytes, oid rows between unbounded preceding and current row) as running"," from candidate",")","select coalesce(sum(%I.pg_prewarm(oid::pg_catalog.regclass, 'buffer'::pg_catalog.text, 'main'::pg_catalog.text, null::pg_catalog.int8, null::pg_catalog.int8)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4$snoutpod$, n.nspname)","from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_prewarm' \\gexec",""].join(`
368
+ `)}function ug(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function mg(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 Ko(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function pg(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 fg(e,t,n,r){return{image:await gg(e,t,r),data:await hg(e,t,n)}}async function gg(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 mg(r,n)}async function hg(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",U(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?Ko(s.stdout):null}catch{return null}}function yg(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:hl;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,hl)}`),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 wg(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??ig;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await cg(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Sl(e){let t=e.binary??"podman",n=e.exec,r=wg(e),o=e.probe??_l;async function s(p){try{await n(t,["exec",...ce,"--interactive",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:dg,stdin:lg()})}catch{}}async function a(){let p=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]),y=p.trim()?JSON.parse(p):[],_=new Map,C=new Map,R=new Map;for(let P of y){let Z=P.Labels?.[Se],Q=P.Labels?.[nt];if(!Z||!Q)continue;let oe=_.get(Z)??[];oe.push(Q),_.set(Z,oe),wl(P.State)==="stopped"&&C.set(Z,[...C.get(Z)??[],Q]);let O=Number(P.Labels?.[Uo]??0),te=Number.isInteger(O)?O:0;R.set(Z,Math.max(R.get(Z)??te,te))}let H=y.filter(P=>P.Labels?.[Se]&&!P.Labels?.[nt]),B=H.map(P=>wl(P.State)),G=await _g(H,bg,(P,Z)=>B[Z]==="running"?Promise.resolve(!0):e.hasData(P.Labels?.[Se]??""));return H.map((P,Z)=>{let Q=P.Labels?.[Se]??"",oe=Number(P.Labels?.[Lo]);return{ref:Q,status:B[Z]??"unknown",generation:Number.isInteger(oe)?oe:null,hasData:G[Z]??!1,services:_.get(Q)??[],servicesStopped:C.get(Q)??[],...R.has(Q)?{servicesGeneration:R.get(Q)}:{}}})}async function l(){let p=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(_=>_.Labels?.[Se]&&!_.Labels?.[nt]).length}async function i(p){let y=await(e.majors??((_,C)=>fg(n,t,_,C)))(p.ref,p.image);return pg(p.ref,y)}async function u(p){let y=U(p.ref),_=await i(p);if(_)throw new Error(_);let C=e.credentials,R=await Xc(e.repository,C?()=>C.forProject(p.ref,p.bucket):null),H={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...Qc(R)},B=[...await r(),"run","--detach","--name",y.pod,"--label",`${Se}=${p.ref}`,"--label",`${Lo}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...vg(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?Ho(e.podBind).flatMap(G=>["--publish",`${G}:${p.port}:5432`]):[],...rg(p.services,e.podBind),"--volume",`${y.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"]:[],...kg(p.oauth),...yg(p.tuning).flatMap(G=>["--env",G]),"--env",`SNOUTPOD_RETENTION_FULL=${ac(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...Zc(R),p.image,"snoutpod-postgres"];await q(n,t,B,{env:H})}async function c(p){let y=await q(n,t,["ps","--all","--filter",`label=${Se}=${re(p)}`,"--filter",`label=${nt}`,"--format","json"]),_=y.trim()?JSON.parse(y):[],C=[];for(let R of _){let H=R.Labels?.[nt];if(!H)continue;let B=Number(R.Labels?.[Uo]??0);C.push({name:H,container:pl(p,H),spec:R.Labels?.[dl]??"",pod:R.Labels?.[ul]??"",running:R.State==="running",generation:Number.isInteger(B)?B:0})}return C}async function m(p){let y=await n(t,["container","inspect","--format","{{.Id}}",U(p).pod]);return y.code===0?y.stdout.trim():""}async function h(p,y){let _=[];for(let G of y){let P=G.dbUriRole??null;G.dbUriEnv&&P&&!_.includes(P)&&_.push(qt(P))}let C=new Map;if(_.length===0)return C;let R={},H=[],B=[];return _.some(G=>tl.includes(G))&&B.push(nl),_.forEach((G,P)=>{let Z=Jf(24).toString("base64url"),Q=`SNOUTPOD_SERVICE_PASSWORD_${P}`;C.set(G,Z),R[Q]=Z,H.push("--env",Q),B.push(`\\getenv service_password_${P} ${Q}`),B.push(`\\set service_role_${P} ${G}`),B.push(`alter role :"service_role_${P}" with password :'service_password_${P}';`)}),B.push(""),await q(n,t,["exec",...ce,"--interactive",...H,U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",U(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:R,stdin:B.join(`
369
+ `)}),C}async function x(p){let y=Wn(p.ref);await q(n,t,["volume","create","--ignore",y]),await q(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${No}`,"--entrypoint","bash",p.image,"-c",Ec])}async function L(p){let y=await r();if((await n(t,[...y,"image","exists",p])).code===0)return;let _=await n(t,[...y,"pull","--quiet",p]);if(_.code===0)return;let C=ug(p);if(!C||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(_.stderr||_.stdout).trim()}`);await e.registryLogin(C),await q(n,t,[...y,"pull","--quiet",p])}async function V(p){let y=p.services??[],_=await c(p.ref),C;y.some(O=>O.smtpSecretEnv)&&(C=await e.smtpSecret?.().catch(()=>{}));let R=y.length>0?await m(p.ref):"",H=new Map(_.map(O=>[O.name,O])),B=new Map(y.map(O=>[O.name,Xf(O,C)])),G=new Set(y.filter(O=>{let te=H.get(O.name);return!te||!te.running||te.spec!==B.get(O.name)||R===""||te.pod!==R}).map(O=>O.name)),P=p.servicesGeneration??0;if(G.size===0&&y.length>0&&!_.some(O=>O.generation>=P))for(let O of y)G.add(O.name);let Z=O=>O.dbUriEnv&&O.dbUriRole?O.dbUriRole:null;for(let O=!0;O;){O=!1;let te=new Set(y.filter(We=>G.has(We.name)).map(Z));for(let We of y){let as=Z(We);!G.has(We.name)&&as!==null&&te.has(as)&&(G.add(We.name),O=!0)}}for(let O of new Set(y.filter(te=>G.has(te.name)).map(te=>te.image)))await L(O);for(let O of _)(G.has(O.name)||!B.has(O.name))&&await q(n,t,["rm","--force","--ignore",O.container]);let Q=y.filter(O=>G.has(O.name)),oe=await h(p,Q);Q.some(O=>O.name===Bt)&&await x(p);for(let O of Q){let te=pl(p.ref,O.name),We=[...await r(),"run","--detach","--name",te,"--label",`${Se}=${p.ref}`,"--label",`${Lo}=${p.generation}`,"--label",`${nt}=${O.name}`,"--label",`${Uo}=${p.servicesGeneration??0}`,"--label",`${dl}=${B.get(O.name)??""}`,"--label",`${ul}=${R}`,"--network",`container:${U(p.ref).pod}`,"--userns",`container:${U(p.ref).pod}`,"--memory",`${O.memoryMb}m`,"--restart","no",...sg(O),...O.name===Bt?["--volume",`${Wn(p.ref)}:${No}:ro`]:[],...gl(ml(p,O,oe,C)),O.image];await q(n,t,We,{env:ml(p,O,oe,C)})}}async function F(p,y){if(y%6!==0)return!1;let _=await n(t,["container","inspect","--format","{{.State.Status}}",U(p).pod]);return _.code===0&&/^(exited|stopped|dead)$/.test(_.stdout.trim())}async function de(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let _=y.stdout.trim();return _==="t"?!0:_==="f"?!1:null}async function Oe(p,y){let _=R=>n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",R]),C=Date.now()+y;do{if((await _("checkpoint")).code!==0)return!1;let H=await _("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(H.code===0&&H.stdout.trim()==="t")return!0;await new Promise(B=>setTimeout(B,1e3))}while(Date.now()<C);return!1}async function le(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",cc]);return y.code===0?lc(y.stdout):null}async function ve(p,y){return(await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...dc(y).flatMap(C=>["-c",C])])).code===0}return{list:a,inRecovery:de,settleTimeline:Oe,podHealth:le,setSyncNames:ve,preflight:i,create:u,createServices:V,async start(p){let y=(await c(p).catch(()=>[])).filter(_=>_.running).map(_=>_.container);y.length>0&&await q(n,t,["stop","--time","5",...y]),await q(n,t,["start",U(p).pod])},async startServices(p){let y=(await c(p)).map(_=>_.container);y.length>0&&await q(n,t,["start",...y])},async applyHostServices(p){let y=[],_=await q(n,t,["ps","--all","--filter",`label=${Gt}`,"--format","json"]),C=_.trim()?JSON.parse(_):[],R=new Map;for(let B of C){let G=B.Labels?.[Gt];G&&R.set(G,{spec:B.Labels?.[fl]??"",state:B.State??""})}for(let B of p)try{await H(B)}catch(G){y.push({name:B.name,action:"failed",reason:G instanceof Error?G.message:String(G)})}async function H(B){let G=e.hostServiceLocal?.(B,p),P=G?{...B,...G.env&&Object.keys(G.env).length>0?{env:{...B.env,...G.env}}:{},...G.volumes?{volumes:G.volumes}:{},...G.network?{network:G.network}:{}}:B,Z=Xn(P.name),Q=ng(P),oe=R.get(P.name);if(R.delete(P.name),oe&&oe.spec===Q){oe.state!=="running"&&(await q(n,t,["start",Z]),y.push({name:P.name,action:"started",reason:oe.state}));return}let O=oe?"the spec changed":"it was not here";await L(P.image),oe&&await q(n,t,["rm","--force","--ignore",Z]),await q(n,t,[...await r(),"run","--detach","--name",Z,"--label",`${Gt}=${P.name}`,"--label",`${fl}=${Q}`,...P.healthPath?["--label",`${qo}=${P.hostPort}:${P.healthPath}`]:[],"--memory",`${P.memoryMb}m`,"--restart","no",...P.network?["--network",P.network]:e.network?["--network",e.network]:[],...(P.volumes??[]).flatMap(te=>["--volume",te]),...Ho(e.podBind).flatMap(te=>["--publish",`${te}:${P.hostPort}:${P.port}`]),...P.adminPort&&P.adminHostPort?["--publish",`127.0.0.1:${P.adminHostPort}:${P.adminPort}`]:[],...gl(P.env),P.image,...P.command??[]],{env:{...P.env}}),y.push({name:P.name,action:oe?"recreated":"created",reason:O})}for(let B of R.keys())await q(n,t,["rm","--force","--ignore",Xn(B)]),y.push({name:B,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(p){let y=await q(n,t,["ps","--all","--filter",`label=${Gt}`,"--format","json"]),_=(y.trim()?JSON.parse(y):[]).map(R=>R.Labels?.[Gt]??"").filter(R=>R!=="");if(_.length===0)return[];let C=Date.now()+p;for(;;){let R=[];for(let H of _)R.push(await yl(n,t,H,Xn(H),o));if(R.every(H=>H.ok)||Date.now()>=C)return R;await new Promise(H=>setTimeout(H,500))}},async serviceHealth(p,y){let _=await c(p);if(_.length===0)return[];let C=Date.now()+y;for(;;){let R=await Promise.all(_.map(H=>yl(n,t,H.name,H.container,o)));if(R.every(H=>H.ok)||Date.now()>=C)return R;await new Promise(H=>setTimeout(H,500))}},async stop(p,y){for(let _ of await c(p))await q(n,t,["stop","--time",String(Zf),_.container]);await q(n,t,["stop","--time",String(y),U(p).pod])},async remove(p){for(let y of await c(p))await q(n,t,["rm","--force","--ignore",y.container]);await q(n,t,["rm","--force","--ignore","--volumes",U(p).pod])},async removeData(p){await q(n,t,["volume","rm","--force",U(p).pod]);let y=Wn(p);(await n(t,["volume","exists",y])).code===0&&await q(n,t,["volume","rm","--force",y])},async applyOwnerPassword(p){let y=await n(t,["exec",...ce,U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${re(p.ref)}_owner'`]);if(y.code===0&&Jc(y.stdout,p.ownerPassword))return;let _=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
370
+ `);await q(n,t,["exec",...ce,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:p.ownerPassword},stdin:_})},async applyRolePassword(p,y,_){let C=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${qt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
371
+ `);await q(n,t,["exec",...ce,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",U(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:_},stdin:C})},async updateExtensions(p){let y=U(p),_=await n(t,["exec",...ce,y.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${y.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${y.database}' desc, datname limit 1`]),C=_.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(_.stdout.trim())?_.stdout.trim():y.database,R=await q(n,t,["exec",...ce,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",C,"--quiet","-tA","-F",Do,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:ol});return sl(R)},async applyDbAccess(p,y){let _=U(p),C=await q(n,t,["exec",...ce,"--interactive","--env",Yn,_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Yn]:Uc(p,y)},stdin:Mc,timeoutMs:6e4});return Lc(C)},async runProjectSql(p,y){let _=U(p);await q(n,t,["exec",...ce,"--interactive",_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(p,y,_){let C=Xn(p),R=qt(y),H=qt(_),B=P=>["exec",C,"psql","--username",R,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",P];(await q(n,t,B(`select 1 from pg_database where datname = '${H}'`))).trim()!=="1"&&await q(n,t,B(`create database "${H}"`))},async renameProject(p,y){y!==p.ref&&await q(n,t,["exec",...ce,"--interactive",U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Sg(y,p.ref)})},async waitReady(p,y){let _=U(p).pod,C=Date.now()+y,R=0;for(;;){let H=await n(t,["exec",...ce,_,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(H.code===0&&H.stdout.trim()==="t")return s(p),!0;if(Date.now()>=C||await F(p,++R))return!1;await new Promise(B=>setTimeout(B,500))}},async waitStandby(p,y){let _=Date.now()+y,C=0;for(;;){if(await de(p)===!0)return!0;if(Date.now()>=_||await F(p,++C))return!1;await new Promise(R=>setTimeout(R,500))}},async capacity(){let[p,y]=await Promise.all([l(),e.capacity()]);return{pods:p,...y}},async databaseBytes(p){let y=await n(t,["exec",...ce,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return y.code===0?xo(y.stdout):null},async storageBytes(p){let y=await n(t,["exec",...ce,U(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 y.code===0?xo(y.stdout):null},async setReadOnly(p,y){let _=U(p);await q(n,t,["exec",...ce,_.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${_.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(p){let y=await n(t,["exec",...ce,U(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(y.code!==0)return null;let[_="",C=""]=y.stdout.trim().split(`
372
+ `)[0]?.split("|")??[],R=Number(C);return{lastArchivedAt:_.trim()||null,failures:Number.isInteger(R)&&R>=0?R:0}}}}async function yl(e,t,n,r,o=_l){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let m=JSON.parse(s.stdout);a=m[0]?.State,l=m[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=ag(l?.[qo]);if(!u)return{name:n,ok:!0,detail:"running"};let c=await o(u.port,u.path);return c===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${c}`}}async function _g(e,t,n){let r=new Array(e.length),o=0;async function s(){for(;o<e.length;){let a=o++;r[a]=await n(e[a],a)}}return await Promise.all(Array.from({length:Math.min(Math.max(1,t),e.length)},s)),r}function wl(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Sg(e,t){return[`\\set from_db ${e}`,`\\set to_db ${t}`,`\\set from_role ${e}_owner`,`\\set to_role ${t}_owner`,'alter database :"from_db" rename to :"to_db";','alter role :"from_role" rename to :"to_role";',"alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;",""].join(`
373
+ `)}function kg(e){if(!e?.issuer)return[];try{return["--env",`${il}=${cl(e.issuer)}`]}catch{return[]}}function vg(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Se,Lo,nt,Uo,dl,ul,Zf,Qf,eg,Gt,fl,og,bl,qo,_l,ig,ce,dg,hl,bg,Mo=j(()=>{"use strict";_e();vo();Po();zc();Ht();el();$o();rl();al();Co();ll();jo();Se="snoutpod.ref",Lo="snoutpod.generation",nt="snoutpod.service",Uo="snoutpod.services-generation",dl="snoutpod.service-spec",ul="snoutpod.service-pod";Zf=10,Qf=/^[a-z][a-z0-9-]{0,30}$/;eg=/^[a-z][a-z0-9_]{0,62}$/;Gt="snoutpod.host-service",fl="snoutpod.host-service-spec";og=/^[A-Za-z_][A-Za-z0-9_]*$/;bl=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,qo="snoutpod.health";_l=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()})},ig="crun";ce=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],dg=3e4;hl=200;bg=8});var Dl={};is(Dl,{DEFAULT_LOCAL_MEMORY_MB:()=>Pl,DEFAULT_LOCAL_PORT:()=>Ag,ENGINE:()=>z,ENGINE_NAME:()=>Jo,IMAGE:()=>Vo,NETWORK:()=>Ke,NEW_PROJECT_MAJOR:()=>Wo,NO_BLACKHOLE_ROUTES:()=>Tl,PUBLIC_IMAGE:()=>Ng,PUBLISHED_MAJORS:()=>ht,capacity:()=>Rl,chooseLocalImage:()=>zt,dropIfUnused:()=>Cl,ensureImage:()=>jg,ensureNetwork:()=>Lg,exec:()=>ee,hasData:()=>nr,localImageFor:()=>Al,localPsql:()=>Bg,localRuntime:()=>Mg,localSpec:()=>jl,localStatus:()=>Yo,localUri:()=>Nl,networkPolicyInPlace:()=>Ol,newLocalPassword:()=>Tg,newLocalRef:()=>Rg,podLog:()=>Il,podName:()=>yt,podmanReady:()=>Ig,removeLocal:()=>qg,staleImage:()=>zo,startLocal:()=>Fg,stopLocal:()=>Gg,volumePgVersion:()=>Qn});import{randomBytes as xl}from"node:crypto";import{existsSync as kl}from"node:fs";import{platform as tr}from"node:os";import{delimiter as xg,join as $g}from"node:path";function Pg(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=tr()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(xg))if(t&&e.some(n=>kl($g(t,n))))return"podman";return kl("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function zt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Jt(Wo);let n=Number(Ko(e));if(!ht.includes(n)){let r=e.trim()||"(empty)",o=ht.map(s=>Jt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ht.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Jt(n)}function Jt(e){return{name:`${Eg}:${e}`,published:`${$l}:${e}`,major:e}}function Rg(){return rc(e=>xl(e))}function Tg(){return xl(18).toString("base64url")}async function jg(e,t=ee,n=zt(null)){let r=n.published;if(!r)return null;if((await t(z,["image","exists",n.name])).code===0)return await Cg(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(z,["pull",r],{timeoutMs:El}).catch(a=>({code:1,stdout:"",stderr:a instanceof Error?a.message:String(a)}));if(o.code!==0)return`could not fetch the database image ${r}: ${o.stderr.trim()||o.stdout.trim()||"the pull failed"}`;let s=await t(z,["tag",r,n.name]);return s.code!==0?`fetched ${r} but could not tag it as ${n.name}: ${s.stderr.trim()}`:(e?.("Got it."),null)}async function Cg(e,t,n,r){let o=await t(z,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
374
+ `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(z,["pull",r],{timeoutMs:El}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(l));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
375
+ `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(z,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(z,["tag",r,n]);if(m.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${m.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function Ig(e=Og,t=Vo){if(Jo!=="podman")return`SNOUTPOD_ENGINE=${Jo}: a local project is Podman-only, because a database that works under Docker and not under Podman is a database that works here and not in the cloud`;let n;try{n=await ee(z,["--version"])}catch{return`${z} is not installed. macOS: brew install podman, then podman machine init && podman machine set --rootful=false && podman machine start. Windows: winget install RedHat.Podman, then the same three machine commands. Linux: your distribution's podman package.`}if(n.code!==0)return`${z} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await ee(z,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await ee(z,["machine","inspect","--format","{{.Rootful}}"]);return o.code===0&&o.stdout.trim().startsWith("true")?"the podman machine is ROOTFUL, and the hosts are not: podman machine stop && podman machine set --rootful=false && podman machine start":null}function Dg(e){let t=`if out=$(ls -A ${JSON.stringify(e)} 2>/dev/null); then if [ -n "$out" ]; then echo DATA; else echo EMPTY; fi; else echo HIDDEN; fi`;return ee(z,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
376
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function nr(e){let t=tr()==="darwin"||tr()==="win32";return Gc(ee,e,{binary:z,look:t?Dg:void 0})}async function Qn(e,t=ee,n=tr()){let r=U(e).pod;if((await t(z,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of ht){let l=Jt(a).name;if((await t(z,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${ht.map(a=>Jt(a).published).join(" or ")}).`);o=await t(z,["run","--rm","--pull=never","--network=none","--userns=host","--user=0","--entrypoint=cat","--volume",`${r}:/data:ro`,s,"/data/PG_VERSION"])}else{let s=await t(z,["volume","inspect","--format","{{.Mountpoint}}",r]),a=s.code===0?s.stdout.trim().replace(/\/+$/,""):"";if(!a)throw new Error(`could not find where ${r} is: ${s.stderr.trim()||"no mountpoint"}`);o=await t(z,["unshare","cat",`${a}/PG_VERSION`])}if(o.code===0)return o.stdout;if(/no such file/i.test(o.stderr))return null;throw new Error(`could not read the Postgres version in ${r}: ${o.stderr.trim()||o.stdout.trim()||`exit ${o.code}`}`)}function Al(e,t=Qn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(zt(null));re(e);let n=t===Qn?er.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return zt(o)}catch(s){throw new Error(`${yt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Qn&&(er.set(e,r),r.catch(()=>er.delete(e))),r}async function Rl(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Mg(e){return Sl({exec:ee,binary:z,network:Ke,hasData:nr,capacity:Rl,repository:e})}function yt(e){return U(e).pod}async function Lg(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await Ug()||await ee(z,Jn({name:Ke}));return}let t=Jn({name:Ke,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Ol())return;await e?.(),await ee(z,["network","rm","--force",Ke]).catch(()=>{});let o=await ee(z,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Tl}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ke} with egress blocked${n?`: ${n}`:""}`)}async function Ug(){return(await ee(z,["network","inspect",Ke,"--format","json"])).code===0}async function Ol(){let e=await ee(z,["network","inspect",Ke,"--format","json"]);if(e.code!==0)return!1;try{return kc(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Nl(e){let t=U(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function jl(e,t={}){return re(e.ref),{ref:e.ref,generation:1,image:t.image??Vo,memoryMb:t.memoryMb??Pl,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 Kt(e,t){e.onProgress?.(t)}async function Fg(e,t,n={}){let r=n.image??(await Al(t.ref,n.pgVersion)).name,o=jl(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??nr,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??zo)(t.ref,r))?(Kt(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??Cl)(i)):l.status==="stopped"?(Kt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Kt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??zo)(t.ref,r)&&Kt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Kt(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 Hg(t.ref,s,n.podLog??Il));return await e.applyOwnerPassword(o),await e.createServices(o),Yo(e,t,n)}function vl(e){return e.trim().replace(/^sha256:/,"")}async function zo(e,t){let[n,r]=await Promise.all([ee(z,["container","inspect","--format","{{.Image}}",yt(e)]).catch(()=>null),ee(z,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=vl(n.stdout),s=vl(r.stdout);return o&&s&&o!==s?o:null}async function Cl(e){let t=await ee(z,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await ee(z,["rmi",e]).catch(()=>{})}async function Bg(e,t){let n=U(e.ref);return ee(z,["exec","--interactive","-e","PGPASSWORD",yt(e.ref),"psql","--host","127.0.0.1","--port","5432","--username",n.ownerRole,"--dbname",n.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA","--file","-"],{stdin:t,env:{PGPASSWORD:e.ownerPassword}})}async function Il(e){let t=await ee(z,["logs","--tail","20",yt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Hg(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${yt(e)} never became ready in ${t}ms
377
377
  --- pod log ---
378
- ${r}`}async function Ng(e,t,n={}){re(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Mc),Wo(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function jg(e,t,n){re(t),await e.remove(t),n&&(await e.removeData(t),Zn.delete(t))}async function Wo(e,t,n={}){re(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??er;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?$l(t):null}}var qo,z,Jo,gt,hg,wl,zo,qe,yg,bl,Z,_g,Sg,_l,Zn,vl,Tl=C(()=>{"use strict";Lc();Bt();Bc();Eo();Do();_e();qo=process.env.SNOUTPOD_ENGINE??"podman";z=gg(),Jo=18,gt=[17,18],hg="snoutpod/postgres",wl="ghcr.io/snoutdata/snoutpod-postgres";zo=Jt(null).name,qe=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",yg=54322,bl=512,Z=Uc();_g="cd packages/snoutpod && npm run image:build",Sg=`${wl}:${Jo}`;_l=20*60*1e3;Zn=new Map;vl="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});le();var fe=["--admin","--token","--project"],ir=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],nt=[...fe,"--plan","--no-wait","--timeout",...ir],de=[...nt,"--yes"],cr=[{name:"init",summary:"Create or link a project for this folder, and print its DATABASE_URL or write it to .env",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, with --sso through your company's identity provider, or with --device by typing a code into one anywhere",flags:["--email","--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to, and whether it is the account login --email asked for",flags:[]},{name:"upgrade",summary:"Install the newest CLI the way this one was installed (binary or npm), checksum verified",flags:["--check"]},{name:"tokens list",summary:"Long-lived access tokens on this account",flags:[]},{name:"tokens create",summary:"Mint an sdt_ token for CI or an agent",flags:["--name","--expires","--project"]},{name:"tokens revoke",args:"<id|sdt_prefix>",summary:"Revoke one by id or prefix",flags:[]},{name:"projects list",summary:"Every project on this account",flags:[]},{name:"projects create",summary:"Make a hosted database, and print its ref",flags:["--name","--region","--team","--no-wait","--timeout","--show-url"]},{name:"projects pause",summary:"Stop a project, and wait until it is paused",flags:["--ref","--no-wait","--timeout"]},{name:"projects resume",summary:"Start a paused project, and wait until it is ready",flags:["--ref","--no-wait","--timeout"]},{name:"projects delete",summary:"Delete a project, and wait until it is gone",flags:["--ref","--no-wait","--timeout"]},{name:"projects show",summary:"One project: state, products (Realtime included), functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage, the data API and push: on or off. Realtime is always on, and listed",flags:["--ref"]},{name:"products enable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push off",flags:["--ref"]},{name:"push credentials",summary:"Push keys: what is set for APNs, FCM and Web Push (never the keys)",flags:["--ref"]},{name:"push credentials set",args:"apns|fcm",summary:"Set the APNs key (a .p8) or the FCM service account, checked before it is stored",flags:["--ref","--p8","--key-id","--team-id","--topic","--environment","--file"]},{name:"push credentials remove",args:"apns|fcm",summary:"Remove the APNs or FCM credentials",flags:["--ref"]},{name:"auth",summary:"Auth settings: Google sign-in and redirect addresses",flags:["--ref"]},{name:"auth google",summary:"Sign in with Google, with your own client (secret on stdin)",flags:["--ref","--client-id","--stdin"]},{name:"auth anonymous",args:"on|off",summary:"Guest sign-in on or off: guestSignIn() gives a browser a session with no email or password",flags:["--ref"]},{name:"auth redirects",summary:"The site URL and the addresses a sign-in may return to",flags:["--ref","--site-url","--allow"]},{name:"auth templates",summary:"The five auth emails, ours or your own",flags:["--ref"]},{name:"auth template",args:"KIND",summary:"Use your own subject and HTML for one, or reset it",flags:["--ref","--file","--subject"]},{name:"realtime inspect",summary:"The channels open now: each client, its roster entry, when it was last heard, and the last minute of messages",flags:["--channel","--watch","--ref"]},{name:"realtime logs",summary:"The connection log: connects, joins, leaves and disconnects, with the reason each ended",flags:["--since","--channel","--ref"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The app key and server key for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...fe]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...fe]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...fe]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...nt]},{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:[...nt,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...de,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...de]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...nt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...nt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...de,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...de]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...de]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...de,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...de]},{name:"cluster range move",summary:"Give a range to another node",flags:[...de,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...de,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...de,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...de,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...nt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...de,"--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:[...de]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...fe,...ir]},{name:"cluster jobs",summary:"The latest jobs",flags:[...fe]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...fe]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...fe,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...fe,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...fe,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...fe,...ir,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...fe,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],is={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","functions logs --errors":"Only what was printed with console.error or console.warn, and the errors that stopped a request","functions logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","functions logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","containers deploy --port":"The port the container listens on, also given to it as PORT (default 8080)","containers deploy --dockerfile":"A folder with a Dockerfile (or the Dockerfile): built for linux/arm64 with your Docker and uploaded to the project","containers deploy --registry-user":"The user name for a private registry. The token comes on stdin, with --stdin","containers deploy --stdin":"Read the registry token from a pipe, so it is not in shell history or ps","containers deploy --clear-registry":"Forget the stored registry login, for an image that is public now","containers deploy --no-wait":"Answer once it is recorded, without waiting for the host to start it","containers logs --errors":"Only what was printed to stderr","containers logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","containers logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","--name":"The project's name","tokens create --name":"What the token is for and where it lives, shown in tokens list","db restore --name":"The name of the new project the restore makes","--region":"Where the project runs. Defaults to the nearest","--env":"Write DATABASE_URL into .env in this folder, and add .env to .gitignore since it holds the password. Without it the URL is printed","--email":"The account to sign in as. The browser offers it, and a different account is reported at once and by whoami","--provider":"google (the default) or github","--sso":"Sign in with your company's identity provider","--domain":"With --sso: your work email or company domain. Only the domain is sent","--no-browser":"Print the sign-in URL instead of opening a browser","--device":"Sign in by typing a code into a browser on any machine: SSH, containers, agents","--timeout":"Seconds to wait before giving up","--check":"Say whether a newer version exists, and install nothing","--expires":"Days until the token stops working. Without it, it works until revoked","--project":"A SnoutData Cloud project ref: its cluster, through the control plane, as you (instead of --admin)","tokens create --project":"Limit the token to one project ref","--team":"Create it in a team (name or id), shared with its members","--no-wait":"Return as soon as it is asked for, without waiting for it to be true","--show-url":"Also print the connection string, which holds the password. Otherwise db url prints it when wanted","--p8":"The APNs .p8 key file (a path, never the key itself)","--key-id":"The APNs key id","--team-id":"Your Apple developer team id","--topic":"The app's bundle id","--environment":"production or sandbox","--file":"A file to read","push credentials set --file":"The FCM service-account JSON","auth template --file":"The email's HTML body","db restore --file":"The dump to restore (from db export or pg_dump -Fc)","--client-id":"Your Google OAuth client id; the secret is read from stdin with --stdin","--stdin":"Read the secret value from a pipe, so it is not in shell history","--site-url":"Where sign-in emails send people by default","--allow":"Comma-separated addresses a sign-in may return to","--subject":"The email's subject line","--dir":"The folder to read from","db push --dir":"The migrations folder (default ./migrations)","start --dir":"The migrations folder (default ./migrations)","functions deploy --dir":"The function's folder (default ./functions/<name>)","--entrypoint":"The file the runtime starts, when it is not index.ts","--no-verify-jwt":"Let anybody with the URL call it (webhooks). By default a caller needs a project key or a user session","--memory":"Memory per worker, in MB, within the plan","--concurrency":"How many workers it may run at once, within the plan","--reset":"Back to the plan's default size","--force":"Do it although it cannot be undone","--days":"How many days of history","--history":"Print the day-by-day table too","--port":"The port the local database listens on (default 54322)","--no-migrations":"Start the database and apply nothing","--out-of-order":"Apply a new migration that sorts before one already applied","--db-url":"Read a postgres:// database instead of a project","--local":"Read the database snoutdata start is running","--schema":"Comma-separated schemas (default public)","--out":"Write to this file instead of stdout","db export --out":"Where to save the dump","--status":"Show the last export without taking another","--dry-run":"Show what would run, and change nothing","--at":"The moment to restore to (an ISO time), into a NEW project","--window":"How far back a point-in-time restore can go","--allow-delete":"Let the agent's delete_project actually delete","--channel":"Only this channel","--since":"How far back, like 10m, 2h or 1d (default 1h)","--watch":"Keep printing what changes until interrupted","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","cluster range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","cluster cleanup --node":"Only this node. Default: every node","cluster replica add --node":"The node pod's ref, as cluster status lists it (or as the first word)","cluster replica remove --node":"The node pod's ref, as cluster status lists it (or as the first word)","--value":"The key value to pin","--add":"Comma-separated nodes to add, each name=host[:port]","--remove":"How many of the emptiest nodes to drain and remove","cluster scale --nodes":"SnoutData Cloud: how many nodes the project should have, the home included","--max-write-pause-ms":"The longest a cutover may pause writes; one that would take longer is postponed","--drain-timeout-ms":"How long a cutover waits for open transactions to finish","--set":"Settings by their API names, name=value,... (the advisor's advice_* thresholds among them)","--sample-ms":"How long to measure write rates over, in ms (default: the advice_sample_ms setting; 0 skips them)","--ack-timeout-ms":"How long a cutover waits for every router to acknowledge it","--copy-mb-per-s":"The copy rate a plan estimates with (chosen, not measured)","--level":"full (everything the project password can do) or read (reads the project's own tables, never the auth or storage schemas, and writes nothing). Default read"};function lr(e,t){return is[`${e} ${t}`]??is[t]??""}function cs(e,t){let n=cr.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}Y();oe();W();import{spawn as du}from"node:child_process";function uu(e){return`snout-${e}`}function dt(e,t,n){return new Promise((r,o)=>{let s=du("docker",["compose","-p",uu(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",l="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(l+=String(i),n)for(let u of String(i).split(/\r?\n/).filter(Boolean))d(y(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new k("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 k("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function mu(e){let t=await dt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new k("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 Ws(e){let t=await mu(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${S(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${L([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
379
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Vs(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await dt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new k("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function Ys(e){let t=await dt(e,["stop"],!0);if(t.code!==0)throw new k("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Xs(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${L([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
380
- `)})}_t();import{cpSync as ea,existsSync as Rt,readdirSync as _u,readFileSync as ta,rmSync as na,statSync as Su,writeFileSync as ra}from"node:fs";import{join as ye,resolve as kn}from"node:path";var pu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function fu(e){return pu.test(e)}function Xe(e){if(!fu(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 gu=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function hu(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!==".."&&gu.test(n))}var Er=2*1024*1024,Zs=200;function Qs(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>Zs)throw new Error(`a function may hold ${Zs} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!hu(a.path))throw new Error(`${JSON.stringify(a.path)} is not a path a function may contain`);if(n.has(a.path))throw new Error(`${a.path} appears twice`);n.add(a.path)}if(!n.has(t))throw new Error(`the entrypoint ${JSON.stringify(t)} is not one of the files`);let r=[...e.files].sort((a,l)=>a.path<l.path?-1:a.path>l.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=yu(o);if(s.length>Er)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Er/1024} KB. A function is source code; put large files in Storage.`);return o}function yu(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 wu=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var bu=["SNOUTDATA_","SNOUT_"];function At(e){if(!wu.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=bu.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 Hy=64*1024;oe();le();W();function oa(e){let t=ye(e.folder,"functions");return Rt(t)?_u(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Su(ye(t,n)).isDirectory()).sort():[]}async function vn(e){d(y(" docker compose run --rm functions-deploy"));let t=await dt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new k("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function sa(e,t,n){let r=ye(e.folder,".env"),o=ta(r,"utf8"),s=(en(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return ra(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
378
+ ${r}`}async function Gg(e,t,n={}){re(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Fc),Yo(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function qg(e,t,n){re(t),await e.remove(t),n&&(await e.removeData(t),er.delete(t))}async function Yo(e,t,n={}){re(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??nr;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?Nl(t):null}}var Jo,z,Wo,ht,Eg,$l,Vo,Ke,Ag,Pl,ee,Og,Ng,El,er,Tl,Ml=j(()=>{"use strict";Bc();Ht();qc();Ro();Mo();_e();Jo=process.env.SNOUTPOD_ENGINE??"podman";z=Pg(),Wo=18,ht=[17,18],Eg="snoutpod/postgres",$l="ghcr.io/snoutdata/snoutpod-postgres";Vo=zt(null).name,Ke=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Ag=54322,Pl=512,ee=Hc();Og="cd packages/snoutpod && npm run image:build",Ng=`${$l}:${Wo}`;El=20*60*1e3;er=new Map;Tl="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});ue();var fe=["--admin","--token","--project"],lr=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],rt=[...fe,"--plan","--no-wait","--timeout",...lr],me=[...rt,"--yes"],dr=[{name:"init",summary:"Create or link a project for this folder, and print its DATABASE_URL or write it to .env",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, with --sso through your company's identity provider, or with --device by typing a code into one anywhere",flags:["--email","--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to, and whether it is the account login --email asked for",flags:[]},{name:"upgrade",summary:"Install the newest CLI the way this one was installed (binary or npm), checksum verified",flags:["--check"]},{name:"tokens list",summary:"Long-lived access tokens on this account",flags:[]},{name:"tokens create",summary:"Mint an sdt_ token for CI or an agent",flags:["--name","--expires","--project"]},{name:"tokens revoke",args:"<id|sdt_prefix>",summary:"Revoke one by id or prefix",flags:[]},{name:"projects list",summary:"Every project on this account",flags:[]},{name:"projects create",summary:"Make a hosted database, and print its ref",flags:["--name","--region","--team","--no-wait","--timeout","--show-url"]},{name:"projects pause",summary:"Stop a project, and wait until it is paused",flags:["--ref","--no-wait","--timeout"]},{name:"projects resume",summary:"Start a paused project, and wait until it is ready",flags:["--ref","--no-wait","--timeout"]},{name:"projects delete",summary:"Delete a project, and wait until it is gone",flags:["--ref","--no-wait","--timeout"]},{name:"projects show",summary:"One project: state, products (Realtime included), functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage, the data API and push: on or off. Realtime is always on, and listed",flags:["--ref"]},{name:"products enable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push off",flags:["--ref"]},{name:"push credentials",summary:"Push keys: what is set for APNs, FCM and Web Push (never the keys)",flags:["--ref"]},{name:"push credentials set",args:"apns|fcm",summary:"Set the APNs key (a .p8) or the FCM service account, checked before it is stored",flags:["--ref","--p8","--key-id","--team-id","--topic","--environment","--file"]},{name:"push credentials remove",args:"apns|fcm",summary:"Remove the APNs or FCM credentials",flags:["--ref"]},{name:"auth",summary:"Auth settings: Google sign-in and redirect addresses",flags:["--ref"]},{name:"auth google",summary:"Sign in with Google, with your own client (secret on stdin)",flags:["--ref","--client-id","--stdin"]},{name:"auth anonymous",args:"on|off",summary:"Guest sign-in on or off: guestSignIn() gives a browser a session with no email or password",flags:["--ref"]},{name:"auth redirects",summary:"The site URL and the addresses a sign-in may return to",flags:["--ref","--site-url","--allow"]},{name:"auth templates",summary:"The five auth emails, ours or your own",flags:["--ref"]},{name:"auth template",args:"KIND",summary:"Use your own subject and HTML for one, or reset it",flags:["--ref","--file","--subject"]},{name:"realtime inspect",summary:"The channels open now: each client, its roster entry, when it was last heard, and the last minute of messages",flags:["--channel","--watch","--ref"]},{name:"realtime logs",summary:"The connection log: connects, joins, leaves and disconnects, with the reason each ended",flags:["--since","--channel","--ref"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The app key and server key for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...fe]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...fe]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...fe]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...rt]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...rt,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...me,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...me]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...rt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...rt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...me,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...me]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...me]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...me,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...me]},{name:"cluster range move",summary:"Give a range to another node",flags:[...me,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...me,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...me,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...me,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...rt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...me,"--node"]},{name:"cluster dissolve",summary:"A standalone cluster of the home node alone: check the data, take the fences off and forget every keyspace and table, then stop the router yourself",flags:[...me]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...fe,...lr]},{name:"cluster jobs",summary:"The latest jobs",flags:[...fe]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...fe]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...fe,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...fe,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...fe,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...fe,...lr,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...fe,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],ls={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","functions logs --errors":"Only what was printed with console.error or console.warn, and the errors that stopped a request","functions logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","functions logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","containers deploy --port":"The port the container listens on, also given to it as PORT (default 8080)","containers deploy --dockerfile":"A folder with a Dockerfile (or the Dockerfile): built for linux/arm64 with your Docker and uploaded to the project","containers deploy --registry-user":"The user name for a private registry. The token comes on stdin, with --stdin","containers deploy --stdin":"Read the registry token from a pipe, so it is not in shell history or ps","containers deploy --clear-registry":"Forget the stored registry login, for an image that is public now","containers deploy --no-wait":"Answer once it is recorded, without waiting for the host to start it","containers logs --errors":"Only what was printed to stderr","containers logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","containers logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","--name":"The project's name","tokens create --name":"What the token is for and where it lives, shown in tokens list","db restore --name":"The name of the new project the restore makes","--region":"Where the project runs. Defaults to the nearest","--env":"Write DATABASE_URL into .env in this folder, and add .env to .gitignore since it holds the password. Without it the URL is printed","--email":"The account to sign in as. The browser offers it, and a different account is reported at once and by whoami","--provider":"google (the default) or github","--sso":"Sign in with your company's identity provider","--domain":"With --sso: your work email or company domain. Only the domain is sent","--no-browser":"Print the sign-in URL instead of opening a browser","--device":"Sign in by typing a code into a browser on any machine: SSH, containers, agents","--timeout":"Seconds to wait before giving up","--check":"Say whether a newer version exists, and install nothing","--expires":"Days until the token stops working. Without it, it works until revoked","--project":"A SnoutData Cloud project ref: its cluster, through the control plane, as you (instead of --admin)","tokens create --project":"Limit the token to one project ref","--team":"Create it in a team (name or id), shared with its members","--no-wait":"Return as soon as it is asked for, without waiting for it to be true","--show-url":"Also print the connection string, which holds the password. Otherwise db url prints it when wanted","--p8":"The APNs .p8 key file (a path, never the key itself)","--key-id":"The APNs key id","--team-id":"Your Apple developer team id","--topic":"The app's bundle id","--environment":"production or sandbox","--file":"A file to read","push credentials set --file":"The FCM service-account JSON","auth template --file":"The email's HTML body","db restore --file":"The dump to restore (from db export or pg_dump -Fc)","--client-id":"Your Google OAuth client id; the secret is read from stdin with --stdin","--stdin":"Read the secret value from a pipe, so it is not in shell history","--site-url":"Where sign-in emails send people by default","--allow":"Comma-separated addresses a sign-in may return to","--subject":"The email's subject line","--dir":"The folder to read from","db push --dir":"The migrations folder (default ./migrations)","start --dir":"The migrations folder (default ./migrations)","functions deploy --dir":"The function's folder (default ./functions/<name>)","--entrypoint":"The file the runtime starts, when it is not index.ts","--no-verify-jwt":"Let anybody with the URL call it (webhooks). By default a caller needs a project key or a user session","--memory":"Memory per worker, in MB, within the plan","--concurrency":"How many workers it may run at once, within the plan","--reset":"Back to the plan's default size","--force":"Do it although it cannot be undone","--days":"How many days of history","--history":"Print the day-by-day table too","--port":"The port the local database listens on (default 54322)","--no-migrations":"Start the database and apply nothing","--out-of-order":"Apply a new migration that sorts before one already applied","--db-url":"Read a postgres:// database instead of a project","--local":"Read the database snoutdata start is running","--schema":"Comma-separated schemas (default public)","--out":"Write to this file instead of stdout","db export --out":"Where to save the dump","--status":"Show the last export without taking another","--dry-run":"Show what would run, and change nothing","--at":"The moment to restore to (an ISO time), into a NEW project","--window":"How far back a point-in-time restore can go","--allow-delete":"Let the agent's delete_project actually delete","--channel":"Only this channel","--since":"How far back, like 10m, 2h or 1d (default 1h)","--watch":"Keep printing what changes until interrupted","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","cluster range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","cluster cleanup --node":"Only this node. Default: every node","cluster replica add --node":"The node pod's ref, as cluster status lists it (or as the first word)","cluster replica remove --node":"The node pod's ref, as cluster status lists it (or as the first word)","--value":"The key value to pin","--add":"Comma-separated nodes to add, each name=host[:port]","--remove":"How many of the emptiest nodes to drain and remove","cluster scale --nodes":"SnoutData Cloud: how many nodes the project should have, the home included","--max-write-pause-ms":"The longest a cutover may pause writes; one that would take longer is postponed","--drain-timeout-ms":"How long a cutover waits for open transactions to finish","--set":"Settings by their API names, name=value,... (the advisor's advice_* thresholds among them)","--sample-ms":"How long to measure write rates over, in ms (default: the advice_sample_ms setting; 0 skips them)","--ack-timeout-ms":"How long a cutover waits for every router to acknowledge it","--copy-mb-per-s":"The copy rate a plan estimates with (chosen, not measured)","--level":"full (everything the project password can do) or read (reads the project's own tables, never the auth or storage schemas, and writes nothing). Default read"};function ur(e,t){return ls[`${e} ${t}`]??ls[t]??""}function ds(e,t){let n=dr.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}Y();se();W();import{spawn as yu}from"node:child_process";function wu(e){return`snout-${e}`}function ut(e,t,n){return new Promise((r,o)=>{let s=yu("docker",["compose","-p",wu(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",l="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(l+=String(i),n)for(let u of String(i).split(/\r?\n/).filter(Boolean))d(w(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new v("tool-missing","docker is not installed, or not on PATH. A local project runs in Docker (Docker Desktop on Windows and macOS)."));return}o(new v("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function bu(e){let t=await ut(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new v("failed",t.err.trim()||"docker compose ps failed. Is Docker running?");let n=t.out.trim();return(n.startsWith("[")?JSON.parse(n):n.split(/\r?\n/).filter(Boolean).map(o=>JSON.parse(o))).map(o=>({service:o.Service??"?",state:o.State==="exited"&&o.ExitCode===0?"done":o.State??"?",health:o.Health??""})).sort((o,s)=>o.service.localeCompare(s.service))}async function Vs(e){let t=await bu(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${k(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${M([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
379
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Ys(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await ut(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new v("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function Xs(e){let t=await ut(e,["stop"],!0);if(t.code!==0)throw new v("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Zs(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${M([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
380
+ `)})}St();import{cpSync as ta,existsSync as Tt,readdirSync as Eu,readFileSync as na,rmSync as ra,statSync as Au,writeFileSync as oa}from"node:fs";import{join as ye,resolve as xn}from"node:path";var _u=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Su(e){return _u.test(e)}function Ze(e){if(!Su(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 ku=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function vu(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!==".."&&ku.test(n))}var Rr=2*1024*1024,Qs=200;function ea(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>Qs)throw new Error(`a function may hold ${Qs} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!vu(a.path))throw new Error(`${JSON.stringify(a.path)} is not a path a function may contain`);if(n.has(a.path))throw new Error(`${a.path} appears twice`);n.add(a.path)}if(!n.has(t))throw new Error(`the entrypoint ${JSON.stringify(t)} is not one of the files`);let r=[...e.files].sort((a,l)=>a.path<l.path?-1:a.path>l.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=xu(o);if(s.length>Rr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Rr/1024} KB. A function is source code; put large files in Storage.`);return o}function xu(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 $u=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Pu=["SNOUTDATA_","SNOUT_"];function Rt(e){if(!$u.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Pu.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 Qy=64*1024;se();ue();W();function sa(e){let t=ye(e.folder,"functions");return Tt(t)?Eu(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Au(ye(t,n)).isDirectory()).sort():[]}async function $n(e){d(w(" docker compose run --rm functions-deploy"));let t=await ut(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new v("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function aa(e,t,n){let r=ye(e.folder,".env"),o=na(r,"utf8"),s=(tn(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return oa(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
381
381
  `)}${l}
382
- `),!0}async function Ar(e,t,n){Xe(t);let r=kn(n.dir??ye(process.cwd(),"functions",t));if(!Rt(ye(r,"index.ts")))throw new f(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ye(e.folder,"functions",t);if(kn(o)!==r){na(o,{recursive:!0,force:!0}),ea(r,o,{recursive:!0});let a=ye(r,"..","_shared");Rt(a)&&kn(a)!==kn(ye(e.folder,"functions","_shared"))&&ea(a,ye(e.folder,"functions","_shared"),{recursive:!0})}let s=sa(e,t,!n.verifyJwt);if(await vn(e),s){let a=await dt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new k("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:Rr(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function aa(e,t,n){let r=await Ar(e,t,n);g(r,()=>{d(`${S(t)} deployed locally: ${r.url}`),n.verifyJwt||d(y(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Rr(e,t){return`${e.apiUrl}/functions/v1/${t}`}function xn(e){return{ref:e.ref,local:e.folder,functions:oa(e).map(t=>({name:t,url:Rr(e,t)}))}}function ia(e){let t=oa(e);g(xn(e),()=>{if(t.length===0){d(`No functions in ${ye(e.folder,"functions")}.`);return}process.stdout.write(`${L([["NAME","URL"],...t.map(n=>[n,Rr(e,n)])])}
383
- `)})}async function Tr(e,t){Xe(t);let n=ye(e.folder,"functions",t);if(!Rt(n))throw new k("not-found",`this local project has no function called ${t}`);return na(n,{recursive:!0,force:!0}),sa(e,t,!1),await vn(e),{ref:e.ref,name:t,removed:!0}}async function ca(e,t){g(await Tr(e,t),()=>d(`${t} is gone.`))}function $n(e){return ye(e.folder,"functions",".env")}function Or(e){let t=$n(e);return Rt(t)?en(ta(t,"utf8")):new Map}function la(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;ra($n(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
382
+ `),!0}async function Tr(e,t,n){Ze(t);let r=xn(n.dir??ye(process.cwd(),"functions",t));if(!Tt(ye(r,"index.ts")))throw new f(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ye(e.folder,"functions",t);if(xn(o)!==r){ra(o,{recursive:!0,force:!0}),ta(r,o,{recursive:!0});let a=ye(r,"..","_shared");Tt(a)&&xn(a)!==xn(ye(e.folder,"functions","_shared"))&&ta(a,ye(e.folder,"functions","_shared"),{recursive:!0})}let s=aa(e,t,!n.verifyJwt);if(await $n(e),s){let a=await ut(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new v("failed",`docker compose up failed after changing who may call ${t}: ${a.err.trim().split(/\r?\n/).slice(-2).join(" ")}`)}return{ref:e.ref,name:t,url:Or(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function ia(e,t,n){let r=await Tr(e,t,n);g(r,()=>{d(`${k(t)} deployed locally: ${r.url}`),n.verifyJwt||d(w(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Or(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Pn(e){return{ref:e.ref,local:e.folder,functions:sa(e).map(t=>({name:t,url:Or(e,t)}))}}function ca(e){let t=sa(e);g(Pn(e),()=>{if(t.length===0){d(`No functions in ${ye(e.folder,"functions")}.`);return}process.stdout.write(`${M([["NAME","URL"],...t.map(n=>[n,Or(e,n)])])}
383
+ `)})}async function Nr(e,t){Ze(t);let n=ye(e.folder,"functions",t);if(!Tt(n))throw new v("not-found",`this local project has no function called ${t}`);return ra(n,{recursive:!0,force:!0}),aa(e,t,!1),await $n(e),{ref:e.ref,name:t,removed:!0}}async function la(e,t){g(await Nr(e,t),()=>d(`${t} is gone.`))}function En(e){return ye(e.folder,"functions",".env")}function jr(e){let t=En(e);return Tt(t)?tn(na(t,"utf8")):new Map}function da(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;oa(En(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
384
384
  `)+(t.size?`
385
- `:""))}async function da(e,t){for(let r of t){let o=At(r.name);if(o)throw new f(o)}let n=Or(e);for(let r of t)n.set(r.name,r.value);la(e,n),await vn(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${S(r.name)} set in ${$n(e)}`)})}function Pn(e){return{ref:e.ref,local:e.folder,secrets:[...Or(e).keys()].sort().map(t=>({name:t}))}}function ua(e){let t=Pn(e),n=t.secrets.map(r=>r.name);g(t,()=>{d(n.length?n.join(`
386
- `):`No function secrets in ${$n(e)}.`)})}async function ma(e,t){let n=Or(e);if(!n.delete(t))throw new k("not-found",`${t} is not set for this local project`);la(e,n),await vn(e),g({ref:e.ref,unset:t},()=>d(`${t} unset.`))}oe();Ve();Ie();br();an();W();bn();Y();Y();oe();Ie();W();async function pa(){let{projects:e,allowance:t}=await he();g({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${L(ku(e))}
387
- `)})}function ku(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?S("read-only"):n.state==="ready"?n.state:S(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],je(n.lastConnectionAt)])]}async function Nr(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await Et(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(y(` ${a}`)))}})}e.link&&ot(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;g({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
388
- `),d(""),d(`Ready. ${S(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 fa(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Pt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(y(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new k("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ga(e){let t=await b("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
389
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function ha(){let{teams:e}=await Sn();g({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${L([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
390
- `)})}async function ya(e){let{teams:t}=await Sn(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}Y();W();async function wa(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
391
- `),d(""),d(`${S(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(y(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function ba(){let{tokens:e}=await b("cloud-token-list");g({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${L([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?S(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",je(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
392
- `)})}async function _a(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);g(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}Y();le();W();async function Sa(e){let t=await ue(e);g({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${va(t.appKey,t.serverKey)}
393
- `),$u(t.keysIssuedAt)})}async function ka(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 f("keys rotate needs --force, because this cannot be undone");let n=_n(await b("cloud-project-rotate-jwt",{ref:e}));g(n,()=>{process.stdout.write(`${va(n.appKey,n.serverKey)}
394
- `),d(""),d(n.breaks),d(y(" snoutdata keys --json to read them again later"))})}function va(e,t){return L([["KEY","VALUE"],["app key",e],["server key",t]])}function $u(e){d(""),d(`The ${S("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${S("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(y(` issued ${e.slice(0,10)}`))}Y();le();W();import{readdir as Eu,readFile as Au,stat as Ru}from"node:fs/promises";import{join as Cr,relative as Tu,sep as Ou}from"node:path";var xa=["functions"];async function jr(e){try{return(await Ru(e)).isDirectory()}catch{return!1}}async function Nu(e,t){if(t){if(!await jr(t))throw new f(`${t} is not a directory`);return t}for(let n of xa){let r=Cr(n,e);if(await jr(r))return r}throw new f(`no folder found for ${e} at ${xa[0]}/${e}; use --dir to say where it is.`)}async function $a(e){let t=[];async function n(r){for(let o of await Eu(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Cr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Au(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new f(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:Tu(e,s).split(Ou).join("/"),text:l})}}return await n(e),t}async function ju(e,t,n){let r=await $a(t);if(r.length===0)throw new f(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new f(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Cr(t,"..","_shared");if(!await jr(s))return{entrypoint:o,files:r};let a=(await $a(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function Dr(e,t,n){Xe(t);let r=await Nu(t,n.dir),{entrypoint:o,files:s}=await ju(t,r,n.entrypoint),a=Qs({entrypoint:o,files:s});return{answer:await b("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function Ea(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Dr(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${S(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d(S(" Anybody who knows this URL can run it. No API key is required.")),d(y(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(y(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Ir(e){return b("cloud-project-functions",{ref:e})}function Mr(e,t){return Xe(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function Lr(e,t,n){if(Xe(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new f("functions size needs --memory MB, --concurrency N, or --reset");return b("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Cu(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 Aa(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 Ra(e,t,n){let r=await Lr(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?Cu(o,r.limit):null;d(`${S(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(y(" The next request runs at it; workers already running finish what they hold."));let a=Aa(r.limit);a&&d(y(` ${a}`))})}async function Ta(e){let t=await Ir(e);g(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(y(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${L([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":S("open"),r.bytes===null?"-":`${Math.max(1,Math.round(r.bytes/1024))} KB`,r.memoryMb==null?"-":`${r.memoryMb} MB`,r.concurrency==null?"-":String(r.concurrency),r.url])])}
395
- `),t.limit.maxFunctions!==null&&d(y(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Aa(t.limit);n&&d(y(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Pa(e){let t=e.dropped_before>0?y(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?y(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?S("error "):"";return`${y(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Oa(e,t,n){let r=a=>b("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(g({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(y(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Pa(a)}
396
- `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(l=>setTimeout(l,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let l of a)process.stdout.write(`${Pa(l)}
397
- `),s=l.at}}async function Na(e,t){let n=await Mr(e,t);g(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}Y();W();import{spawn as Gu}from"node:child_process";import{createReadStream as qu}from"node:fs";import{mkdtemp as Ku,rm as Ju,stat as En}from"node:fs/promises";import{request as zu}from"node:http";import{request as Wu}from"node:https";import{tmpdir as Vu}from"node:os";import{basename as Yu,dirname as Xu,join as Fr}from"node:path";var Iu=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Mu(e){return Iu.test(e)}function ja(e){return Mu(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 Lu=/^sha256:[0-9a-f]{64}$/,Uu=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,Fu=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,Bu=/^(?:[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 Hu(e){return Lu.test(e)}function Ca(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),!Hu(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),l=n.lastIndexOf(":");if(l>a&&(s=n.slice(l+1),n=n.slice(0,l),!Uu.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",c=i[0]??"";return i.length>1&&(c.includes(".")||c.includes(":")||c==="localhost")&&(u=c.toLowerCase(),i.shift()),Bu.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=>!Fu.test(m))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var fw=["application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json"].join(", ");var gw=1024**3;var Zu=new Set(["pending","pulling","starting","restarting"]),Qu=5*6e4;function Rn(e){let t=ja(e);if(t)throw new Error(t)}function Br(e,t,n){Rn(t);let r=Ca(n.image);if("error"in r)throw new Error(r.error);return b("cloud-project-containers",{ref:e,action:"deploy",name:t,image:n.image,...n.port===void 0?{}:{port:n.port},...n.registryUser?{registryUser:n.registryUser,registryPassword:n.registryPassword??""}:{},...n.clearCredential?{clearCredential:!0}:{}})}function Tn(e){return b("cloud-project-containers",{ref:e})}function Hr(e,t){return Rn(t),b("cloud-project-containers",{ref:e,action:"delete",name:t})}function Gr(e,t){return Rn(t),b("cloud-project-containers",{ref:e,action:"restart",name:t})}function An(e,t){return b("cloud-project-containers",{ref:e,action:"logs",...t.name?{name:t.name}:{},...t.errorsOnly?{errorsOnly:!0}:{},...t.since?{since:t.since}:{},limit:t.limit??100})}async function La(e,t){let n=Date.now()+Qu,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await Tn(e)).containers.find(o=>o.name===t)??null,!r||!Zu.has(r.status))return r;return r}function qr(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 Ua(e,t,n){await Fa(e,t,await Br(e,t,n),n.wait!==!1)}async function Fa(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(y(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await La(e,t)??o),g({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${S(t)} deployed.`);return}d(`${S(t)} ${qr(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(y(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d(S(" Anything written inside the container is lost when it restarts or is redeployed.")),d(y(" Keep what matters in your database.")),n.allowance&&d(y(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var em=2*1024**3,tm=3*6e4;async function nm(e){let t;try{t=await En(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 En(Fr(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Xu(e),file:e}}function rm(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function Kr(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 om(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?Kr(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 sm(e){return new Promise((t,n)=>{let r=Gu("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function am(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?zu:Wu)(s,{method:"PUT",headers:{"content-type":"application/octet-stream","content-length":String(n)}});a.on("response",i=>{let u=[];i.on("data",c=>u.push(c)),i.on("end",()=>r({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",o)}),a.on("error",o);let l=qu(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var Ba={docker:sm,scratch:()=>Ku(Fr(Vu(),"snoutdata-build-")),remove:e=>Ju(e,{recursive:!0,force:!0}),size:async e=>(await En(e)).size,upload:async(e,t)=>am(e,t,(await En(t)).size),ask:e=>b("cloud-project-containers",e),say:e=>d(y(e)),now:()=>Date.now()};function Da(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function Ia(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 im(e,t,n,r=Ba){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 Ia(r,e,t),s=await r.scratch();try{let a=Fr(s,"image.tar");if(r.say(`${t}: building ${n.file?Yu(n.file):n.context} for linux/arm64`),await r.docker(rm({...n,archive:a}))!==0)throw new Error("the build failed; its output is above. If it says the docker driver cannot export OCI, turn on the containerd image store, or run: docker buildx create --use");let l=await r.size(a).catch(()=>0);if(l<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(l>em)throw new Error(`the image is ${Da(l)}, and a container's image may be at most 1 GB compressed. A slimmer base (alpine, distroless, -slim) usually fixes it.`);Date.parse(o.expiresAt)-r.now()>tm||(o=await Ia(r,e,t)),r.say(`${t}: uploading the image (${Da(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(om(i.status,i.body));let u="";try{u=String(JSON.parse(i.body).digest??"")}catch{}if(!/^sha256:[0-9a-f]{64}$/.test(u))throw new Error("the upload finished without saying what it stored, so there is nothing to deploy; build again.");return u}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(Kr(o instanceof Error?o.message:String(o)))}}async function Ha(e,t,n,r=Ba){Rn(t);let o=await nm(n.path),s=await im(e,t,o,r),a;try{a=await r.ask({ref:e,action:"deploy",name:t,pushed:s,...n.port===void 0?{}:{port:n.port}})}catch(l){throw new Error(Kr(l instanceof Error?l.message:String(l)))}await Fa(e,t,a,n.wait!==!1)}async function Ga(e){let t=await Tn(e);g(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(y(" Containers run on the Plus and Pro plans.")):d(y(" snoutdata containers deploy <name> <image>"));return}d(L([["name","status","image","url"],...t.containers.map(r=>[r.name,qr(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(y(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(y(` Sent this month: ${Ur(n.usedBytes)} of ${Ur(n.allowanceBytes)}. At ${Ur(n.allowanceBytes)} the project's containers stop until the 1st.`))})}function Ur(e){return`${Math.round(e/1024**3*10)/10} GB`}async function qa(e,t){await Gr(e,t);let n=await La(e,t);g({ref:e,containers:n?[n]:[]},()=>{d(n?`${S(t)} ${qr(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Ka(e,t){let n=await Hr(e,t);g(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function Ma(e){return`${y(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?S(e.message):e.message}`}async function Ja(e,t,n){let r=((await An(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(g({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(y(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${Ma(s)}
398
- `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await An(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${Ma(a)}
399
- `),o=a.at}}Y();le();W();function Wa(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function On(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Va(e,t){if(e.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await On()}]}return e.map(n=>{let r=Wa(n);if(!r)throw new f(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function Ya(e,t,n){if(t.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await On()})}else for(let s of t){let a=Wa(s);if(!a)throw new f(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=At(s.name);if(a)throw new f(a)}let o=null;for(let s of r)o=await b("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)d(`${S(s.name)} set`);d(y(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(y(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function Xa(e,t){let n=At(t);if(n)throw new f(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{d(`${t} is gone.`)})}async function Za(e){let t=await b("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(y(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${L([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,je(n.updatedAt)])])}
400
- `),d(y("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as It}from"node:child_process";import{appendFileSync as _i,existsSync as Ln,readFileSync as Si,statSync as Wm,unlinkSync as Vm,writeFileSync as mt}from"node:fs";import{dirname as Ym,join as Un}from"node:path";import{tmpdir as Xm}from"node:os";import{open as ki}from"node:fs/promises";function Qa(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 ei(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 ti(e){let t=e.split(`
401
- `).filter(r=>/^pg_restore: error:/.test(r.trim())),n=t.filter(r=>!/must be owner of extension/.test(r));return{total:t.length,harmless:t.length-n.length,fatal:n}}var Jr={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},lm=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Nn=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"]),dm=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,um={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},mm=new Set(["push.settings"]),pm=["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 fm(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=pm.find(i=>n.startsWith(`${i} `))??n.split(" ")[0],o=n.slice(r.length).trim().split(" "),s=o[0]??"-",a=o.length>2?o[o.length-1]:"",l=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:l,owner:a}}function gm(e){let t=new Map,n=o=>o.replace(/"/g,""),r=/ALTER TABLE (?:ONLY )?([\w."$]+)\s+ADD CONSTRAINT [^;]*?FOREIGN KEY \([^)]*\) REFERENCES ([\w."$]+)\(/g;for(let o of e.matchAll(r)){let s=n(o[1]),a=n(o[2]);if(s!==a){let l=t.get(s)??new Set;l.add(a),t.set(s,l)}}return t}function ni(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
402
- `)){let u=fm(i);if(!u)continue;let{type:c,schema:m,name:h,owner:x}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let V=/^(schema_migrations|migrations)$/.test(h)&&Nn.has(m);if(/realtime/.test(m)||m==="snout_live"||V||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(m==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let F=um[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(Nn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?Nn.has(h):Nn.has(m))||dm.test(h)||lm.test(x)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=gm(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(h=>h.type==="TABLE DATA"&&mm.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),m=u.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:c,tables:m,lines:hm(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function hm(e,t){let n=l=>`${l.schema}.${l.name}`,r=new Set(e.map(n)),o=new Set,s=[],a=[...e];for(;a.length>0;){let i=a.find(u=>[...t.get(n(u))??[]].every(c=>!r.has(c)||o.has(c)||c===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}Y();le();W();Y();oe();var Wr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],oi=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Tt(e){return e==="done"||e==="failed"||e==="cancelled"}function Ot(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new k("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Ae(Sm(e.project))}let n=e.admin??ri(t.LEPIS_ADMIN_URL);if(!n)throw new k("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??ri(t.LEPIS_ADMIN_TOKEN);if(!r)throw new k("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new zr(ym(n),r)}function ri(e){return e===void 0||e===""?void 0:e}function ym(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new k("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new k("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new k("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!wm(n.hostname))throw new k("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 wm(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var bm=6e4,zr=class{target;#t;#n;constructor(t,n){this.#t=t,this.#n=n,this.target=t.toString().replace(/\/$/,"")}status(){return this.#e("GET","/v1/status")}ops(){return this.#e("GET","/v1/ops")}plan(t){return this.#e("POST","/v1/plan",t)}submit(t){return this.#e("POST","/v1/jobs",t)}async jobs(){return(await this.#e("GET","/v1/jobs")).jobs}job(t){return this.#e("GET",`/v1/jobs/${t}`)}cancel(t){return this.#e("POST",`/v1/jobs/${t}/cancel`)}resume(t){return this.#e("POST",`/v1/jobs/${t}/resume`)}settings(t){return this.#e("POST","/v1/settings",t)}advice(t){return this.#e("GET",t===void 0?"/v1/advice":`/v1/advice?sample_ms=${Math.max(0,Math.floor(t))}`)}async#e(t,n,r){let o=`${this.target}${n}`,s;try{s=await fetch(o,{method:t,headers:{authorization:`Bearer ${this.#n}`,accept:"application/json",...t==="POST"?{"content-type":"application/json"}:{}},...t==="POST"?{body:JSON.stringify(r??{})}:{},signal:AbortSignal.timeout(bm)})}catch(i){let u=tn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new k(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${c}`)}let a=await s.text(),l;try{l=a===""?{}:JSON.parse(a)}catch{throw new k("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw si(s.status,l);return l}},_m={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function si(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?_m[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":We(e));return s==="not-signed-in"?new k(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new k(s,r,o)}function Sm(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new k("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var km={"node.add":"add-node","node.attach":"attach","node.drain":"drain","node.remove":"remove","keyspace.create":"keyspace","table.distribute":"distribute","table.reference":"reference","table.global":"global","range.split":"split","range.merge":"merge","range.move":"move","tenant.pin":"pin",rebalance:"rebalance",scale:"scale",verify:"verify",cleanup:"cleanup","keyspace.drop":"drop-keyspace",dissolve:"disable"},vm="cloud-project-shards",Ae=class{target;ref;#t;constructor(t,n=b){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:He(o),answer:o}}async jobs(){return(await this.#e({action:"jobs"})).jobs}job(t){return this.#e({action:"jobs",id:t})}cancel(t){return this.#e({action:"cancel",id:t})}resume(t){return this.#e({action:"resume",id:t})}settings(t){return this.#e({action:"settings",settings:t})}advice(t){return this.#e({action:"advice",...t===void 0?{}:{sampleMs:Math.max(0,Math.floor(t))}})}enable(t){return this.#e({action:"enable",...t?{plan:!0}:{}})}spread(t,n){return this.#e({action:"spread",request:t,...n?{plan:!0}:{}})}retry(t){return this.#e({action:"retry",...t})}disable(t){return this.#e({action:"disable",...t?{plan:!0}:{}})}replica(t,n,r){return this.#e({action:n?"remove-replica":"add-replica",node:t,...r?{plan:!0}:{}})}async alerts(){return(await this.status()).cloud?.alerts??[]}acknowledge(t){return this.#e({action:"acknowledge",alert:t})}#n(t){let n=km[t.op];if(!n)throw new k("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 k("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 k("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(vm,{ref:this.ref,...t})}catch(n){throw xm(n)}}};function He(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 xm(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return si(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":We(e.status);return new k(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function jt(e){if(Array.isArray(e))return`[${e.map(jt).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)}:${jt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Xr=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
403
- ${jt(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
404
- ${jt(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
405
- ${jt(t)}`)}},$m=new Xr,Zr=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],Pm="2024-11-05";function M(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var P={type:"string"},ai={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:[...Zr]}},required:["op"],additionalProperties:!0},jn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},Ge={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)."},Dt=[{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:M({})},{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:M({})},{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:M({name:P,region:P,teamId:P},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:M({ref:P},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:M({ref:P},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:M({ref:P},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:M({ref:P,dir:P,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:M({ref:P,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:M({ref:P},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:M({ref:P},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:M({ref:P},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:M({})},{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:M({})},{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:M({name:P,expires:P,project:P},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:M({id:P},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/v1/fn/<name>. Reads functions/<name> unless dir says otherwise. Deno, so no build step and no node_modules. Set openToAnyone ONLY for a webhook receiver whose sender cannot send an API key: it makes the URL callable by anybody who knows it, and the function must then check the sender's signature itself.",inputSchema:M({ref:P,name:P,dir:P,entrypoint:P,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:M({ref:P},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:M({ref:P,name:P,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:M({ref:P,name:P},["ref","name"])},{name:"list_containers",description:"The project's Snout Containers: each one's status (pending, pulling, starting, running, restarting, failed, stopped), why when it is not running, its image and pinned digest, restarts, and its public URL. allowed=false means the plan no longer covers it and it is stopped. Also the plan's allowance. Costs nothing.",inputSchema:M({ref:P},["ref"])},{name:"deploy_container",description:"Run a Docker image all the time beside the project, at https://<ref>.api.snoutdata.com/containers/v1/<name>/ (no API key needed there: the container authenticates its own callers). The image tag is pinned to the digest it points at now and must include linux/arm64. It listens on PORT (port, default 8080) and gets DATABASE_URL and the project's secrets in its environment. STATELESS: anything written inside it is lost on every restart or redeploy, so state belongs in the database. Plus and Pro plans only. A private image needs a person's first deploy with the CLI (no tool takes a registry token); a redeploy reuses the stored login. Returns at once: poll list_containers until the status is running or failed.",inputSchema:M({ref:P,name:P,image:P,port:{type:"integer"}},["ref","name","image"])},{name:"restart_container",description:"Start one container again from its image. Everything written inside it is lost, which is also how to clear a container stuck in a bad state. Poll list_containers to see it come back.",inputSchema:M({ref:P,name:P},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:M({ref:P,name:P},["ref","name"])},{name:"container_logs",description:"What a project's containers printed to stdout and stderr in the last seven days, newest first: each line's time, container, stream and text. Lines arrive about a minute after they are printed. Use it when list_containers says failed or restarting. name narrows to one container, errorsOnly to stderr, limit is at most 500 (default 100). Costs nothing.",inputSchema:M({ref:P,name:P,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:M({ref:P},["ref"])},{name:"function_logs",description:"What a project's functions printed with console.log and console.error in the last seven days, newest first: each line's time, function, level ('error' for console.error and console.warn) and text. Lines arrive about a minute after they are printed. Use it after deploying or calling a function to see why it failed. name narrows to one function, errorsOnly to errors, limit is at most 500 (default 100). Costs nothing.",inputSchema:M({ref:P,name:P,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"get_project",description:"Everything about one project in one call: its state, region and size, which products are on (auth, storage, data-api), the names of its deployed functions and function secrets, and its custom domains. Never a password, a key or a download link (export_status has the link to the last export).",inputSchema:M({ref:P},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:M({ref:P},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. All three are on every plan, including free.",inputSchema:M({ref:P,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:M({ref:P},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:M({ref:P,hostname:P},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:M({ref:P,hostname:P},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:M({ref:P,hostname:P},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:M({ref:P},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:M({ref:P,at:P,name:P},["ref","at"])},{name:"cluster_status",description:"A Lepis cluster (one Postgres database spread over several nodes): a Cloud project's with project, else the router this server was started against with LEPIS_ADMIN_URL. Its nodes, keyspaces and their ranges (a range is keyspace:lo; bounds are strings), tables, routers, unfinished jobs and settings; for a Cloud project also whether it is a cluster, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:M({project:Ge})},{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:M({project:Ge,sampleMs:{type:"integer"}})},{name:"cluster_plan",description:"The dry run of one cluster operation: its steps, what moves where, rows, bytes, the estimated copy time and the expected write pause. Changes nothing. Planning an operation is what lets cluster_run run it.",inputSchema:M({project:Ge,operation:ai},["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:M({project:Ge,operation:ai,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:M({project:Ge,id:jn})},{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:M({project:Ge,id:jn,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:M({project:Ge,id:jn},["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:M({project:Ge,id:jn},["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:M({ref:P},["ref"])}],Em=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Am=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),ii=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Qr(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Qr)};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 eo(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 Rm(e){return{...e,annotations:Em.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Am.has(e.name)}}}function Tm(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Ct(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Cn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function _(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Ct(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Om(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Nt(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 Vr(e){return{id:e.id,op:e.op,state:e.state,finished:Tt(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 ci(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"||!Zr.includes(r.op))return`${t}: operation.op must be one of ${Zr.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 Yr(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 Nm(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var jm="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 Cm(e,t,n,r,o){let s,a=typeof n.project=="string"&&n.project.trim()!==""?n.project.trim():void 0;try{if(!r.shards)return _(e,jm,!0);s=r.shards(a)}catch(u){return _(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??$m,i=a??"";try{switch(t){case"cluster_status":return _(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let m of c.advice)m.plan&&l.remember(m.request,i);return _(e,{summary:c.summary,advice:c.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:Nt(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with cluster_plan.`}:{},...m.plan_error?{plan_error:m.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=ci(n,t);if(typeof u=="string")return _(e,u,!0);let c=await s.plan(u);return l.remember(u,i),_(e,{plan:Nt(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=ci(n,t);if(typeof u=="string")return _(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let m=await s.plan(u);l.remember(u,i);let h=n.confirm===!0;return _(e,{ran:!1,plan:Nt(m),next:h?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},h)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?_(e,{ran:!0,done:c.done,answer:c.answer}):_(e,{ran:!0,job:c.job,plan:Nt(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return _(e,{jobs:await s.jobs()});let u=Yr(n);if(u===null)return _(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return _(e,{...Vr(c),plan:c.plan&&Array.isArray(c.plan.steps)?Nt(c.plan):null})}case"cluster_watch_job":{let u=Yr(n);if(u===null)return _(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+c*1e3;for(;;){let h=await s.job(u);if(Tt(h.state))return _(e,Vr(h),h.state!=="done");if(Date.now()>=m)return _(e,{...Vr(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(x=>setTimeout(x,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=Yr(n);if(u===null)return _(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return _(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return Cn(e,-32602,`unknown tool: ${t}`)}}catch(u){return _(e,Nm(u),!0)}}function K(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function li(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 Ct(r,{protocolVersion:Pm,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Ct(r,{});case"tools/list":return Ct(r,{tools:Tm([...Dt.map(Rm),...n.borrowed??[]])});case"tools/call":return Dm(r,s,t,n);default:return Cn(r,-32601,`unknown method: ${o}`)}}async function Dm(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Dt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return Cn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"];if(o.startsWith("cluster_")&&Dt.some(c=>c.name===o))return Cm(e,o,s,n,r);let i=K(s,"ref");if(l.includes(o)&&!i)return _(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!ii.includes(o))return _(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${ii.join(", ")}.`,!0);try{switch(o){case"whoami":return _(e,await n.whoami());case"list_projects":return _(e,Qr(await n.listProjects()));case"create_project":{let c=K(s,"name");return c?_(e,await n.createProject({name:c,region:K(s,"region")??void 0,teamId:K(s,"teamId")??void 0})):_(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return _(e,{url:c.uri,state:c.state,wakesInstantly:c.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return _(e,await n.setProjectState("pause",i));case"resume_project":return _(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return _(e,await n.usage(i,c))}case"export_status":return _(e,await n.exportStatus(i));case"start_export":return _(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),m=await n.connection(i);return _(e,{url:m.uri,appliesIn:c.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return _(e,await n.listTeams());case"get_project":return _(e,Qr(await n.getProject(i)));case"list_products":return _(e,await n.getProducts(i));case"set_product":{let c=K(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?_(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?_(e,"set_product needs enabled: true or false.",!0):_(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return _(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=K(s,"hostname");return c?_(e,await n.domainAction(i,o.split("_")[0],c)):_(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),m=c&&c.restore?eo(i,c.restore):null;return _(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=K(s,"at");return c?_(e,await n.restoreTo(i,c,K(s,"name")??void 0)):_(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?_(e,"deploy_function needs a project ref and a function name.",!0):_(e,await n.deployFunction(c,m,{dir:K(s,"dir")??void 0,entrypoint:K(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=K(s,"ref");return c?_(e,await n.listFunctions(c)):_(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=K(s,"ref"),m=K(s,"name");if(!c||!m)return _(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,x=typeof s.concurrency=="number"?s.concurrency:void 0,U=s.reset===!0;return!U&&h===void 0&&x===void 0?_(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):_(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:x,reset:U}))}case"delete_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?_(e,"delete_function needs a project ref and a function name.",!0):_(e,await n.deleteFunction(c,m))}case"list_containers":{let c=K(s,"ref");return c?_(e,await n.listContainers(c)):_(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=K(s,"ref"),m=K(s,"name"),h=K(s,"image");if(!c||!m||!h)return _(e,"deploy_container needs a project ref, a container name and an image.",!0);let x=typeof s.port=="number"?s.port:void 0;return _(e,await n.deployContainer(c,m,{image:h,...x===void 0?{}:{port:x}}))}case"restart_container":case"delete_container":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?_(e,`${o} needs a project ref and a container name.`,!0):_(e,o==="restart_container"?await n.restartContainer(c,m):await n.deleteContainer(c,m))}case"container_logs":{let c=K(s,"ref");if(!c)return _(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.containerLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_function_secrets":{let c=K(s,"ref");return c?_(e,await n.listFunctionSecrets(c)):_(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=K(s,"ref");if(!c)return _(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.functionLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return _(e,await n.listTokens());case"create_token":{let c=K(s,"name");if(!c)return _(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=K(s,"expires"),h=m?Om(m):void 0;return h===null?_(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):_(e,await n.createToken(c,h,K(s,"project")??void 0))}case"revoke_token":{let c=K(s,"id");return c?_(e,await n.revokeToken(c)):_(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return _(e,await n.pushMigrations(i,{dir:K(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?_(e,await n.setProjectState("delete",i)):_(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(c=>c.name===o)?n.callBorrowed?Ct(e,await n.callBorrowed(o,s)):_(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Cn(e,-32602,`unknown tool: ${o}`)}}catch(c){return _(e,c instanceof Error?c.message:String(c),!0)}}function di(e){let t=e.split(`
406
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Dn=["auth","storage","data-api","push"];function Ze(e){return{error:e instanceof Error?e.message:String(e)}}async function ut(e){let[t,n,r,o]=await Promise.all([b("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Ze),b("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Ze),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Ze),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Ze)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Im}}var Im={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function ui(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Dn.includes(t))throw new f(`say which product: ${Dn.join(", ")}`);return t}async function to(e,t,n){if(t==="auth"){let r=await b("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await b("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await b(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function mi(e){let t=await ut(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?S("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${L([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"not on this plan":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":""),n("realtime",t.realtime,"always on: signals, roster; table changes on Plus and Pro")])}
407
- `);let s=l=>!!(l&&typeof l=="object"&&"enabled"in l&&!l.enabled),a=["auth","storage","data-api","push"].find(l=>s(l==="data-api"?o:t[l]));a&&d(y(` snoutdata products enable ${a} --ref ${e}`))})}async function pi(e,t,n){let r=await to(e,t,n);g(r,()=>d(r.note))}async function In(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function no(e,t,n){if(!n)throw new f(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await b("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Mm(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(`${L(n)}
408
- `)}async function fi(e,t,n){if(t===void 0||t==="list"){let o=await In(e);g(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${L([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":S("no"),s.certificate?.state??""])])}
409
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new f(`unknown command: domains ${t}`);let r=await no(e,t,n??"");g(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Mm(r.domain,e)),r.next&&d(r.next)})}async function ro(e){return b("cloud-project-restore",{ref:e},"GET")}async function oo(e,t,n){if(Number.isNaN(Date.parse(t)))throw new f(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function gi(e){let t=await ro(e);g(t,()=>{let n=t.restore,r=eo(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(y(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function hi(e,t,n){let r=await oo(e,t,n);g(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${S(r.project.name)} (${r.project.ref}).`),d(y(" snoutdata projects list to see when it is ready"))})}async function so(e){let{projects:t}=await he(),n=t.find(l=>l.ref===e);if(!n)throw new f(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([ut(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Ze),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Ze),In(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Ze)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function yi(e){let t=await so(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${L([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (signals and the roster; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
410
- `)})}Y();oe();Ve();Y();W();import{existsSync as Lm,mkdirSync as Um,readFileSync as Fm,renameSync as Bm,writeFileSync as Hm}from"node:fs";import{homedir as ao}from"node:os";import{dirname as Gm,join as qm}from"node:path";import{rootCertificates as Km}from"node:tls";var Jm="SNOUTDATA_DB_SSLMODE";function zm(e=ao()){let t=qm(e,".snoutdata","ca-roots.pem"),n=`${Km.join(`
385
+ `:""))}async function ua(e,t){for(let r of t){let o=Rt(r.name);if(o)throw new f(o)}let n=jr(e);for(let r of t)n.set(r.name,r.value);da(e,n),await $n(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${k(r.name)} set in ${En(e)}`)})}function An(e){return{ref:e.ref,local:e.folder,secrets:[...jr(e).keys()].sort().map(t=>({name:t}))}}function ma(e){let t=An(e),n=t.secrets.map(r=>r.name);g(t,()=>{d(n.length?n.join(`
386
+ `):`No function secrets in ${En(e)}.`)})}async function pa(e,t){let n=jr(e);if(!n.delete(t))throw new v("not-found",`${t} is not set for this local project`);da(e,n),await $n(e),g({ref:e.ref,unset:t},()=>d(`${t} unset.`))}se();Ye();Me();Sr();cn();W();Sn();Y();Y();se();Me();W();async function fa(){let{projects:e,allowance:t}=await he();g({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${M(Ru(e))}
387
+ `)})}function Ru(e){let t=e.some(n=>typeof n.postgresVersion=="number");return[["REF","NAME","STATE","REGION",...t?["POSTGRES"]:[],"LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?k("read-only"):n.state==="ready"?n.state:k(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Ce(n.lastConnectionAt)])]}async function Cr(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await At(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&st(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;g({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
388
+ `),d(""),d(`Ready. ${k(n)} in ${r.region}, at ${r.host}.`),d(e.showUrl?"The password is stored for you: `snoutdata db url` prints this again.":"`snoutdata db url` prints the connection string (it holds the password, so it is not shown here).")})}async function ga(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Et(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new v("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ha(e){let t=await b("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
389
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function ya(){let{teams:e}=await vn();g({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${M([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
390
+ `)})}async function wa(e){let{teams:t}=await vn(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}Y();W();async function ba(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
391
+ `),d(""),d(`${k(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),d(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),d("This is the only time it is shown: only its hash is stored."),d(w(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function _a(){let{tokens:e}=await b("cloud-token-list");g({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${M([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?k(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Ce(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
392
+ `)})}async function Sa(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);g(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}Y();ue();W();async function ka(e){let t=await pe(e);g({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${xa(t.appKey,t.serverKey)}
393
+ `),Nu(t.keysIssuedAt)})}async function va(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 f("keys rotate needs --force, because this cannot be undone");let n=kn(await b("cloud-project-rotate-jwt",{ref:e}));g(n,()=>{process.stdout.write(`${xa(n.appKey,n.serverKey)}
394
+ `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function xa(e,t){return M([["KEY","VALUE"],["app key",e],["server key",t]])}function Nu(e){d(""),d(`The ${k("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${k("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}Y();ue();W();import{readdir as Cu,readFile as Iu,stat as Du}from"node:fs/promises";import{join as Dr,relative as Mu,sep as Lu}from"node:path";var $a=["functions"];async function Ir(e){try{return(await Du(e)).isDirectory()}catch{return!1}}async function Uu(e,t){if(t){if(!await Ir(t))throw new f(`${t} is not a directory`);return t}for(let n of $a){let r=Dr(n,e);if(await Ir(r))return r}throw new f(`no folder found for ${e} at ${$a[0]}/${e}; use --dir to say where it is.`)}async function Pa(e){let t=[];async function n(r){for(let o of await Cu(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Dr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Iu(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new f(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:Mu(e,s).split(Lu).join("/"),text:l})}}return await n(e),t}async function Fu(e,t,n){let r=await Pa(t);if(r.length===0)throw new f(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new f(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Dr(t,"..","_shared");if(!await Ir(s))return{entrypoint:o,files:r};let a=(await Pa(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function Mr(e,t,n){Ze(t);let r=await Uu(t,n.dir),{entrypoint:o,files:s}=await Fu(t,r,n.entrypoint),a=ea({entrypoint:o,files:s});return{answer:await b("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function Aa(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Mr(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${k(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d(k(" Anybody who knows this URL can run it. No API key is required.")),d(w(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(w(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Lr(e){return b("cloud-project-functions",{ref:e})}function Ur(e,t){return Ze(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function Fr(e,t,n){if(Ze(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new f("functions size needs --memory MB, --concurrency N, or --reset");return b("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Bu(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 Ra(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 Ta(e,t,n){let r=await Fr(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?Bu(o,r.limit):null;d(`${k(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(w(" The next request runs at it; workers already running finish what they hold."));let a=Ra(r.limit);a&&d(w(` ${a}`))})}async function Oa(e){let t=await Lr(e);g(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${M([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":k("open"),r.bytes===null?"-":`${Math.max(1,Math.round(r.bytes/1024))} KB`,r.memoryMb==null?"-":`${r.memoryMb} MB`,r.concurrency==null?"-":String(r.concurrency),r.url])])}
395
+ `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Ra(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Ea(e){let t=e.dropped_before>0?w(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?w(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?k("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Na(e,t,n){let r=a=>b("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(g({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Ea(a)}
396
+ `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(l=>setTimeout(l,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let l of a)process.stdout.write(`${Ea(l)}
397
+ `),s=l.at}}async function ja(e,t){let n=await Ur(e,t);g(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}Y();W();import{spawn as Yu}from"node:child_process";import{createReadStream as Xu}from"node:fs";import{mkdtemp as Zu,rm as Qu,stat as Rn}from"node:fs/promises";import{request as em}from"node:http";import{request as tm}from"node:https";import{tmpdir as nm}from"node:os";import{basename as rm,dirname as om,join as Hr}from"node:path";var Gu=/^[a-z0-9][a-z0-9_-]{0,31}$/;function qu(e){return Gu.test(e)}function Ca(e){return qu(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 Ku=/^sha256:[0-9a-f]{64}$/,Ju=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,zu=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,Wu=/^(?:[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 Vu(e){return Ku.test(e)}function Ia(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),!Vu(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),l=n.lastIndexOf(":");if(l>a&&(s=n.slice(l+1),n=n.slice(0,l),!Ju.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",c=i[0]??"";return i.length>1&&(c.includes(".")||c.includes(":")||c==="localhost")&&(u=c.toLowerCase(),i.shift()),Wu.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=>!zu.test(m))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var $w=["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(", ");var Pw=1024**3;var sm=new Set(["pending","pulling","starting","restarting"]),am=5*6e4;function On(e){let t=Ca(e);if(t)throw new Error(t)}function Gr(e,t,n){On(t);let r=Ia(n.image);if("error"in r)throw new Error(r.error);return b("cloud-project-containers",{ref:e,action:"deploy",name:t,image:n.image,...n.port===void 0?{}:{port:n.port},...n.registryUser?{registryUser:n.registryUser,registryPassword:n.registryPassword??""}:{},...n.clearCredential?{clearCredential:!0}:{}})}function Nn(e){return b("cloud-project-containers",{ref:e})}function qr(e,t){return On(t),b("cloud-project-containers",{ref:e,action:"delete",name:t})}function Kr(e,t){return On(t),b("cloud-project-containers",{ref:e,action:"restart",name:t})}function Tn(e,t){return b("cloud-project-containers",{ref:e,action:"logs",...t.name?{name:t.name}:{},...t.errorsOnly?{errorsOnly:!0}:{},...t.since?{since:t.since}:{},limit:t.limit??100})}async function Ua(e,t){let n=Date.now()+am,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await Nn(e)).containers.find(o=>o.name===t)??null,!r||!sm.has(r.status))return r;return r}function Jr(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 Fa(e,t,n){await Ba(e,t,await Gr(e,t,n),n.wait!==!1)}async function Ba(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(w(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await Ua(e,t)??o),g({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${k(t)} deployed.`);return}d(`${k(t)} ${Jr(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(w(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d(k(" Anything written inside the container is lost when it restarts or is redeployed.")),d(w(" Keep what matters in your database.")),n.allowance&&d(w(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var im=2*1024**3,cm=3*6e4;async function lm(e){let t;try{t=await Rn(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 Rn(Hr(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:om(e),file:e}}function dm(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function zr(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 um(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?zr(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 mm(e){return new Promise((t,n)=>{let r=Yu("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function pm(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?em:tm)(s,{method:"PUT",headers:{"content-type":"application/octet-stream","content-length":String(n)}});a.on("response",i=>{let u=[];i.on("data",c=>u.push(c)),i.on("end",()=>r({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",o)}),a.on("error",o);let l=Xu(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var Ha={docker:mm,scratch:()=>Zu(Hr(nm(),"snoutdata-build-")),remove:e=>Qu(e,{recursive:!0,force:!0}),size:async e=>(await Rn(e)).size,upload:async(e,t)=>pm(e,t,(await Rn(t)).size),ask:e=>b("cloud-project-containers",e),say:e=>d(w(e)),now:()=>Date.now()};function Da(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function Ma(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 fm(e,t,n,r=Ha){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 Ma(r,e,t),s=await r.scratch();try{let a=Hr(s,"image.tar");if(r.say(`${t}: building ${n.file?rm(n.file):n.context} for linux/arm64`),await r.docker(dm({...n,archive:a}))!==0)throw new Error("the build failed; its output is above. If it says the docker driver cannot export OCI, turn on the containerd image store, or run: docker buildx create --use");let l=await r.size(a).catch(()=>0);if(l<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(l>im)throw new Error(`the image is ${Da(l)}, and a container's image may be at most 1 GB compressed. A slimmer base (alpine, distroless, -slim) usually fixes it.`);Date.parse(o.expiresAt)-r.now()>cm||(o=await Ma(r,e,t)),r.say(`${t}: uploading the image (${Da(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(um(i.status,i.body));let u="";try{u=String(JSON.parse(i.body).digest??"")}catch{}if(!/^sha256:[0-9a-f]{64}$/.test(u))throw new Error("the upload finished without saying what it stored, so there is nothing to deploy; build again.");return u}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(zr(o instanceof Error?o.message:String(o)))}}async function Ga(e,t,n,r=Ha){On(t);let o=await lm(n.path),s=await fm(e,t,o,r),a;try{a=await r.ask({ref:e,action:"deploy",name:t,pushed:s,...n.port===void 0?{}:{port:n.port}})}catch(l){throw new Error(zr(l instanceof Error?l.message:String(l)))}await Ba(e,t,a,n.wait!==!1)}async function qa(e){let t=await Nn(e);g(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(w(" Containers run on the Plus and Pro plans.")):d(w(" snoutdata containers deploy <name> <image>"));return}d(M([["name","status","image","url"],...t.containers.map(r=>[r.name,Jr(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(w(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(w(` Sent this month: ${Br(n.usedBytes)} of ${Br(n.allowanceBytes)}. At ${Br(n.allowanceBytes)} the project's containers stop until the 1st.`))})}function Br(e){return`${Math.round(e/1024**3*10)/10} GB`}async function Ka(e,t){await Kr(e,t);let n=await Ua(e,t);g({ref:e,containers:n?[n]:[]},()=>{d(n?`${k(t)} ${Jr(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Ja(e,t){let n=await qr(e,t);g(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function La(e){return`${w(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?k(e.message):e.message}`}async function za(e,t,n){let r=((await Tn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(g({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${La(s)}
398
+ `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await Tn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${La(a)}
399
+ `),o=a.at}}Y();ue();W();function Va(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function jn(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Ya(e,t){if(e.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await jn()}]}return e.map(n=>{let r=Va(n);if(!r)throw new f(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function Xa(e,t,n){if(t.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await jn()})}else for(let s of t){let a=Va(s);if(!a)throw new f(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Rt(s.name);if(a)throw new f(a)}let o=null;for(let s of r)o=await b("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)d(`${k(s.name)} set`);d(w(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(w(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function Za(e,t){let n=Rt(t);if(n)throw new f(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{d(`${t} is gone.`)})}async function Qa(e){let t=await b("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${M([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Ce(n.updatedAt)])])}
400
+ `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Mt}from"node:child_process";import{appendFileSync as ki,existsSync as Fn,readFileSync as vi,statSync as ep,unlinkSync as tp,writeFileSync as pt}from"node:fs";import{dirname as np,join as Bn}from"node:path";import{tmpdir as rp}from"node:os";import{open as xi}from"node:fs/promises";function ei(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 ti(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 ni(e){let t=e.split(`
401
+ `).filter(r=>/^pg_restore: error:/.test(r.trim())),n=t.filter(r=>!/must be owner of extension/.test(r));return{total:t.length,harmless:t.length-n.length,fatal:n}}var Wr={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},hm=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Cn=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"]),ym=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,wm={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},bm=new Set(["push.settings"]),_m=["TABLE DATA","SEQUENCE SET","SEQUENCE OWNED BY","FK CONSTRAINT","CHECK CONSTRAINT","DEFAULT ACL","EVENT TRIGGER","ROW SECURITY","INDEX ATTACH","TABLE ATTACH","MATERIALIZED VIEW DATA","MATERIALIZED VIEW","PUBLICATION TABLES IN SCHEMA","PUBLICATION TABLE","FOREIGN TABLE","FOREIGN DATA WRAPPER","FOREIGN SERVER","USER MAPPING","TEXT SEARCH CONFIGURATION","TEXT SEARCH DICTIONARY","TEXT SEARCH PARSER","TEXT SEARCH TEMPLATE","LARGE OBJECT","PROCEDURAL LANGUAGE","OPERATOR CLASS","OPERATOR FAMILY","ACCESS METHOD","EXTENDED STATISTICS","DATABASE PROPERTIES"];function Sm(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=_m.find(i=>n.startsWith(`${i} `))??n.split(" ")[0],o=n.slice(r.length).trim().split(" "),s=o[0]??"-",a=o.length>2?o[o.length-1]:"",l=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:l,owner:a}}function km(e){let t=new Map,n=o=>o.replace(/"/g,""),r=/ALTER TABLE (?:ONLY )?([\w."$]+)\s+ADD CONSTRAINT [^;]*?FOREIGN KEY \([^)]*\) REFERENCES ([\w."$]+)\(/g;for(let o of e.matchAll(r)){let s=n(o[1]),a=n(o[2]);if(s!==a){let l=t.get(s)??new Set;l.add(a),t.set(s,l)}}return t}function ri(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
402
+ `)){let u=Sm(i);if(!u)continue;let{type:c,schema:m,name:h,owner:x}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let V=/^(schema_migrations|migrations)$/.test(h)&&Cn.has(m);if(/realtime/.test(m)||m==="snout_live"||V||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(m==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let F=wm[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(Cn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?Cn.has(h):Cn.has(m))||ym.test(h)||hm.test(x)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=km(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(h=>h.type==="TABLE DATA"&&bm.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),m=u.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:c,tables:m,lines:vm(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function vm(e,t){let n=l=>`${l.schema}.${l.name}`,r=new Set(e.map(n)),o=new Set,s=[],a=[...e];for(;a.length>0;){let i=a.find(u=>[...t.get(n(u))??[]].every(c=>!r.has(c)||o.has(c)||c===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}Y();ue();se();W();Y();se();var Yr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],si=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Ot(e){return e==="done"||e==="failed"||e==="cancelled"}function Nt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new v("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Re(Am(e.project))}let n=e.admin??oi(t.LEPIS_ADMIN_URL);if(!n)throw new v("usage","cluster needs a project: --project <ref> for a SnoutData Cloud project, or --admin <url> (or LEPIS_ADMIN_URL) for the admin API of a standalone Lepis router (LEPIS_ADMIN_ADDR)");let r=e.token??oi(t.LEPIS_ADMIN_TOKEN);if(!r)throw new v("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new Vr(xm(n),r)}function oi(e){return e===void 0||e===""?void 0:e}function xm(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new v("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new v("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new v("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!$m(n.hostname))throw new v("usage",`--admin: ${n.host} is not this machine, and plain http would send the token in the clear. Use https (the router serves it when it has LEPIS_TLS_CERT), or a tunnel to 127.0.0.1`);return n.pathname=n.pathname.replace(/\/+$/,""),n.search="",n.hash="",n}function $m(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var Pm=6e4,Vr=class{target;#t;#n;constructor(t,n){this.#t=t,this.#n=n,this.target=t.toString().replace(/\/$/,"")}status(){return this.#e("GET","/v1/status")}ops(){return this.#e("GET","/v1/ops")}plan(t){return this.#e("POST","/v1/plan",t)}submit(t){return this.#e("POST","/v1/jobs",t)}async jobs(){return(await this.#e("GET","/v1/jobs")).jobs}job(t){return this.#e("GET",`/v1/jobs/${t}`)}cancel(t){return this.#e("POST",`/v1/jobs/${t}/cancel`)}resume(t){return this.#e("POST",`/v1/jobs/${t}/resume`)}settings(t){return this.#e("POST","/v1/settings",t)}advice(t){return this.#e("GET",t===void 0?"/v1/advice":`/v1/advice?sample_ms=${Math.max(0,Math.floor(t))}`)}async#e(t,n,r){let o=`${this.target}${n}`,s;try{s=await fetch(o,{method:t,headers:{authorization:`Bearer ${this.#n}`,accept:"application/json",...t==="POST"?{"content-type":"application/json"}:{}},...t==="POST"?{body:JSON.stringify(r??{})}:{},signal:AbortSignal.timeout(Pm)})}catch(i){let u=nn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new v(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${c}`)}let a=await s.text(),l;try{l=a===""?{}:JSON.parse(a)}catch{throw new v("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw ai(s.status,l);return l}},Em={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function ai(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?Em[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":Ve(e));return s==="not-signed-in"?new v(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new v(s,r,o)}function Am(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new v("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var Rm={"node.add":"add-node","node.attach":"attach","node.drain":"drain","node.remove":"remove","keyspace.create":"keyspace","table.distribute":"distribute","table.reference":"reference","table.global":"global","range.split":"split","range.merge":"merge","range.move":"move","tenant.pin":"pin",rebalance:"rebalance",scale:"scale",verify:"verify",cleanup:"cleanup","keyspace.drop":"drop-keyspace",dissolve:"disable"},Tm="cloud-project-shards",Re=class{target;ref;#t;constructor(t,n=b){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Ge(o),answer:o}}async jobs(){return(await this.#e({action:"jobs"})).jobs}job(t){return this.#e({action:"jobs",id:t})}cancel(t){return this.#e({action:"cancel",id:t})}resume(t){return this.#e({action:"resume",id:t})}settings(t){return this.#e({action:"settings",settings:t})}advice(t){return this.#e({action:"advice",...t===void 0?{}:{sampleMs:Math.max(0,Math.floor(t))}})}enable(t){return this.#e({action:"enable",...t?{plan:!0}:{}})}spread(t,n){return this.#e({action:"spread",request:t,...n?{plan:!0}:{}})}retry(t){return this.#e({action:"retry",...t})}disable(t){return this.#e({action:"disable",...t?{plan:!0}:{}})}replica(t,n,r){return this.#e({action:n?"remove-replica":"add-replica",node:t,...r?{plan:!0}:{}})}async alerts(){return(await this.status()).cloud?.alerts??[]}acknowledge(t){return this.#e({action:"acknowledge",alert:t})}#n(t){let n=Rm[t.op];if(!n)throw new v("usage",`${t.op} is not an operation a SnoutData Cloud project takes`);let r=t;if(t.op==="node.add"&&(r.host!==void 0||r.name!==void 0))throw new v("usage","in SnoutData Cloud a node is a pod the project makes: nodes add takes no name or --host. It joins the cluster on its own once it is ready.");if(t.op==="scale"&&(r.add!==void 0||r.remove!==void 0))throw new v("usage","in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");return n}async#e(t){try{return await this.#t(Tm,{ref:this.ref,...t})}catch(n){throw Om(n)}}};function Ge(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"",r=t.length===1;return e.joining===!1?`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. Attach ${r?"it":"each"} once it is ready: snoutdata cluster nodes attach <ref>.${n}`:`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. ${r?"It joins":"They join"} the cluster and take${r?"s":""} ${r?"its":"their"} share of the data on ${r?"its":"their"} own; follow it with snoutdata cluster status.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`:Array.isArray(e.queued)?`${String(e.next??"The table is being spread over the cluster.")} Steps: ${e.queued.map(String).join(", ")}. Follow it with snoutdata cluster status.`:typeof e.joining=="boolean"&&typeof e.node=="string"?e.joining?`${e.node} is joining the cluster again.`:`${e.node} is left as it is.`:e.queue==="retrying"?"The cluster is trying that step again.":typeof e.replica=="string"&&typeof e.node=="string"?`A replica of node ${e.node} is being made (${e.replica}). ${String(e.note??"It protects the node once it is following it.")}`:typeof e.changed=="boolean"&&typeof e.node=="string"&&e.sharded===void 0?e.changed?`The replica of node ${e.node} is being removed, and its project slot freed.`:`node ${e.node} has no replica. Nothing changed.`:e.acknowledged===!0?"Marked as seen. The alert stays open until what it is about is over.":e.unchanged===!0?`The project already has ${String(e.nodes)} nodes. Nothing changed.`:e.sharded===!0?e.changed===!1?"This project is already a cluster.":String(e.note??"This project is a cluster now."):e.sharded===!1?e.changed===!1?"This project is not a cluster.":String(e.note??"This project is not a cluster any more."):typeof e.next=="string"?e.next:"Done."}function Om(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return ai(e.status,{error:{code:typeof t.apiCode=="string"?t.apiCode:void 0,kind:typeof t.kind=="string"?t.kind:void 0,message:e.message,sqlstate:typeof t.sqlstate=="string"?t.sqlstate:void 0}});let n=t.code==="not-sharded"?"conflict":Ve(e.status);return new v(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Ct(e){if(Array.isArray(e))return`[${e.map(Ct).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)}:${Ct(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Qr=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
403
+ ${Ct(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
404
+ ${Ct(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
405
+ ${Ct(t)}`)}},Nm=new Qr,eo=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],jm="2024-11-05";function D(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var E={type:"string"},ii={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:[...eo]}},required:["op"],additionalProperties:!0},In={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},qe={type:"string",description:"A SnoutData Cloud project's ref (from list_projects): its cluster, as the signed-in user. Omit for the standalone router this server was started with (LEPIS_ADMIN_URL)."},Dt=[{name:"whoami",description:"Who this CLI is signed in as, and how (a session or a long-lived access token). Use it to check a credential before doing anything else.",inputSchema:D({})},{name:"list_projects",description:"Every hosted Postgres database on this account, with its ref, name, region, state (ready, paused, error), size, and whether it is read-only because it is over its plan's storage limit. Under `local`, the self-hosted projects on this machine (SnoutData Studio's, or linked with `snoutdata link --local`), whose refs work with get_connection_url, push_migrations, get_project and the function tools.",inputSchema:D({})},{name:"create_project",description:"Make a new hosted Postgres database and wait for it to be ready. Costs a project against the plan allowance; a free plan includes two. Returns its ref, which every other tool takes.",inputSchema:D({name:E,region:E,teamId:E},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:D({ref:E},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:D({ref:E},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:D({ref:E},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:D({ref:E,dir:E,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:D({ref:E,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:D({ref:E},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:D({ref:E},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:D({ref:E},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:D({})},{name:"list_tokens",description:"The long-lived sdt_ access tokens on this account: id, name, prefix, when each was made and last used. Never the token itself, which is shown once when it is made.",inputSchema:D({})},{name:"create_token",description:"Mint a long-lived sdt_ access token, for a CI job or another machine. RETURNS THE TOKEN ONCE AND NEVER AGAIN. Without `project` it is as capable as this account, so hand it over deliberately; with a project ref it reaches that one project and nothing else, which is what a CI job for one project should get. Optionally expires, as an ISO date.",inputSchema:D({name:E,expires:E,project:E},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:D({id:E},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/v1/fn/<name>. Reads functions/<name> unless dir says otherwise. Deno, so no build step and no node_modules. Set openToAnyone ONLY for a webhook receiver whose sender cannot send an API key: it makes the URL callable by anybody who knows it, and the function must then check the sender's signature itself.",inputSchema:D({ref:E,name:E,dir:E,entrypoint:E,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:D({ref:E},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:D({ref:E,name:E,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:D({ref:E,name:E},["ref","name"])},{name:"list_containers",description:"The project's Snout Containers: each one's status (pending, pulling, starting, running, restarting, failed, stopped), why when it is not running, its image and pinned digest, restarts, and its public URL. allowed=false means the plan no longer covers it and it is stopped. Also the plan's allowance. Costs nothing.",inputSchema:D({ref:E},["ref"])},{name:"deploy_container",description:"Run a Docker image all the time beside the project, at https://<ref>.api.snoutdata.com/containers/v1/<name>/ (no API key needed there: the container authenticates its own callers). The image tag is pinned to the digest it points at now and must include linux/arm64. It listens on PORT (port, default 8080) and gets DATABASE_URL and the project's secrets in its environment. STATELESS: anything written inside it is lost on every restart or redeploy, so state belongs in the database. Plus and Pro plans only. A private image needs a person's first deploy with the CLI (no tool takes a registry token); a redeploy reuses the stored login. Returns at once: poll list_containers until the status is running or failed.",inputSchema:D({ref:E,name:E,image:E,port:{type:"integer"}},["ref","name","image"])},{name:"restart_container",description:"Start one container again from its image. Everything written inside it is lost, which is also how to clear a container stuck in a bad state. Poll list_containers to see it come back.",inputSchema:D({ref:E,name:E},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:D({ref:E,name:E},["ref","name"])},{name:"container_logs",description:"What a project's containers printed to stdout and stderr in the last seven days, newest first: each line's time, container, stream and text. Lines arrive about a minute after they are printed. Use it when list_containers says failed or restarting. name narrows to one container, errorsOnly to stderr, limit is at most 500 (default 100). Costs nothing.",inputSchema:D({ref:E,name:E,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:D({ref:E},["ref"])},{name:"function_logs",description:"What a project's functions printed with console.log and console.error in the last seven days, newest first: each line's time, function, level ('error' for console.error and console.warn) and text. Lines arrive about a minute after they are printed. Use it after deploying or calling a function to see why it failed. name narrows to one function, errorsOnly to errors, limit is at most 500 (default 100). Costs nothing.",inputSchema:D({ref:E,name:E,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"get_project",description:"Everything about one project in one call: its state, region and size, which products are on (auth, storage, data-api), the names of its deployed functions and function secrets, and its custom domains. Never a password, a key or a download link (export_status has the link to the last export).",inputSchema:D({ref:E},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:D({ref:E},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. All three are on every plan, including free.",inputSchema:D({ref:E,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:D({ref:E},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:D({ref:E,hostname:E},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:D({ref:E,hostname:E},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:D({ref:E,hostname:E},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:D({ref:E},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:D({ref:E,at:E,name:E},["ref","at"])},{name:"cluster_status",description:"A Lepis cluster (one Postgres database spread over several nodes): a Cloud project's with project, else the router this server was started against with LEPIS_ADMIN_URL. Its nodes, keyspaces and their ranges (a range is keyspace:lo; bounds are strings), tables, routers, unfinished jobs and settings; for a Cloud project also whether it is a cluster, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:D({project:qe})},{name:"cluster_advice",description:"Ask the cluster's advisor what to split, move or add, from each node's size, connections and write rate and each range's measured share. Each recommendation has a reason, its plan, and an `operation` that cluster_run takes as it is. RUNS NOTHING. sampleMs: how long to measure write rates over (the cluster's setting by default; 0 skips them; at most 30000).",inputSchema:D({project:qe,sampleMs:{type:"integer"}})},{name:"cluster_plan",description:"The dry run of one cluster operation: its steps, what moves where, rows, bytes, the estimated copy time and the expected write pause. Changes nothing. Planning an operation is what lets cluster_run run it.",inputSchema:D({project:qe,operation:ii},["operation"])},{name:"cluster_run",description:"Run one cluster operation as a durable job. IT MOVES DATA, and each cutover pauses writes to what moves for up to the cluster's max_write_pause_ms. Without confirm: true it only returns the plan and runs nothing: show that plan to the user, and when they agree call again with the same operation and confirm: true. confirm runs only an operation planned here (cluster_plan, cluster_advice, or cluster_run without confirm) in the last 15 minutes. Returns the job id; follow it with cluster_watch_job.",inputSchema:D({project:qe,operation:ii,confirm:{type:"boolean"}},["operation"])},{name:"cluster_jobs",description:"The cluster's latest jobs, or with id one job with each step's state and phase. Costs nothing.",inputSchema:D({project:qe,id:In})},{name:"cluster_watch_job",description:"Wait for a job to finish, up to timeoutSeconds (default 30, at most 120), and return its state and steps: done, failed (with the error; cluster_resume_job runs it again from the failed step) or cancelled. Running out of time stops the waiting, never the job; call again to keep waiting.",inputSchema:D({project:qe,id:In,timeoutSeconds:{type:"integer"}},["id"])},{name:"cluster_cancel_job",description:"Stop a running job. A move cancelled before its cutover rolls back; one cancelled after it finishes its cleanup.",inputSchema:D({project:qe,id:In},["id"])},{name:"cluster_resume_job",description:"Run a failed job again from the step that failed. It carries on the operation that was already approved and planned; follow it with cluster_watch_job.",inputSchema:D({project:qe,id:In},["id"])},{name:"delete_project",description:"Delete a project. THIS DESTROYS A DATABASE. Off unless the person who started this server passed --allow-delete, in which case it still only marks the project deleted and its data is erasable for a grace period.",inputSchema:D({ref:E},["ref"])}],Cm=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Im=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),ci=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function to(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(to)};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 no(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 Dm(e){return{...e,annotations:Cm.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Im.has(e.name)}}}function Mm(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function It(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Dn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function S(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return It(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Lm(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function jt(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 Xr(e){return{id:e.id,op:e.op,state:e.state,finished:Ot(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 li(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"||!eo.includes(r.op))return`${t}: operation.op must be one of ${eo.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 Zr(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 Um(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var Fm="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 Bm(e,t,n,r,o){let s,a=typeof n.project=="string"&&n.project.trim()!==""?n.project.trim():void 0;try{if(!r.shards)return S(e,Fm,!0);s=r.shards(a)}catch(u){return S(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??Nm,i=a??"";try{switch(t){case"cluster_status":return S(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let m of c.advice)m.plan&&l.remember(m.request,i);return S(e,{summary:c.summary,advice:c.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:jt(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with cluster_plan.`}:{},...m.plan_error?{plan_error:m.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=li(n,t);if(typeof u=="string")return S(e,u,!0);let c=await s.plan(u);return l.remember(u,i),S(e,{plan:jt(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=li(n,t);if(typeof u=="string")return S(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let m=await s.plan(u);l.remember(u,i);let h=n.confirm===!0;return S(e,{ran:!1,plan:jt(m),next:h?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},h)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?S(e,{ran:!0,done:c.done,answer:c.answer}):S(e,{ran:!0,job:c.job,plan:jt(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return S(e,{jobs:await s.jobs()});let u=Zr(n);if(u===null)return S(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return S(e,{...Xr(c),plan:c.plan&&Array.isArray(c.plan.steps)?jt(c.plan):null})}case"cluster_watch_job":{let u=Zr(n);if(u===null)return S(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+c*1e3;for(;;){let h=await s.job(u);if(Ot(h.state))return S(e,Xr(h),h.state!=="done");if(Date.now()>=m)return S(e,{...Xr(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(x=>setTimeout(x,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=Zr(n);if(u===null)return S(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return S(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return Dn(e,-32602,`unknown tool: ${t}`)}}catch(u){return S(e,Um(u),!0)}}function K(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function di(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 It(r,{protocolVersion:jm,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return It(r,{});case"tools/list":return It(r,{tools:Mm([...Dt.map(Dm),...n.borrowed??[]])});case"tools/call":return Hm(r,s,t,n);default:return Dn(r,-32601,`unknown method: ${o}`)}}async function Hm(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Dt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return Dn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"];if(o.startsWith("cluster_")&&Dt.some(c=>c.name===o))return Bm(e,o,s,n,r);let i=K(s,"ref");if(l.includes(o)&&!i)return S(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!ci.includes(o))return S(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${ci.join(", ")}.`,!0);try{switch(o){case"whoami":return S(e,await n.whoami());case"list_projects":return S(e,to(await n.listProjects()));case"create_project":{let c=K(s,"name");return c?S(e,await n.createProject({name:c,region:K(s,"region")??void 0,teamId:K(s,"teamId")??void 0})):S(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return S(e,{url:c.uri,state:c.state,wakesInstantly:c.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return S(e,await n.setProjectState("pause",i));case"resume_project":return S(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return S(e,await n.usage(i,c))}case"export_status":return S(e,await n.exportStatus(i));case"start_export":return S(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),m=await n.connection(i);return S(e,{url:m.uri,appliesIn:c.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return S(e,await n.listTeams());case"get_project":return S(e,to(await n.getProject(i)));case"list_products":return S(e,await n.getProducts(i));case"set_product":{let c=K(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?S(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?S(e,"set_product needs enabled: true or false.",!0):S(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return S(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=K(s,"hostname");return c?S(e,await n.domainAction(i,o.split("_")[0],c)):S(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),m=c&&c.restore?no(i,c.restore):null;return S(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=K(s,"at");return c?S(e,await n.restoreTo(i,c,K(s,"name")??void 0)):S(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,"deploy_function needs a project ref and a function name.",!0):S(e,await n.deployFunction(c,m,{dir:K(s,"dir")??void 0,entrypoint:K(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=K(s,"ref");return c?S(e,await n.listFunctions(c)):S(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=K(s,"ref"),m=K(s,"name");if(!c||!m)return S(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,x=typeof s.concurrency=="number"?s.concurrency:void 0,L=s.reset===!0;return!L&&h===void 0&&x===void 0?S(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):S(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:x,reset:L}))}case"delete_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,"delete_function needs a project ref and a function name.",!0):S(e,await n.deleteFunction(c,m))}case"list_containers":{let c=K(s,"ref");return c?S(e,await n.listContainers(c)):S(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=K(s,"ref"),m=K(s,"name"),h=K(s,"image");if(!c||!m||!h)return S(e,"deploy_container needs a project ref, a container name and an image.",!0);let x=typeof s.port=="number"?s.port:void 0;return S(e,await n.deployContainer(c,m,{image:h,...x===void 0?{}:{port:x}}))}case"restart_container":case"delete_container":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,`${o} needs a project ref and a container name.`,!0):S(e,o==="restart_container"?await n.restartContainer(c,m):await n.deleteContainer(c,m))}case"container_logs":{let c=K(s,"ref");if(!c)return S(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return S(e,await n.containerLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_function_secrets":{let c=K(s,"ref");return c?S(e,await n.listFunctionSecrets(c)):S(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=K(s,"ref");if(!c)return S(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return S(e,await n.functionLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return S(e,await n.listTokens());case"create_token":{let c=K(s,"name");if(!c)return S(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=K(s,"expires"),h=m?Lm(m):void 0;return h===null?S(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):S(e,await n.createToken(c,h,K(s,"project")??void 0))}case"revoke_token":{let c=K(s,"id");return c?S(e,await n.revokeToken(c)):S(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return S(e,await n.pushMigrations(i,{dir:K(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?S(e,await n.setProjectState("delete",i)):S(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(c=>c.name===o)?n.callBorrowed?It(e,await n.callBorrowed(o,s)):S(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Dn(e,-32602,`unknown tool: ${o}`)}}catch(c){return S(e,c instanceof Error?c.message:String(c),!0)}}function ui(e){let t=e.split(`
406
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Mn=["auth","storage","data-api","push"];function Qe(e){return{error:e instanceof Error?e.message:String(e)}}async function mt(e){let[t,n,r,o]=await Promise.all([b("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Qe),b("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Qe),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Qe),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Qe)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Gm}}var Gm={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function pi(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Mn.includes(t))throw new f(`say which product: ${Mn.join(", ")}`);return t}async function ro(e,t,n){if(t==="auth"){let r=await b("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await b("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await b(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function fi(e){let t=await mt(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?k("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${M([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"not on this plan":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":""),n("realtime",t.realtime,"always on: signals, roster; table changes on Plus and Pro")])}
407
+ `);let s=l=>!!(l&&typeof l=="object"&&"enabled"in l&&!l.enabled),a=["auth","storage","data-api","push"].find(l=>s(l==="data-api"?o:t[l]));a&&d(w(` snoutdata products enable ${a} --ref ${e}`))})}async function gi(e,t,n){let r=await ro(e,t,n);g(r,()=>d(r.note))}async function Ln(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function oo(e,t,n){if(!n)throw new f(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await b("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function mi(e,t){let n=[["TYPE","NAME","VALUE"]];e.record&&n.push([e.record.type,e.record.name,e.record.value]),e.certificateRecord&&n.push([e.certificateRecord.type,e.certificateRecord.name,e.certificateRecord.value]),n.push(["CNAME",e.hostname,`${t}.api.snoutdata.com`]),process.stdout.write(`${M(n)}
408
+ `)}async function hi(e,t,n){if(t===void 0||t==="list"){let o=await Ln(e);g(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${M([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":k("no"),s.certificate?.state??""])])}
409
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new f(`unknown command: domains ${t}`);let r=await oo(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(wn()||mi(r.domain,e),I("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),g(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||mi(r.domain,e)),r.next&&d(r.next)})}async function so(e){return b("cloud-project-restore",{ref:e},"GET")}async function ao(e,t,n){if(Number.isNaN(Date.parse(t)))throw new f(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function yi(e){let t=await so(e);g(t,()=>{let n=t.restore,r=no(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(w(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function wi(e,t,n){let r=await ao(e,t,n);g(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${k(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function io(e){let{projects:t}=await he(),n=t.find(l=>l.ref===e);if(!n)throw new f(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([mt(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Qe),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Qe),Ln(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Qe)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function bi(e){let t=await io(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${M([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (signals and the roster; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
410
+ `)})}Y();se();Ye();Y();W();import{existsSync as qm,mkdirSync as Km,readFileSync as Jm,renameSync as zm,writeFileSync as Wm}from"node:fs";import{homedir as co}from"node:os";import{dirname as Vm,join as Ym}from"node:path";import{rootCertificates as Xm}from"node:tls";var Zm="SNOUTDATA_DB_SSLMODE";function Qm(e=co()){let t=Ym(e,".snoutdata","ca-roots.pem"),n=`${Xm.join(`
411
411
  `)}
412
- `,r=null;try{r=Lm(t)?Fm(t,"utf8"):null}catch{r=null}if(r!==n){Um(Gm(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Hm(o,n,{mode:420}),Bm(o,t)}return t}function io(e,t=process.env,n=ao()){if(e==="disable")return{mode:"disable"};let r=t[Jm]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||zm(n)}}function Qe(e,t=process.env,n=ao()){let{mode:r,rootCert:o}=io(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function vi(e){try{let{projects:t}=await he(),n=t.find(r=>r.ref===e);n?.readOnly?X(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&X(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function lo(e){await vi(e);let t=await ue(e);g(t,()=>{process.stdout.write(`${t.uri}
413
- `)})}async function xi(e,t){if(t.length===0&&!se()){let o=it();I("usage",`db psql with no statements opens an interactive session, and ${o?at(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await vi(e);let n=await ue(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=It("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Qe(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new k("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new k("failed",String(a)))}),r.on("close",a=>o(a??0))})}function $i(e,t,n="DATABASE_URL"){let r=Un(e,".env"),o=`${n}=${t}`,s=Zm(e);if(!Ln(r))return mt(r,`${o}
414
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Wm(r).mode&63&&X(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Si(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(mt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(_i(r,a.endsWith(`
412
+ `,r=null;try{r=qm(t)?Jm(t,"utf8"):null}catch{r=null}if(r!==n){Km(Vm(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Wm(o,n,{mode:420}),zm(o,t)}return t}function lo(e,t=process.env,n=co()){if(e==="disable")return{mode:"disable"};let r=t[Zm]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Qm(n)}}function et(e,t=process.env,n=co()){let{mode:r,rootCert:o}=lo(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function $i(e){try{let{projects:t}=await he(),n=t.find(r=>r.ref===e);n?.readOnly?X(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&X(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function mo(e){await $i(e);let t=await pe(e);g(t,()=>{process.stdout.write(`${t.uri}
413
+ `)})}async function Pi(e,t){if(t.length===0&&!ae()){let o=ct();I("usage",`db psql with no statements opens an interactive session, and ${o?it(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await $i(e);let n=await pe(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Mt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...et(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new v("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new v("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Ei(e,t,n="DATABASE_URL"){let r=Bn(e,".env"),o=`${n}=${t}`,s=op(e);if(!Fn(r))return pt(r,`${o}
414
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&ep(r).mode&63&&X(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=vi(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(pt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(ki(r,a.endsWith(`
415
415
  `)?`${o}
416
416
  `:`
417
417
  ${o}
418
- `),{path:r,replaced:!1,ignored:s})}function Zm(e){let t=e;for(;!Ln(Un(t,".git"));){let s=Ym(t);if(s===t)return!1;t=s}let n=Un(e,".gitignore"),r=Ln(n)?Si(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(_i(n,r===""||r.endsWith(`
418
+ `),{path:r,replaced:!1,ignored:s})}function op(e){let t=e;for(;!Fn(Bn(t,".git"));){let s=np(t);if(s===t)return!1;t=s}let n=Bn(e,".gitignore"),r=Fn(n)?vi(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(ki(n,r===""||r.endsWith(`
419
419
  `)?`.env
420
420
  `:`
421
421
  .env
422
- `),!0)}async function Pi(e,t={}){if(t.statusOnly){let{export:a}=await lt(e);return g({ref:e,export:a},()=>co(a)),0}let n=await $t(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>co(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await ep(e,t.timeoutMs);if(r.error&&I("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>co(r)),0;r.url||I("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await tp(r.url,t.out),s=r.rolesSql?Qm(t.out):null;return s&&r.rolesSql&&mt(s,r.rolesSql.endsWith(`
422
+ `),!0)}async function Ai(e,t={}){if(t.statusOnly){let{export:a}=await dt(e);return g({ref:e,export:a},()=>uo(a)),0}let n=await Pt(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>uo(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await ap(e,t.timeoutMs);if(r.error&&I("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>uo(r)),0;r.url||I("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await ip(r.url,t.out),s=r.rolesSql?sp(t.out):null;return s&&r.rolesSql&&pt(s,r.rolesSql.endsWith(`
423
423
  `)?r.rolesSql:`${r.rolesSql}
424
- `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${De(o)} to ${t.out}.`),d(y(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(y("Into any other Postgres, in this order:")),d(y(` psql --dbname <your database> -f ${s}`)),d(y(` pg_restore --dbname <your database> ${t.out}`)),d(y("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(y("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(y("Owners and grants are skipped: this export carries no roles file.")))}),0}function Qm(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function ep(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await lt(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 tp(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 ki(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 co(e){if(e.error){X(`The last export did not finish: ${e.error}`);return}if(e.pending){d("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){d("This project has never been exported.");return}d(`A copy taken ${je(e.completedAt)}, ${De(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
425
- `),e.urlExpiresAt&&d(y(`That link stops working ${je(e.urlExpiresAt)}.`))):d(y("The link has expired. Run the export again to sign a new one; the copy is kept."))}function De(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function Ei(e,t){let n=t.file;n||I("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Ln(n)||I("usage",`no file at ${n}`,{file:n});let r=await ki(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Qa(o.slice(0,s.bytesRead)),l=await ue(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await he(),u=i.find(V=>V.ref===e),c=await np(l);c===null&&I("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let m=ei(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});m.ok||I("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let V=await bi("pg_restore",["--list",n]),F=await bi("pg_restore",["--section=post-data","--file=-",n]);if((V.code!==0||F.code!==0)&&I("failed",`pg_restore could not read ${n}: ${(V.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=ni(V.stdout,F.stdout),h.products.length>0){let pe=await ut(e),Te=p=>!!(p&&typeof p=="object"&&"enabled"in p&&p.enabled),ie=h.products.filter(p=>!Te(pe[p]));ie.length>0&&I("conflict",`this dump holds ${ie.length>1?`${ie.slice(0,-1).join(", ")} and ${ie[ie.length-1]}`:ie[0]} data, and ${e} has ${ie.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ie.length===1?"it":"them"} on, wait a minute, and run this again:
426
- ${ie.map(p=>` snoutdata products enable ${p} --ref ${e}`).join(`
427
- `)}`,{ref:e,file:n,refusal:"products-off",products:ie})}}if(h&&h.platformData.length>0){let V=h.platformData.flatMap(({tables:Te})=>Te),F=await rp(l,V);F===null&&I("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let pe=h.platformData.filter(({tables:Te})=>Te.some(ie=>F.includes(ie))).map(({product:Te})=>Te);pe.length>0&&I("conflict",`${pe.join(", ")} on ${e} ${pe.length===1?"has":"have"} not finished setting up ${pe.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:pe})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let x=h?await op(l,n,h):await Mn(l,m.tool,n),U=ti(x.stderr);for(let V of h?.notes??[])d(y(V));return x.code!==0&&U.fatal.length===0&&U.total>0?(g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:U.harmless},()=>{d(`Restored into ${e}.`),d(`${U.harmless} error${U.harmless===1?"":"s"} above ${U.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(x.code!==0&&I("failed",`${m.tool} exited ${x.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:U.fatal}),g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function np(e){return new Promise(t=>{let n=It("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,...Qe(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{X(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function rp(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=It("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,...Qe(e.ssl)}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
428
- `).map(l=>l.trim()).filter(Boolean):null))})}function bi(e,t){return new Promise(n=>{let r=It(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{X(a.code==="ENOENT"?`${e} is not installed, and this dump needs it.`:String(a)),n({code:127,stdout:o,stderr:s})}),r.on("close",a=>n({code:a??0,stdout:o,stderr:s}))})}async function op(e,t,n){let r=Un(Xm(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){mt(`${r}.sql`,`set role ${Jr[l]};
424
+ `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${De(o)} to ${t.out}.`),d(w(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(w("Into any other Postgres, in this order:")),d(w(` psql --dbname <your database> -f ${s}`)),d(w(` pg_restore --dbname <your database> ${t.out}`)),d(w("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(w("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(w("Owners and grants are skipped: this export carries no roles file.")))}),0}function sp(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function ap(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await dt(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function ip(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 xi(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 uo(e){if(e.error){X(`The last export did not finish: ${e.error}`);return}if(e.pending){d("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){d("This project has never been exported.");return}d(`A copy taken ${Ce(e.completedAt)}, ${De(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
425
+ `),e.urlExpiresAt&&d(w(`That link stops working ${Ce(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function De(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function Ri(e,t){let n=t.file;n||I("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Fn(n)||I("usage",`no file at ${n}`,{file:n});let r=await xi(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=ei(o.slice(0,s.bytesRead)),l=await pe(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await he(),u=i.find(V=>V.ref===e),c=await cp(l);c===null&&I("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let m=ti(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});m.ok||I("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let V=await Si("pg_restore",["--list",n]),F=await Si("pg_restore",["--section=post-data","--file=-",n]);if((V.code!==0||F.code!==0)&&I("failed",`pg_restore could not read ${n}: ${(V.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=ri(V.stdout,F.stdout),h.products.length>0){let de=await mt(e),Oe=ve=>!!(ve&&typeof ve=="object"&&"enabled"in ve&&ve.enabled),le=h.products.filter(ve=>!Oe(de[ve]));le.length>0&&I("conflict",`this dump holds ${le.length>1?`${le.slice(0,-1).join(", ")} and ${le[le.length-1]}`:le[0]} data, and ${e} has ${le.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${le.length===1?"it":"them"} on, wait a minute, and run this again:
426
+ ${le.map(ve=>` snoutdata products enable ${ve} --ref ${e}`).join(`
427
+ `)}`,{ref:e,file:n,refusal:"products-off",products:le})}}if(h&&h.platformData.length>0){let V=h.platformData.flatMap(({tables:Oe})=>Oe),F=await lp(l,V);F===null&&I("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let de=h.platformData.filter(({tables:Oe})=>Oe.some(le=>F.includes(le))).map(({product:Oe})=>Oe);de.length>0&&I("conflict",`${de.join(", ")} on ${e} ${de.length===1?"has":"have"} not finished setting up ${de.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:de})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let x=h?await dp(l,n,h):await Un(l,m.tool,n),L=ni(x.stderr);for(let V of h?.notes??[])d(w(V));return x.code!==0&&L.fatal.length===0&&L.total>0?(g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:L.harmless},()=>{d(`Restored into ${e}.`),d(`${L.harmless} error${L.harmless===1?"":"s"} above ${L.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(x.code!==0&&I("failed",`${m.tool} exited ${x.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:L.fatal}),g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function cp(e){return new Promise(t=>{let n=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e') and pg_get_userbyid(c.relowner) !~ '^(snoutpod_admin|snout_[a-z]+_admin)$'"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...et(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{X(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function lp(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc",`select t from unnest(array[${n}]) t where to_regclass(t) is null`],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...et(e.ssl)}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
428
+ `).map(l=>l.trim()).filter(Boolean):null))})}function Si(e,t){return new Promise(n=>{let r=Mt(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{X(a.code==="ENOENT"?`${e} is not installed, and this dump needs it.`:String(a)),n({code:127,stdout:o,stderr:s})}),r.on("close",a=>n({code:a??0,stdout:o,stderr:s}))})}async function dp(e,t,n){let r=Bn(rp(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){pt(`${r}.sql`,`set role ${Wr[l]};
429
429
  ${i.map(h=>`delete from ${h};`).join(`
430
430
  `)}
431
- `,"utf8");let m=await Mn(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}mt(`${r}.platform`,`${u.join(`
431
+ `,"utf8");let m=await Un(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}pt(`${r}.platform`,`${u.join(`
432
432
  `)}
433
- `,"utf8");let c=await Mn(e,"pg_restore",t,["--data-only",`--role=${Jr[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}mt(`${r}.rest`,`${n.rest.join(`
433
+ `,"utf8");let c=await Un(e,"pg_restore",t,["--data-only",`--role=${Wr[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}pt(`${r}.rest`,`${n.rest.join(`
434
434
  `)}
435
- `,"utf8");let a=await Mn(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{Vm(`${r}${a}`)}catch{}}}function Mn(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=It(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Qe(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{X(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),l.on("close",u=>a({code:u??0,stderr:i}))})}Y();le();W();function ap(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new f(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new f("db access list takes no argument");if(t!==void 0)throw new f("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new f("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 f(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new f("db access revoke needs an email address or a role name");if(t!==void 0)throw new f("--level is for db access grant");return{action:"revoke",who:r};default:throw new f(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Ai(e,t,n){let r=ap(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>ip(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>cp(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>dp(o))}function ip(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(`${L([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?S(n.email):n.email,n.level,n.role])])}
436
- `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(y(` psql "${t.connection.keywords}"`)))}function cp(e){e.connection&&process.stdout.write(`${e.connection.keywords}
437
- `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${S(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(y(` psql "${e.connection.keywords}"`))}function lp(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function dp(e){let t=lp(e);for(let n of t.slice(0,-1))d(n);X(t[t.length-1])}Y();le();W();var up=6e4;async function uo(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function mp(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 f(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function pp(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Ri(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 fp={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 Ti(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[pp(e.at),fp[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function gp(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function hp(e){let t=e.inspect;if(d(S(`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(S(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(y(` 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,Ri(Date.now()-a.joined_at),Ri(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>up?" (silent: likely gone)":"")]);d(L([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
435
+ `,"utf8");let a=await Un(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{tp(`${r}${a}`)}catch{}}}function Un(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=Mt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...et(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{X(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),l.on("close",u=>a({code:u??0,stderr:i}))})}Y();ue();W();function mp(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new f(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new f("db access list takes no argument");if(t!==void 0)throw new f("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new f("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 f(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new f("db access revoke needs an email address or a role name");if(t!==void 0)throw new f("--level is for db access grant");return{action:"revoke",who:r};default:throw new f(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Ti(e,t,n){let r=mp(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>pp(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>fp(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>hp(o))}function pp(e){if(e.note&&d(e.note),e.access.length===0){d(`Nobody signs in to ${e.ref}'s database as themselves yet. \`snoutdata db access grant you@example.com --level full\`.`);return}process.stdout.write(`${M([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?k(n.email):n.email,n.level,n.role])])}
436
+ `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(w(` psql "${t.connection.keywords}"`)))}function fp(e){e.connection&&process.stdout.write(`${e.connection.keywords}
437
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${k(e.level)} access to ${e.ref}'s database, as ${e.role}${e.means?`: ${e.means}`:""}.`),e.outcome!=="unchanged"&&d(e.appliesIn),d("They sign in with psql 18 (libpq 18 with OAuth support) and approve the code it prints in a browser:"),e.connection&&d(w(` psql "${e.connection.keywords}"`))}function gp(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function hp(e){let t=gp(e);for(let n of t.slice(0,-1))d(n);X(t[t.length-1])}Y();ue();W();var yp=6e4;async function po(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function wp(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 f(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function bp(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Oi(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 _p={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 Ni(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[bp(e.at),_p[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function Sp(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function kp(e){let t=e.inspect;if(d(k(`Realtime for ${e.ref}`)),d(`${t.connections} connection${t.connections===1?"":"s"}, ${t.connected_users} on a channel. The project's limit is ${t.limits.max_events_per_second} messages a second, counted once per message sent, however many receive it.`),t.channels.length===0){d("No channels are open.");return}for(let n of t.channels){let r=n.messages;if(d(""),d(k(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(w(` last ${r.window_seconds}s: ${r.broadcasts_received} signal${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} roster update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,Oi(Date.now()-a.joined_at),Oi(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>yp?" (silent: likely gone)":"")]);d(M([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
438
438
  `).map(a=>` ${a}`).join(`
439
- `))}let o=Object.keys(n.presence);if(o.length>0){d(" roster:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${gp(a)}`)}}}async function Oi(e,t={}){if(t.watch&&Ms())throw new f("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await uo(e,{channel:t.channel});if(g(n.inspect,()=>hp(n)),!t.watch)return 0;d(""),d(y("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 uo(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Ti(a)}
440
- `);if(s.events.length>0){r=Math.max(...s.events.map(l=>l.at))+1;let a=s.inspect.channels.map(l=>`${l.name}: ${Object.keys(l.presence).join(", ")||"nobody tracked"}`).join("; ");process.stdout.write(y(` now ${a||"no channels open"}
441
- `))}}}async function Ni(e,t={}){let n=t.since?mp(t.since):0,r=await uo(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${Ti(o)}
442
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(y(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}Y();le();W();import{readFile as wp}from"node:fs/promises";var bp="api.snoutdata.com";function Di(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}.${bp}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function _p(e){return Di(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function Fn(e,t){return Di(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function mo(e){let t=e.google,n="off";t.ready?n=S("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(`${L([["auth",e.enabled?S("on"):"off"],["site url",e.siteUrl??y("not set")],["redirects",e.redirects.length?e.redirects.join(", "):y("none")],["google",n],["google client id",t.clientId??y("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?S("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
443
- `),e.enabled||d(y(` snoutdata products enable auth --ref ${e.ref}`))}async function Sp(){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 Ii(e,t,n,r){if(t===void 0||t==="show"){let o=await _p(e);g(o,()=>mo(o));return}if(t==="google"){if(n[0]==="off"){let a=await Fn(e,{googleEnabled:!1});g(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new f('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 Sp();if(!a)throw new f("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Fn(e,o);g(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):mo(s),d(y(` 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 f('auth anonymous takes "on" or "off"');let s=await Fn(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(y(` 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 f("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Fn(e,o);g(s,()=>mo(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>Ci(o.templates));return}if(t==="template"){let o=n[0];if(!o||!ji.includes(o))throw new f(`auth template needs a kind: ${ji.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new f(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await wp(r.file,"utf8"),i=await b("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await b("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{Ci(a.templates),d(y(" Your auth service restarts with it within about a minute."))});return}throw new f(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var ji=["confirmation","recovery","magic_link","invite","email_change"];function Ci(e){process.stdout.write(`${L([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?S("yours"):"ours",t.subject])])}
444
- `)}Y();W();oe();import{spawn as vp}from"node:child_process";import{createHash as xp}from"node:crypto";import{existsSync as $p,readdirSync as Pp,readFileSync as Ep,statSync as Ap}from"node:fs";import{isAbsolute as Rp,join as po,relative as Tp,resolve as Op}from"node:path";function kp(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Bn(e,t,n={}){let r=kp(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){s.push({code:"missing",name:u.name,reason:`${u.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}c.checksum!==u.checksum&&s.push({code:"changed",name:u.name,reason:`${u.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(u=>u.name).sort((u,c)=>u<c?-1:u>c?1:0)[t.length-1],l=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!l.has(u.name)){if(a!==void 0&&u.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function Mt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function Li(e){let t=Tp(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Lt="_snoutdata_migrations",Np=`create table if not exists ${Lt} (
439
+ `))}let o=Object.keys(n.presence);if(o.length>0){d(" roster:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${Sp(a)}`)}}}async function ji(e,t={}){if(t.watch&&wn())throw new f("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await po(e,{channel:t.channel});if(g(n.inspect,()=>kp(n)),!t.watch)return 0;d(""),d(w("Watching. New events appear below as they happen; Ctrl+C to stop."));let r=n.at+1,o=t.intervalMs??2e3;for(;;){await new Promise(a=>setTimeout(a,o));let s=await po(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Ni(a)}
440
+ `);if(s.events.length>0){r=Math.max(...s.events.map(l=>l.at))+1;let a=s.inspect.channels.map(l=>`${l.name}: ${Object.keys(l.presence).join(", ")||"nobody tracked"}`).join("; ");process.stdout.write(w(` now ${a||"no channels open"}
441
+ `))}}}async function Ci(e,t={}){let n=t.since?wp(t.since):0,r=await po(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${Ni(o)}
442
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}Y();ue();W();import{readFile as xp}from"node:fs/promises";var $p="api.snoutdata.com";function Mi(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}.${$p}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function Pp(e){return Mi(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function Hn(e,t){return Mi(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function fo(e){let t=e.google,n="off";t.ready?n=k("on"):t.enabled&&(n=`switched on, not offered yet: needs ${[!t.clientId&&"a client id",!t.clientSecretSet&&"a client secret"].filter(Boolean).join(" and ")}`),process.stdout.write(`${M([["auth",e.enabled?k("on"):"off"],["site url",e.siteUrl??w("not set")],["redirects",e.redirects.length?e.redirects.join(", "):w("none")],["google",n],["google client id",t.clientId??w("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?k("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
443
+ `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function Ep(){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 Li(e,t,n,r){if(t===void 0||t==="show"){let o=await Pp(e);g(o,()=>fo(o));return}if(t==="google"){if(n[0]==="off"){let a=await Hn(e,{googleEnabled:!1});g(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new f('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 Ep();if(!a)throw new f("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Hn(e,o);g(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):fo(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new f('auth anonymous takes "on" or "off"');let s=await Hn(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new f("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Hn(e,o);g(s,()=>fo(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>Di(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Ii.includes(o))throw new f(`auth template needs a kind: ${Ii.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new f(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await xp(r.file,"utf8"),i=await b("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await b("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{Di(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new f(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Ii=["confirmation","recovery","magic_link","invite","email_change"];function Di(e){process.stdout.write(`${M([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?k("yours"):"ours",t.subject])])}
444
+ `)}Y();W();se();import{spawn as Rp}from"node:child_process";import{createHash as Tp}from"node:crypto";import{existsSync as Op,readdirSync as Np,readFileSync as jp,statSync as Cp}from"node:fs";import{isAbsolute as Ip,join as go,relative as Dp,resolve as Mp}from"node:path";function Ap(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Gn(e,t,n={}){let r=Ap(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){s.push({code:"missing",name:u.name,reason:`${u.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}c.checksum!==u.checksum&&s.push({code:"changed",name:u.name,reason:`${u.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(u=>u.name).sort((u,c)=>u<c?-1:u>c?1:0)[t.length-1],l=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!l.has(u.name)){if(a!==void 0&&u.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function Lt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function Fi(e){let t=Dp(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Ut="_snoutdata_migrations",Lp=`create table if not exists ${Ut} (
445
445
  name text primary key,
446
446
  checksum text not null,
447
447
  applied_at timestamptz not null default now()
448
- );`;function jp(e){if(!$p(e)||!Ap(e).isDirectory())throw new Error(`no migrations folder at ${Li(e)}. Put your .sql files there, or pass --dir.`);return Pp(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=po(e,t),r=Ep(n,"utf8");return{name:t,path:n,sql:r,checksum:xp("sha256").update(r,"utf8").digest("hex")}})}function fo(e,t,n=[]){return new Promise(r=>{let o=vp("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Qe(e.ssl)}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function Cp(e){let t=await fo(e,Np);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Lt}`);let n=await fo(e,`select name || E'\\t' || checksum from ${Lt};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Lt}`);return n.out.split(`
449
- `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function Mi(e){return`'${e.replace(/'/g,"''")}'`}async function Ui(e,t={}){let n=await go(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
448
+ );`;function Up(e){if(!Op(e)||!Cp(e).isDirectory())throw new Error(`no migrations folder at ${Fi(e)}. Put your .sql files there, or pass --dir.`);return Np(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=go(e,t),r=jp(n,"utf8");return{name:t,path:n,sql:r,checksum:Tp("sha256").update(r,"utf8").digest("hex")}})}function ho(e,t,n=[]){return new Promise(r=>{let o=Rp("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...et(e.ssl)}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function Fp(e){let t=await ho(e,Lp);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Ut}`);let n=await ho(e,`select name || E'\\t' || checksum from ${Ut};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Ut}`);return n.out.split(`
449
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function Ui(e){return`'${e.replace(/'/g,"''")}'`}async function Bi(e,t={}){let n=await yo(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
450
450
  `);I("conflict",`Refused. This folder and ${e} do not agree:
451
451
  ${r}`,{ref:e,problems:n.problems})}I("failed",`${n.failed} failed, and nothing after it was tried.
452
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function go(e,t={},n=()=>{}){let r=Op(t.dir===void 0?po(process.cwd(),"migrations"):Rp(t.dir)?t.dir:po(process.cwd(),t.dir)),o=[];function s(x){o.push(x),n(x)}let a=jp(r);if(a.length===0)return s(`No .sql files in ${Li(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await ue(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await Cp(l),u=Bn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let c=new Map(a.map(x=>[x.name,x])),m=u.pending.map(x=>c.get(x.name));if(m.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let x of m)Mt(x.sql)||s(`${x.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(x=>x.name),notes:o}}let h=[];for(let x of m){let U=`insert into ${Lt} (name, checksum) values (${Mi(x.name)}, ${Mi(x.checksum)});`,V=Mt(x.sql);V||s(`${x.name} runs outside a transaction, so an interruption can leave it half applied.`);let F=V?`begin;
452
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function yo(e,t={},n=()=>{}){let r=Mp(t.dir===void 0?go(process.cwd(),"migrations"):Ip(t.dir)?t.dir:go(process.cwd(),t.dir)),o=[];function s(x){o.push(x),n(x)}let a=Up(r);if(a.length===0)return s(`No .sql files in ${Fi(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await pe(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await Fp(l),u=Gn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let c=new Map(a.map(x=>[x.name,x])),m=u.pending.map(x=>c.get(x.name));if(m.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let x of m)Lt(x.sql)||s(`${x.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(x=>x.name),notes:o}}let h=[];for(let x of m){let L=`insert into ${Ut} (name, checksum) values (${Ui(x.name)}, ${Ui(x.checksum)});`,V=Lt(x.sql);V||s(`${x.name} runs outside a transaction, so an interruption can leave it half applied.`);let F=V?`begin;
453
453
  ${x.sql}
454
- ${U}
454
+ ${L}
455
455
  commit;
456
456
  `:`${x.sql}
457
- ${U}
458
- `,pe=await fo(l,F);if(pe.code!==0)return V&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:x.name,error:pe.err.trim(),notes:o};h.push(x.name),s(` applied ${x.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}Y();le();W();import{readFile as Fi}from"node:fs/promises";function ho(e){if(e==="apns"||e==="fcm")return e;throw new f("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Dp(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function Bi(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new f("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 f("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await Fi(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function Hi(e){if(!e)throw new f("push credentials set fcm needs --file service-account.json");return{service_account:await Fi(e,"utf8")}}async function Ip(e,t,n){let r=await b("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Mp(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Gi(e){let t=await Dp(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${L([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
459
- `),(!n||!r)&&d(y(` 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 qi(e,t,n){let r=await Ip(e,t,n);g(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function Ki(e,t){let n=await Mp(e,t);g(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}Y();oe();W();import{spawn as Xp}from"node:child_process";import{writeFileSync as Zp}from"node:fs";import{isAbsolute as Qp,resolve as ef}from"node:path";var Up=" ";function N(e){return Up.repeat(e)}function ae(e){return JSON.stringify(e)}function Re(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:ae(e)}function et(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Fp(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 Bp={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"},Hp={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Gp(e,t){let n=Hp[e]?.[t];return n||(Bp[t]??"unknown")}function Hn(e,t){let{base:n,array:r}=Fp(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Gp(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function qp(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function yo(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${N(n)}${Re(t.name)}?: never`;let s=Hn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${N(n)}${Re(t.name)}${r?"?":""}: ${a}`}function Kp(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,l)=>a===n[l])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function Jp(e,t){let n=[],r=[...e.foreignKeys].sort((o,s)=>o.constraintName<s.constraintName?-1:o.constraintName>s.constraintName?1:0);if(r.length===0)return n.push(`${N(t)}Relationships: []`),n;n.push(`${N(t)}Relationships: [`);for(let o of r)n.push(`${N(t+1)}{`),n.push(`${N(t+2)}foreignKeyName: ${ae(o.constraintName)}`),n.push(`${N(t+2)}columns: [${o.columns.map(ae).join(", ")}]`),n.push(`${N(t+2)}isOneToOne: ${Kp(e,o)?"true":"false"}`),n.push(`${N(t+2)}referencedRelation: ${ae(o.referencedTable)}`),n.push(`${N(t+2)}referencedColumns: [${o.referencedColumns.map(ae).join(", ")}]`),n.push(`${N(t+1)}},`);return n.push(`${N(t)}]`),n}function Ji(e,t,n){let r=et(t.columns),o=[];o.push(`${N(n)}${Re(t.name)}: {`),o.push(`${N(n+1)}Row: {`);for(let s of r)o.push(yo(e,s,n+2,!1));if(o.push(`${N(n+1)}}`),t.kind!=="view"){o.push(`${N(n+1)}Insert: {`);for(let s of r)o.push(yo(e,s,n+2,qp(s),!0));o.push(`${N(n+1)}}`),o.push(`${N(n+1)}Update: {`);for(let s of r)o.push(yo(e,s,n+2,!0,!0));o.push(`${N(n+1)}}`)}return o.push(...Jp(t,n+1)),o.push(`${N(n)}}`),o}function Ut(e,t){return[`${N(t)}${e}: {`,`${N(t+1)}[_ in never]: never`,`${N(t)}}`]}function zp(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}[]`:`${Hn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??Hn(e,n)}function Wp(e,t,n){let r=[],o=et((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${N(n)}${Re(t.name)}: {`),o.length===0)r.push(`${N(n+1)}Args: never`);else{r.push(`${N(n+1)}Args: {`);for(let s of o)r.push(`${N(n+2)}${Re(s.name)}: ${Hn(e,s.dataType)}`);r.push(`${N(n+1)}}`)}return r.push(`${N(n+1)}Returns: ${zp(e,t)}`),r.push(`${N(n)}}`),r}function pt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function zi(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[${ae(h)}][${ae("Enums")}][${ae(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=pt(m,n),x=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${ae(h)}][${ae(x)}][${ae(m.name)}][${ae("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(pt(m,n));for(let m of e.routines??[])i.add(pt(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let m of u){let h=et(e.tables.filter(F=>pt(F,n)===m&&F.kind!=="view")),x=et(e.tables.filter(F=>pt(F,n)===m&&F.kind==="view")),U=et((e.routines??[]).filter(F=>pt(F,n)===m)),V=et(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(c.push(`${N(1)}${Re(m)}: {`),h.length===0)c.push(...Ut("Tables",2));else{c.push(`${N(2)}Tables: {`);for(let F of h)c.push(...Ji(l,F,3));c.push(`${N(2)}}`)}if(x.length===0)c.push(...Ut("Views",2));else{c.push(`${N(2)}Views: {`);for(let F of x)c.push(...Ji(l,F,3));c.push(`${N(2)}}`)}if(U.length===0)c.push(...Ut("Functions",2));else{c.push(`${N(2)}Functions: {`);for(let F of U)c.push(...Wp(l,F,3));c.push(`${N(2)}}`)}if(V.length===0)c.push(...Ut("Enums",2));else{c.push(`${N(2)}Enums: {`);for(let F of V)c.push(`${N(3)}${Re(F.name)}: ${F.values.map(ae).join(" | ")}`);c.push(`${N(2)}}`)}c.push(...Ut("CompositeTypes",2)),c.push(`${N(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Yp(n))),t.constants!==!1&&(c.push(""),c.push(Vp(u,o,n))),`${c.join(`
457
+ ${L}
458
+ `,de=await ho(l,F);if(de.code!==0)return V&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:x.name,error:de.err.trim(),notes:o};h.push(x.name),s(` applied ${x.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}Y();ue();W();import{readFile as Hi}from"node:fs/promises";function wo(e){if(e==="apns"||e==="fcm")return e;throw new f("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Bp(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function Gi(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new f("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 f("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await Hi(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function qi(e){if(!e)throw new f("push credentials set fcm needs --file service-account.json");return{service_account:await Hi(e,"utf8")}}async function Hp(e,t,n){let r=await b("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Gp(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Ki(e){let t=await Bp(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${M([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
459
+ `),(!n||!r)&&d(w(` snoutdata push credentials set ${n?"fcm --file service-account.json":"apns --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app"} --ref ${e}`))})}async function Ji(e,t,n){let r=await Hp(e,t,n);g(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function zi(e,t){let n=await Gp(e,t);g(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}Y();se();W();import{spawn as rf}from"node:child_process";import{writeFileSync as of}from"node:fs";import{isAbsolute as sf,resolve as af}from"node:path";var Kp=" ";function N(e){return Kp.repeat(e)}function ie(e){return JSON.stringify(e)}function Te(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:ie(e)}function tt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Jp(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 zp={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"},Wp={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Vp(e,t){let n=Wp[e]?.[t];return n||(zp[t]??"unknown")}function qn(e,t){let{base:n,array:r}=Jp(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Vp(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Yp(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function bo(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${N(n)}${Te(t.name)}?: never`;let s=qn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${N(n)}${Te(t.name)}${r?"?":""}: ${a}`}function Xp(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,l)=>a===n[l])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function Zp(e,t){let n=[],r=[...e.foreignKeys].sort((o,s)=>o.constraintName<s.constraintName?-1:o.constraintName>s.constraintName?1:0);if(r.length===0)return n.push(`${N(t)}Relationships: []`),n;n.push(`${N(t)}Relationships: [`);for(let o of r)n.push(`${N(t+1)}{`),n.push(`${N(t+2)}foreignKeyName: ${ie(o.constraintName)}`),n.push(`${N(t+2)}columns: [${o.columns.map(ie).join(", ")}]`),n.push(`${N(t+2)}isOneToOne: ${Xp(e,o)?"true":"false"}`),n.push(`${N(t+2)}referencedRelation: ${ie(o.referencedTable)}`),n.push(`${N(t+2)}referencedColumns: [${o.referencedColumns.map(ie).join(", ")}]`),n.push(`${N(t+1)}},`);return n.push(`${N(t)}]`),n}function Wi(e,t,n){let r=tt(t.columns),o=[];o.push(`${N(n)}${Te(t.name)}: {`),o.push(`${N(n+1)}Row: {`);for(let s of r)o.push(bo(e,s,n+2,!1));if(o.push(`${N(n+1)}}`),t.kind!=="view"){o.push(`${N(n+1)}Insert: {`);for(let s of r)o.push(bo(e,s,n+2,Yp(s),!0));o.push(`${N(n+1)}}`),o.push(`${N(n+1)}Update: {`);for(let s of r)o.push(bo(e,s,n+2,!0,!0));o.push(`${N(n+1)}}`)}return o.push(...Zp(t,n+1)),o.push(`${N(n)}}`),o}function Ft(e,t){return[`${N(t)}${e}: {`,`${N(t+1)}[_ in never]: never`,`${N(t)}}`]}function Qp(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}[]`:`${qn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??qn(e,n)}function ef(e,t,n){let r=[],o=tt((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${N(n)}${Te(t.name)}: {`),o.length===0)r.push(`${N(n+1)}Args: never`);else{r.push(`${N(n+1)}Args: {`);for(let s of o)r.push(`${N(n+2)}${Te(s.name)}: ${qn(e,s.dataType)}`);r.push(`${N(n+1)}}`)}return r.push(`${N(n+1)}Returns: ${Qp(e,t)}`),r.push(`${N(n)}}`),r}function ft(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Vi(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let h=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${ie(h)}][${ie("Enums")}][${ie(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=ft(m,n),x=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${ie(h)}][${ie(x)}][${ie(m.name)}][${ie("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(ft(m,n));for(let m of e.routines??[])i.add(ft(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let m of u){let h=tt(e.tables.filter(F=>ft(F,n)===m&&F.kind!=="view")),x=tt(e.tables.filter(F=>ft(F,n)===m&&F.kind==="view")),L=tt((e.routines??[]).filter(F=>ft(F,n)===m)),V=tt(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(c.push(`${N(1)}${Te(m)}: {`),h.length===0)c.push(...Ft("Tables",2));else{c.push(`${N(2)}Tables: {`);for(let F of h)c.push(...Wi(l,F,3));c.push(`${N(2)}}`)}if(x.length===0)c.push(...Ft("Views",2));else{c.push(`${N(2)}Views: {`);for(let F of x)c.push(...Wi(l,F,3));c.push(`${N(2)}}`)}if(L.length===0)c.push(...Ft("Functions",2));else{c.push(`${N(2)}Functions: {`);for(let F of L)c.push(...ef(l,F,3));c.push(`${N(2)}}`)}if(V.length===0)c.push(...Ft("Enums",2));else{c.push(`${N(2)}Enums: {`);for(let F of V)c.push(`${N(3)}${Te(F.name)}: ${F.values.map(ie).join(" | ")}`);c.push(`${N(2)}}`)}c.push(...Ft("CompositeTypes",2)),c.push(`${N(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(nf(n))),t.constants!==!1&&(c.push(""),c.push(tf(u,o,n))),`${c.join(`
460
460
  `)}
461
- `}function Vp(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=et(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${N(1)}${Re(o)}: {`),r.push(`${N(2)}Enums: {},`),r.push(`${N(1)}},`);continue}r.push(`${N(1)}${Re(o)}: {`),r.push(`${N(2)}Enums: {`);for(let a of s)r.push(`${N(3)}${Re(a.name)}: [${a.values.map(ae).join(", ")}],`);r.push(`${N(2)}},`),r.push(`${N(1)}},`)}return r.push("} as const"),r.join(`
462
- `)}function Yp(e){return`type DefaultSchemaName = Extract<keyof Database, ${ae(e)}>
461
+ `}function tf(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=tt(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${N(1)}${Te(o)}: {`),r.push(`${N(2)}Enums: {},`),r.push(`${N(1)}},`);continue}r.push(`${N(1)}${Te(o)}: {`),r.push(`${N(2)}Enums: {`);for(let a of s)r.push(`${N(3)}${Te(a.name)}: [${a.values.map(ie).join(", ")}],`);r.push(`${N(2)}},`),r.push(`${N(1)}},`)}return r.push("} as const"),r.join(`
462
+ `)}function nf(e){return`type DefaultSchemaName = Extract<keyof Database, ${ie(e)}>
463
463
 
464
464
  type InSchema = { schema: keyof Database }
465
465
 
@@ -496,7 +496,7 @@ export type Enums<
496
496
  export type CompositeTypes<
497
497
  Choice extends keyof Database[DefaultSchemaName]["CompositeTypes"] | InSchema,
498
498
  Name extends Choice extends InSchema ? keyof Database[Choice["schema"]]["CompositeTypes"] : never = never,
499
- > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function tf(e){let t=io(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 Gn(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 wo(e,t){return new Promise(n=>{let r=Xp("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 nf=`
499
+ > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function cf(e){let t=lo(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 Kn(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 _o(e,t){return new Promise(n=>{let r=rf("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 lf=`
500
500
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
501
501
  select json_build_object(
502
502
  'tables', coalesce((
@@ -600,40 +600,40 @@ select json_build_object(
600
600
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
601
601
  ), '[]'::json)
602
602
  )::text
603
- `;function rf(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function of(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(m=>typeof m=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var sf=`select exists (
603
+ `;function df(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function uf(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(m=>typeof m=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var mf=`select exists (
604
604
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
605
605
  where n.nspname = 'lepis' and c.relname = 'relation'
606
- )::text`,af=`select coalesce(json_agg(json_build_object(
606
+ )::text`,pf=`select coalesce(json_agg(json_build_object(
607
607
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
608
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function cf(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:lf(t)}function lf(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let l of a??[]){let i=r(l);o.has(i)||(o.add(i),s.push(l))}return s};return{tables:t(e.map(n=>n.tables),n=>`${n.schema}.${n.name}`),routines:t(e.map(n=>n.routines),n=>`${n.schema}.${n.name}(${(n.parameters??[]).map(r=>`${r.mode} ${r.dataType}`).join(",")})`),enums:t(e.map(n=>n.enums),n=>`${n.schema}.${n.name}`)}}async function df(e,t){let n=await e(sf);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(af);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 uf(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Gn(e.dbUrl):tf(await ue(mf(e))),r=!1,o=async U=>{if(e.sql)return e.sql(U);if(r&&e.ref)return Wi(e.ref,U);let V=await wo(n,U);return V.code===127&&!e.dbUrl&&e.ref?(r=!0,Wi(e.ref,U)):V},s=nf.replace("$SCHEMAS$",rf(t)),a=await o(s);a.code===127&&I("tool-missing",a.err.trim()),a.code!==0&&I("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||I("failed",`${n.label} returned nothing for the schema query`);let i=cf(l);i||I("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await df(o,t),{schema:c,enums:m}=of(i,n.database,Date.now()),h=zi(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),x=null;return e.out&&(x=Qp(e.out)?e.out:ef(process.cwd(),e.out),Zp(x,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(U=>U.kind!=="view").length,views:c.tables.filter(U=>U.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:x,distribution:u,types:h}}async function Wi(e,t,n=b){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function mf(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 Vi(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&I("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await uf(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}oe();import{createHash as Cg}from"node:crypto";import{existsSync as Vo,mkdirSync as Dg,readFileSync as Yo,readdirSync as Ig,statSync as Mg,writeFileSync as Lg}from"node:fs";import{dirname as Ug,isAbsolute as Cl,join as Wt,resolve as Dl}from"node:path";import{pathToFileURL as Fg}from"node:url";W();function Bg(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:Cl(e)||e.startsWith("./")||e.startsWith("../")?Fg(Dl(e)).href:e}function Hg(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(Bg(e)):Promise.resolve().then(()=>(Tl(),Rl))}function Gg(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 Ol="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 Nl(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Vt(){let e;try{e=await Hg()}catch(t){I("tool-missing",Gg(t))}return{ready:async t=>{let n=await Nl(e,t);return typeof n=="string"?await e.podmanReady(Ol)??n:e.podmanReady(Ol,n.name)},ensureImage:async(t,n)=>{let r=await Nl(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 Il(e){return Wt(e,".snoutdata","local.json")}function tr(e){let t=Il(e);if(!Vo(t))return null;try{let n=JSON.parse(Yo(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 qg(e,t){let n=Il(e);return Dg(Ug(n),{recursive:!0}),Lg(n,`${JSON.stringify(t,null,2)}
609
- `,{encoding:"utf8",mode:384}),n}var Kg=54322;async function Ml(e,t){let n=await e.ensureImage(t,o=>d(o));n&&I("tool-missing",n);let r=await e.ready(t);r&&I("tool-missing",r),await e.prepare()}function Ll(e,t,n,r=wo){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 Jg(e,t={}){let n=t.cwd??process.cwd(),r=tr(n);await Ml(e,r?.ref??null);let o=r??e.mint(t.port??Kg),s=r?null:qg(n,o),a=await e.start(o,m=>d(m)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=Ll(e,o,Gn(l)),u=await zg(i,n,t),c=await Wg(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function zg(e,t,n){let r=Dl(n.dir===void 0?Wt(t,"migrations"):Cl(n.dir)?n.dir:Wt(t,n.dir));if(!Vo(r)||!Mg(r).isDirectory())return[];let o=Ig(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Yo(Wt(r,m),"utf8");return{name:m,sql:h,checksum:Cg("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Vg);s.code!==0&&I(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${zt}`);let a=await e(`select name || E'\\t' || checksum from ${zt};`);a.code!==0&&I("failed",a.err.trim()||`could not read ${zt}`);let l=a.out.split(`
610
- `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let h=m.indexOf(" ");return{name:m.slice(0,h),checksum:m.slice(h+1)}}),i=Bn(o,l,{outOfOrder:n.outOfOrder});i.ok||I("conflict",`Refused. This folder and the local database do not agree:
608
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function ff(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:gf(t)}function gf(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let l of a??[]){let i=r(l);o.has(i)||(o.add(i),s.push(l))}return s};return{tables:t(e.map(n=>n.tables),n=>`${n.schema}.${n.name}`),routines:t(e.map(n=>n.routines),n=>`${n.schema}.${n.name}(${(n.parameters??[]).map(r=>`${r.mode} ${r.dataType}`).join(",")})`),enums:t(e.map(n=>n.enums),n=>`${n.schema}.${n.name}`)}}async function hf(e,t){let n=await e(mf);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(pf);if(r.code!==0)return[];try{let o=JSON.parse(r.out.trim()),s=new Set(t);return o.filter(a=>s.has(a.schema)).map(a=>({schema:a.schema,table:a.table,kind:a.kind,keyColumn:a.keyColumn??null}))}catch{return[]}}async function yf(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Kn(e.dbUrl):cf(await pe(wf(e))),r=!1,o=async L=>{if(e.sql)return e.sql(L);if(r&&e.ref)return Yi(e.ref,L);let V=await _o(n,L);return V.code===127&&!e.dbUrl&&e.ref?(r=!0,Yi(e.ref,L)):V},s=lf.replace("$SCHEMAS$",df(t)),a=await o(s);a.code===127&&I("tool-missing",a.err.trim()),a.code!==0&&I("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||I("failed",`${n.label} returned nothing for the schema query`);let i=ff(l);i||I("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await hf(o,t),{schema:c,enums:m}=uf(i,n.database,Date.now()),h=Vi(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),x=null;return e.out&&(x=sf(e.out)?e.out:af(process.cwd(),e.out),of(x,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(L=>L.kind!=="view").length,views:c.tables.filter(L=>L.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:x,distribution:u,types:h}}async function Yi(e,t,n=b){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function wf(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 Xi(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&I("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await yf(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}se();import{createHash as Kg}from"node:crypto";import{existsSync as Xo,mkdirSync as Jg,readFileSync as Zo,readdirSync as zg,statSync as Wg,writeFileSync as Vg}from"node:fs";import{dirname as Yg,isAbsolute as Bl,join as Vt,resolve as Hl}from"node:path";import{pathToFileURL as Xg}from"node:url";W();function Zg(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:Bl(e)||e.startsWith("./")||e.startsWith("../")?Xg(Hl(e)).href:e}function Qg(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(Zg(e)):Promise.resolve().then(()=>(Ml(),Dl))}function eh(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 Ll="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 Ul(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Yt(){let e;try{e=await Qg()}catch(t){I("tool-missing",eh(t))}return{ready:async t=>{let n=await Ul(e,t);return typeof n=="string"?await e.podmanReady(Ll)??n:e.podmanReady(Ll,n.name)},ensureImage:async(t,n)=>{let r=await Ul(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 Gl(e){return Vt(e,".snoutdata","local.json")}function rr(e){let t=Gl(e);if(!Xo(t))return null;try{let n=JSON.parse(Zo(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 th(e,t){let n=Gl(e);return Jg(Yg(n),{recursive:!0}),Vg(n,`${JSON.stringify(t,null,2)}
609
+ `,{encoding:"utf8",mode:384}),n}var nh=54322;async function ql(e,t){let n=await e.ensureImage(t,o=>d(o));n&&I("tool-missing",n);let r=await e.ready(t);r&&I("tool-missing",r),await e.prepare()}function Kl(e,t,n,r=_o){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 rh(e,t={}){let n=t.cwd??process.cwd(),r=rr(n);await ql(e,r?.ref??null);let o=r??e.mint(t.port??nh),s=r?null:th(n,o),a=await e.start(o,m=>d(m)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=Kl(e,o,Kn(l)),u=await oh(i,n,t),c=await sh(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function oh(e,t,n){let r=Hl(n.dir===void 0?Vt(t,"migrations"):Bl(n.dir)?n.dir:Vt(t,n.dir));if(!Xo(r)||!Wg(r).isDirectory())return[];let o=zg(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Zo(Vt(r,m),"utf8");return{name:m,sql:h,checksum:Kg("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(ah);s.code!==0&&I(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${Wt}`);let a=await e(`select name || E'\\t' || checksum from ${Wt};`);a.code!==0&&I("failed",a.err.trim()||`could not read ${Wt}`);let l=a.out.split(`
610
+ `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let h=m.indexOf(" ");return{name:m.slice(0,h),checksum:m.slice(h+1)}}),i=Gn(o,l,{outOfOrder:n.outOfOrder});i.ok||I("conflict",`Refused. This folder and the local database do not agree:
611
611
  ${i.problems.map(m=>` ${m.reason}`).join(`
612
- `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),c=[];for(let m of i.pending){let h=u.get(m.name),x=`insert into ${zt} (name, checksum) values (${jl(h.name)}, ${jl(h.checksum)});`,U=Mt(h.sql);U||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let V=U?`begin;
612
+ `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),c=[];for(let m of i.pending){let h=u.get(m.name),x=`insert into ${Wt} (name, checksum) values (${Fl(h.name)}, ${Fl(h.checksum)});`,L=Lt(h.sql);L||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let V=L?`begin;
613
613
  ${h.sql}
614
614
  ${x}
615
615
  commit;
616
616
  `:`${h.sql}
617
617
  ${x}
618
618
  `,F=await e(V);F.code!==0&&I("failed",`${h.name} failed, and nothing after it was tried.
619
- ${F.err.trim()}`,{failed:h.name,applied:c}),c.push(h.name),d(` applied ${h.name}`)}return c}async function Wg(e,t,n){let r=Wt(t,"seed.sql");if(!n||!Vo(r))return!1;let o=await e(Yo(r,"utf8"));return o.code!==0&&I("failed",`seed.sql failed.
620
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var zt="_snoutdata_migrations",Vg=`create table if not exists ${zt} (
619
+ ${F.err.trim()}`,{failed:h.name,applied:c}),c.push(h.name),d(` applied ${h.name}`)}return c}async function sh(e,t,n){let r=Vt(t,"seed.sql");if(!n||!Xo(r))return!1;let o=await e(Zo(r,"utf8"));return o.code!==0&&I("failed",`seed.sql failed.
620
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Wt="_snoutdata_migrations",ah=`create table if not exists ${Wt} (
621
621
  name text primary key,
622
622
  checksum text not null,
623
623
  applied_at timestamptz not null default now()
624
- );`;function jl(e){return`'${e.replace(/'/g,"''")}'`}function Yg(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 Ul(e,t={}){let n=await Jg(e,t);return g(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${S("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${S("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${S("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
625
- `),d(y(" snoutdata gen types typescript --local > database.types.ts")),d(y(" snoutdata stop"))}),0}async function Fl(e,t={}){let n=t.cwd??process.cwd(),r=tr(n);r||I("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Ml(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function Bl(e,t={}){let n=t.cwd??process.cwd(),r=tr(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{d(` ${S("Project")} ${s.ref}`),d(` ${S("State")} ${s.ready?"running":s.status}`),d(` ${S("Port")} ${s.port}`),d(` ${S("Size")} ${Yg(s.databaseBytes)}`),s.services.length>0&&d(` ${S("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
626
- `)}),0}async function Hl(e,t=process.cwd()){let n=tr(t);n||I("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Ll(e,n,Gn(r))}}Y();W();an();_t();Ie();import{resolve as Gl}from"node:path";function Xg(){let e=n=>Ce(n),t;return{shards:n=>{if(n!==void 0)return Ot({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??=Ot({}),t},whoami:()=>Ye(),listProjects:async()=>({...await he(),local:Zg()}),localProject:n=>e(n),createProject:async n=>{let r=await b("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Et(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(n,r)=>b("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>lt(n),requestExport:n=>$t(n),resetPassword:n=>b("cloud-project-reset-password",{ref:n}),listTeams:()=>b("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Ar(s,r,o):Dr(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?xn(r):Ir(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Tr(o,r):Mr(n,r)},sizeFunction:(n,r,o)=>Lr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?Pn(r):b("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>b("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>Tn(n),deployContainer:(n,r,o)=>Br(n,r,o),restartContainer:(n,r)=>Gr(n,r),deleteContainer:(n,r)=>Hr(n,r),containerLogs:(n,r)=>An(n,r),listTokens:()=>b("cloud-token-list",{}),createToken:(n,r,o)=>b("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>b("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:xn(r).functions.map(o=>o.name),secretNames:Pn(r).secrets.map(o=>o.name)}:so(n)},getProducts:n=>ut(n),setProduct:(n,r,o)=>to(n,r,o),listDomains:n=>In(n),domainAction:(n,r,o)=>no(n,r,o),restoreWindow:n=>ro(n),restoreTo:(n,r,o)=>oo(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await Pt(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>ue(n),pushMigrations:(n,r)=>go(n,{dir:r.dir,dryRun:r.dryRun})}}function Zg(){let e=Qt().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=rt(process.cwd())?.local;if(t&&!e.some(n=>Gl(n.folder)===Gl(t)))try{let n=Le(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var ql="app_";async function Qg(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await on();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Rs(t)).map(o=>({name:`${ql}${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 Kl(e){xt(!0);let{tools:t,config:n}=await Qg(),r=Xg();n&&(r.callBorrowed=(i,u)=>Ts(n,i.slice(ql.length),u));let o={...e,borrowed:t};X(t.length>0?`snoutdata mcp: ${Dt.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 Ye();X(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(X(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return X("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;X(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}X(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function l(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",u=>{let c=di(s+u);s=c.rest;for(let m of c.messages)l(eh(m,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function eh(e,t,n){let r;try{r=JSON.parse(e)}catch{X(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await li(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
624
+ );`;function Fl(e){return`'${e.replace(/'/g,"''")}'`}function ih(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 Jl(e,t={}){let n=await rh(e,t);return g(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${k("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${k("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${k("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
625
+ `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function zl(e,t={}){let n=t.cwd??process.cwd(),r=rr(n);r||I("not-found","there is no local database for this folder. `snoutdata start` makes one."),await ql(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function Wl(e,t={}){let n=t.cwd??process.cwd(),r=rr(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{d(` ${k("Project")} ${s.ref}`),d(` ${k("State")} ${s.ready?"running":s.status}`),d(` ${k("Port")} ${s.port}`),d(` ${k("Size")} ${ih(s.databaseBytes)}`),s.services.length>0&&d(` ${k("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
626
+ `)}),0}async function Vl(e,t=process.cwd()){let n=rr(t);n||I("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Kl(e,n,Kn(r))}}Y();W();cn();St();Me();import{resolve as Yl}from"node:path";function ch(){let e=n=>Ie(n),t;return{shards:n=>{if(n!==void 0)return Nt({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??=Nt({}),t},whoami:()=>Xe(),listProjects:async()=>({...await he(),local:lh()}),localProject:n=>e(n),createProject:async n=>{let r=await b("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await At(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(n,r)=>b("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>dt(n),requestExport:n=>Pt(n),resetPassword:n=>b("cloud-project-reset-password",{ref:n}),listTeams:()=>b("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Tr(s,r,o):Mr(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Pn(r):Lr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Nr(o,r):Ur(n,r)},sizeFunction:(n,r,o)=>Fr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?An(r):b("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>b("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>Nn(n),deployContainer:(n,r,o)=>Gr(n,r,o),restartContainer:(n,r)=>Kr(n,r),deleteContainer:(n,r)=>qr(n,r),containerLogs:(n,r)=>Tn(n,r),listTokens:()=>b("cloud-token-list",{}),createToken:(n,r,o)=>b("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>b("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:Pn(r).functions.map(o=>o.name),secretNames:An(r).secrets.map(o=>o.name)}:io(n)},getProducts:n=>mt(n),setProduct:(n,r,o)=>ro(n,r,o),listDomains:n=>Ln(n),domainAction:(n,r,o)=>oo(n,r,o),restoreWindow:n=>so(n),restoreTo:(n,r,o)=>ao(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await Et(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>pe(n),pushMigrations:(n,r)=>yo(n,{dir:r.dir,dryRun:r.dryRun})}}function lh(){let e=en().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=ot(process.cwd())?.local;if(t&&!e.some(n=>Yl(n.folder)===Yl(t)))try{let n=Ue(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Xl="app_";async function dh(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await sn();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Os(t)).map(o=>({name:`${Xl}${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 Zl(e){$t(!0);let{tools:t,config:n}=await dh(),r=ch();n&&(r.callBorrowed=(i,u)=>Ns(n,i.slice(Xl.length),u));let o={...e,borrowed:t};X(t.length>0?`snoutdata mcp: ${Dt.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await Xe();X(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(X(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return X("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;X(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}X(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function l(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",u=>{let c=ui(s+u);s=c.rest;for(let m of c.messages)l(uh(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 uh(e,t,n){let r;try{r=JSON.parse(e)}catch{X(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await di(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
627
627
  `)}catch(o){let s=r.id??null;process.stdout.write(`${JSON.stringify({jsonrpc:"2.0",id:s,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}
628
- `)}}_t();Y();W();function th(e){return e>=1?"over":e>=.8?"near":"ok"}function Jl(e,t){let n=null,r=null,o=0,s=0;for(let l of e)o+=Number.isFinite(l.computeSeconds)?l.computeSeconds:0,s+=Number.isFinite(l.connections)?l.connections:0,!(l.dbBytes===null||!Number.isFinite(l.dbBytes))&&(r=r===null?l.dbBytes:Math.max(r,l.dbBytes),(!n||l.day>=n.day)&&(n={day:l.day,dbBytes:l.dbBytes,repoBytes:l.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:th(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Xo(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 zl(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Jl(n.days,n.limit);return g({...n,summary:r},()=>{if(d(S(`${n.name} (${n.ref})`)),!r.latest)d("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${De(r.storage.usedBytes)} of ${De(r.storage.limitBytes)} on ${n.limit?.tier}`;d(r.storage.state==="over"?`${s} (${o}%). Over the limit, so writes are refused until it shrinks or the plan changes.`:r.storage.state==="near"?`${s} (${o}%). Close to the limit: at 100% the database goes read-only.`:`${s} (${o}%).`)}else d(`${De(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&d("This project is currently read-only."),d(`Over ${r.days} day${r.days===1?"":"s"}: ${Xo(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(y(`Peak in that window was ${De(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(L([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":De(o.dbBytes),o.repoBytes===null?"-":De(o.repoBytes),Xo(o.computeSeconds),String(o.connections)])])),d(y("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}le();oe();Ve();W();import{createInterface as nh}from"node:readline/promises";var rh=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),Zo={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"},oh="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Wl(e,t,n=!1){let[r,o,...s]=e,a=c=>$(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new f(m);return c},i,u=`${r??""}${o!==void 0&&sh(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new f("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:E(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new f("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new f("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Je(t,"nodes","nodes");if(!c.nodes)throw new f("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(Zo))Object.assign(i,Je(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...Je(t,"port","port"),...Ke(t,"dbname","dbname"),...Ke(t,"sslmode","sslmode"),...Ke(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:or(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...Je(t,"ranges","ranges"),...a("seed")!==void 0?{seed:ah(a("seed"))}:{},...a("nodes")?{nodes:or(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new f("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=nr(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:Qo(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=nr(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new f("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=nr(s[0],t),m=nr(s[1],t);if(c.keyspace!==m.keyspace)throw new f(`range merge: ${c.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:m.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(m,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Ke(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?or(a("add")).map(h=>ih(h,t)):[],m=Je(t,"remove","remove");if(c.length===0&&!m.remove)throw new f("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...m};break}case"verify":i={op:"verify",...Ke(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Ke(t,"node","node")};break;default:throw new f(`unknown operation: cluster ${e.join(" ")}. One of ${oh}`)}for(let[c,m]of Object.entries(Zo)){let h=Je(t,m,c);Object.assign(i,h)}return{body:i,destructive:rh.has(i.op)}}function sh(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function or(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Ke(e,t,n){let r=$(e,t);return r===void 0?{}:{[n]:r}}function Je(e,t,n){let r=$(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new f(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Qo(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new f(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function ah(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new f(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function nr(e,t){let n=$(t,"keyspace"),r=$(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new f(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Qo(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new f("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Qo(o,"--range")}}function ih(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new f(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Ke(t,"dbname","dbname"),...Ke(t,"sslmode","sslmode")}}function rr(e,t){if(e===void 0||!/^\d+$/.test(e))throw new f(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Xl(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=$(e,"project")??$(e,"ref"),l=a!==void 0,i=()=>t.client??Ot({project:a,admin:$(e,"admin"),token:$(e,"token")});switch(r){case"enable":{if(!l)throw new f("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof Ae))throw new f("cluster enable needs --project <ref>");if(E(e,"plan")){let h=await c.enable(!0);return g(h,()=>we(h,process.stdout)),0}if(!E(e,"yes")){if(!t.confirm&&!se())throw new f("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.enable(!0),process.stderr),!await(t.confirm??wt)(`Make a cluster of ${c.target}?`))throw new k("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return g(m,()=>d(He(m))),0}case"disable":{if(!l)throw new f("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof Ae))throw new f("cluster disable needs --project <ref>");if(E(e,"plan")){let h=await c.disable(!0);return g(h,()=>we(h,process.stdout)),0}if(!E(e,"yes")){if(!t.confirm&&!se())throw new f("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.disable(!0),process.stderr),!await(t.confirm??wt)(`Turn the cluster of ${c.target} off?`))throw new k("failed","Not turned off. Nothing was changed.")}let m=await c.disable(!1);return g(m,()=>d(He(m))),0}case"spread":{if(!l)throw new f("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof Ae))throw new f("cluster spread needs --project <ref>");let m={table:o??yt($(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:yt($(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:yt($(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(E(e,"plan")){let x=await c.spread(m,!0);return g(x,()=>we(x,process.stdout)),0}if(!E(e,"yes")){if(!t.confirm&&!se())throw new f("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.spread(m,!0),process.stderr),!await(t.confirm??wt)(`Spread ${m.table} over ${c.target}?`))throw new k("failed","Not spread. Nothing was changed.")}let h=await c.spread(m,!1);return g(h,()=>d(He(h))),0}case"retry":{if(!l)throw new f("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof Ae))throw new f("cluster retry needs --project <ref>");let m=E(e,"queue")?await c.retry({queue:!0}):await c.retry({node:yt(o??$(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return g(m,()=>d(He(m))),0}case"replica":{if(!l)throw new f("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new f("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof Ae))throw new f("cluster replica needs --project <ref>");let m=o==="remove",h=yt(s??$(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(E(e,"plan")){let U=await c.replica(h,m,!0);return g(U,()=>we(U,process.stdout)),0}if(!E(e,"yes")){if(!t.confirm&&!se())throw new f(m?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.replica(h,m,!0),process.stderr),!await(t.confirm??wt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new k("failed","Nothing was changed.")}let x=await c.replica(h,m,!1);return g(x,()=>d(He(x))),0}case"alerts":{if(!l)throw new f("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof Ae))throw new f("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await c.alerts();return g({alerts:m},()=>Ql(m,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let m=yt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new f(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await c.acknowledge(m.toLowerCase());return g(h,()=>d(He(h))),0}throw new f(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return g(c,()=>fh(c)),0}case"ops":{let c=await i().ops();return g(c,()=>{process.stdout.write(`${c.ops.join(`
628
+ `)}}St();Y();W();function mh(e){return e>=1?"over":e>=.8?"near":"ok"}function Ql(e,t){let n=null,r=null,o=0,s=0;for(let l of e)o+=Number.isFinite(l.computeSeconds)?l.computeSeconds:0,s+=Number.isFinite(l.connections)?l.connections:0,!(l.dbBytes===null||!Number.isFinite(l.dbBytes))&&(r=r===null?l.dbBytes:Math.max(r,l.dbBytes),(!n||l.day>=n.day)&&(n={day:l.day,dbBytes:l.dbBytes,repoBytes:l.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:mh(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Qo(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 ed(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Ql(n.days,n.limit);return g({...n,summary:r},()=>{if(d(k(`${n.name} (${n.ref})`)),!r.latest)d("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${De(r.storage.usedBytes)} of ${De(r.storage.limitBytes)} on ${n.limit?.tier}`;d(r.storage.state==="over"?`${s} (${o}%). Over the limit, so writes are refused until it shrinks or the plan changes.`:r.storage.state==="near"?`${s} (${o}%). Close to the limit: at 100% the database goes read-only.`:`${s} (${o}%).`)}else d(`${De(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&d("This project is currently read-only."),d(`Over ${r.days} day${r.days===1?"":"s"}: ${Qo(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(w(`Peak in that window was ${De(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(M([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":De(o.dbBytes),o.repoBytes===null?"-":De(o.repoBytes),Qo(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}ue();se();Ye();W();import{createInterface as ph}from"node:readline/promises";var fh=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),es={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"},gh="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function td(e,t,n=!1){let[r,o,...s]=e,a=c=>$(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new f(m);return c},i,u=`${r??""}${o!==void 0&&hh(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new f("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:A(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new f("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new f("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=ze(t,"nodes","nodes");if(!c.nodes)throw new f("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(es))Object.assign(i,ze(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...ze(t,"port","port"),...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode"),...Je(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:ar(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...ze(t,"ranges","ranges"),...a("seed")!==void 0?{seed:yh(a("seed"))}:{},...a("nodes")?{nodes:ar(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new f("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=or(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:ts(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=or(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new f("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=or(s[0],t),m=or(s[1],t);if(c.keyspace!==m.keyspace)throw new f(`range merge: ${c.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:m.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(m,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Je(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?ar(a("add")).map(h=>wh(h,t)):[],m=ze(t,"remove","remove");if(c.length===0&&!m.remove)throw new f("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...m};break}case"verify":i={op:"verify",...Je(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Je(t,"node","node")};break;default:throw new f(`unknown operation: cluster ${e.join(" ")}. One of ${gh}`)}for(let[c,m]of Object.entries(es)){let h=ze(t,m,c);Object.assign(i,h)}return{body:i,destructive:fh.has(i.op)}}function hh(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function ar(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Je(e,t,n){let r=$(e,t);return r===void 0?{}:{[n]:r}}function ze(e,t,n){let r=$(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new f(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function ts(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new f(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function yh(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new f(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function or(e,t){let n=$(t,"keyspace"),r=$(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new f(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:ts(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new f("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:ts(o,"--range")}}function wh(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new f(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode")}}function sr(e,t){if(e===void 0||!/^\d+$/.test(e))throw new f(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function od(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=$(e,"project")??$(e,"ref"),l=a!==void 0,i=()=>t.client??Nt({project:a,admin:$(e,"admin"),token:$(e,"token")});switch(r){case"enable":{if(!l)throw new f("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof Re))throw new f("cluster enable needs --project <ref>");if(A(e,"plan")){let h=await c.enable(!0);return g(h,()=>we(h,process.stdout)),0}if(!A(e,"yes")){if(!t.confirm&&!ae())throw new f("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.enable(!0),process.stderr),!await(t.confirm??bt)(`Make a cluster of ${c.target}?`))throw new v("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return g(m,()=>d(Ge(m))),0}case"disable":{if(!l)throw new f("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof Re))throw new f("cluster disable needs --project <ref>");if(A(e,"plan")){let h=await c.disable(!0);return g(h,()=>we(h,process.stdout)),0}if(!A(e,"yes")){if(!t.confirm&&!ae())throw new f("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.disable(!0),process.stderr),!await(t.confirm??bt)(`Turn the cluster of ${c.target} off?`))throw new v("failed","Not turned off. Nothing was changed.")}let m=await c.disable(!1);return g(m,()=>d(Ge(m))),0}case"spread":{if(!l)throw new f("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof Re))throw new f("cluster spread needs --project <ref>");let m={table:o??wt($(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:wt($(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:wt($(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(A(e,"plan")){let x=await c.spread(m,!0);return g(x,()=>we(x,process.stdout)),0}if(!A(e,"yes")){if(!t.confirm&&!ae())throw new f("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.spread(m,!0),process.stderr),!await(t.confirm??bt)(`Spread ${m.table} over ${c.target}?`))throw new v("failed","Not spread. Nothing was changed.")}let h=await c.spread(m,!1);return g(h,()=>d(Ge(h))),0}case"retry":{if(!l)throw new f("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof Re))throw new f("cluster retry needs --project <ref>");let m=A(e,"queue")?await c.retry({queue:!0}):await c.retry({node:wt(o??$(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return g(m,()=>d(Ge(m))),0}case"replica":{if(!l)throw new f("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new f("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof Re))throw new f("cluster replica needs --project <ref>");let m=o==="remove",h=wt(s??$(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(A(e,"plan")){let L=await c.replica(h,m,!0);return g(L,()=>we(L,process.stdout)),0}if(!A(e,"yes")){if(!t.confirm&&!ae())throw new f(m?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.replica(h,m,!0),process.stderr),!await(t.confirm??bt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new v("failed","Nothing was changed.")}let x=await c.replica(h,m,!1);return g(x,()=>d(Ge(x))),0}case"alerts":{if(!l)throw new f("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof Re))throw new f("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await c.alerts();return g({alerts:m},()=>ad(m,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let m=wt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new f(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await c.acknowledge(m.toLowerCase());return g(h,()=>d(Ge(h))),0}throw new f(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return g(c,()=>$h(c)),0}case"ops":{let c=await i().ops();return g(c,()=>{process.stdout.write(`${c.ops.join(`
629
629
  `)}
630
- `),d(y(`Default settings: ${Object.entries(c.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(y(`Refusal kinds: ${c.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return dh(e,i);case"advise":case"advice":{let c=Je(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return g(m,()=>ph(m,process.stdout)),0}case"jobs":return uh(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return g({nodes:c.nodes??[]},()=>Zl(c)),0}break;case"plan":{let c=Wl(n.slice(1),e,l),m=await i().plan(c.body);return g(m,()=>we(m,process.stdout)),0}}let u=Wl(n,e,l);if(E(e,"plan")){let c=await i().plan(u.body);return g(c,()=>we(c,process.stdout)),0}return ch(u,e,i,t)}async function ch(e,t,n,r){let o=E(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!se())throw new f(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(we(u,process.stderr),!await(r.confirm??wt)(`Run ${s} on ${a.target}?`))throw new k("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return g(l.answer,()=>d(l.done)),0;if(E(t,"no-wait"))return g({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
631
- `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await es(a,l.job,r);return ts(i)}function yt(e,t){if(e===void 0||e==="")throw new f(t);return e}async function wt(e){let t=nh({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 lh(e){let t=[...Wr,...oi],n={};for(let r of or(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 f(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new f(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function dh(e,t){let n={};for(let s of Wr)Object.assign(n,Je(e,Zo[s],s));let r=$(e,"set");if(r!==void 0&&Object.assign(n,lh(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return g({settings:a},()=>process.stdout.write(`${L(Object.entries(a).map(([l,i])=>[l,String(i)]))}
632
- `)),0}let o=await t().settings(n);return g(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function uh(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return g({jobs:s},()=>_h(s)),0}case"show":{let s=await r.job(rr(t,"show"));return g(s,()=>Sh(s)),0}case"watch":{let s=await es(r,rr(t,"watch"),o);return ts(s)}case"cancel":{let s=rr(t,"cancel");if(!E(n,"yes")){if(!o.confirm&&!se())throw new f(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??wt)(`Cancel job ${s} on ${r.target}?`))throw new k("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return g(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=rr(t,"resume"),a=await r.resume(s);return E(n,"no-wait")?(g(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):ts(await es(r,s,o))}default:throw new f(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function es(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(y(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Tt(a.state))return a;if(Date.now()>=o)throw new k("timeout",`job ${t} is still ${a.state}; it carries on without this command. Follow it again: snoutdata cluster jobs watch ${t}`,{job:a});await new Promise(l=>setTimeout(l,r))}}function ts(e){if(e.state==="done")return g(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
633
- `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata cluster jobs resume ${e.id}`:"was cancelled";throw new k("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function R(e,t=""){e.write(`${t}
634
- `)}function ns(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function we(e,t){R(t,`${S(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&R(t,L([[" 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")}`,ns(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),R(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${ns(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)`),R(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)R(t,` warning: ${n}`);e.assumptions&&R(t,y(` ${e.assumptions}`))}function ke(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function mh(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${ke(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${ke(t.at)}`:""}${t.to!==void 0?` --to ${ke(t.to)}`:""}`;case"range.move":return`${n} range move ${ke(`${t.keyspace}:${t.range}`)} --to ${ke(t.to)}`;case"tenant.pin":return`${n} tenant pin ${ke(t.keyspace)} --value ${ke(t.value)}${t.node!==void 0?` --node ${ke(t.node)}`:""}`;case"node.add":return`${n} nodes add ${ke(t.name)} --host ${t.host?ke(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${ke(t.keyspace)}`:""}`;default:return null}}function ph(e,t){R(t,e.summary),e.advice.forEach((n,r)=>{R(t),R(t,`${S(`${r+1}. ${n.op}`)} (${n.metric})`),R(t,` ${n.reason}`);let o=mh(n.request);if(R(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;R(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${ns(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])R(t,` warning: ${a}`)}else n.needs.length>0?R(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&R(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(R(t),R(t,y(e.assumptions)))}function Vl(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Zl(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}R(process.stdout,L([["ID","NAME","ADDRESS","STATE","HOME","RANGES","POSTGRES"],...t.map(n=>[String(n.id),n.name,`${n.host}:${n.port}/${n.dbname}`,n.state,n.home?"yes":"",String(n.ranges.length),n.server_version_num?String(Math.floor(n.server_version_num/1e4)):"?"])]))}function fh(e){let t=process.stdout;if(e.cloud&&(bh(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){R(t,"The home node has no Lepis catalog yet (schema lepis).");return}R(t,`${S("Cluster")} at epoch ${e.epoch}`),R(t),Zl(e);for(let s of e.keyspaces??[]){R(t),R(t,`${S(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),R(t,L([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Vl(e,a.node)])]));for(let a of s.pins)R(t,` pinned: ${a.value} on ${Vl(e,a.node)}`)}let n=e.tables??[];n.length>0&&(R(t),R(t,L([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(R(t),R(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&&R(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(y(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function gh(e,t){if(t.joining==="failed"&&t.next!=="delete-pod")return`stopped joining: ${t.joinError??"see the audit log"}. Retry: snoutdata cluster retry ${t.ref} --project ${e.ref}`;if(t.replicaOf)return`replica of node ${t.replicaOf} (${t.state})`;switch(t.next){case"attach":return`ready: snoutdata cluster nodes attach ${t.ref} --project ${e.ref}`;case"delete-pod":return`let go by the cluster: snoutdata cluster nodes remove ${t.ref} --project ${e.ref} deletes its pod`;case"wait":return`starting as a copy of the home node (${t.state})`;case"joining":return t.joining==="spreading"?"joining: taking its share of the data":t.joining==="joining"?"joining the cluster":`joining: starting as a copy of the home node (${t.state})`;default:return t.node?`node ${t.node.id}, ${t.node.state}`:t.state}}function hh(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function Yl(e){return e.severity.toUpperCase()}function Ql(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)R(t,`${S(Yl(a))} ${a.message}${a.acknowledged?y(" (seen)"):""}`);o.length>0&&R(t,y(` 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)R(t,`${S(Yl(a))} ${a.message}`),R(t,y(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&R(t),R(t,"Resolved in the last day:");for(let a of s)R(t,` ${a.message}${a.resolution?` ${y(a.resolution)}`:""}`)}}function yh(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function wh(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 bh(e,t){let n=wh(e);if(!e.sharded){R(t,`${S(e.ref)} is not a cluster (${n}).`),yh(e)>0&&R(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}Ql(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(l=>l.desiredState!=="deleted"),o=1+r.filter(l=>!l.replicaOf).length,s=r.length-(o-1);if(R(t,`${S(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let l=e.pods.some(i=>i.replicaOf!==void 0);R(t,L([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,gh(e,i),...l?[hh(i)]:[]])]))}e.router==="starting"?R(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&R(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&R(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?R(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&R(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),R(t)}function _h(e){if(e.length===0){d("No jobs yet.");return}R(process.stdout,L([["ID","OPERATION","STATE","STEPS","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function Sh(e){let t=process.stdout;R(t,`${S(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),R(t,L([[" 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)&&(R(t),we(e.plan,t))}oe();W();kt();import{spawn as kh}from"node:child_process";import{createHash as vh}from"node:crypto";import{chmodSync as xh,renameSync as sr,rmSync as ed,writeFileSync as $h}from"node:fs";import{dirname as Ph,join as Eh}from"node:path";var ar=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Ah="https://github.com/snoutdata/app/releases/download";function Rh(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 Th(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 Oh(){let e;try{e=await fetch(`${ar}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new k("network",`could not reach ${ar} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new k("server",`${ar} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new k("server",`${ar} did not say which version is newest`);return t.version}async function td(e){let t;try{t=await fetch(e)}catch(n){throw new k("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new k("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Nh(e,t){let n=Eh(Ph(e),`.snoutdata-upgrade-${process.pid}`);$h(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;ed(r,{force:!0}),sr(e,r);try{sr(n,e)}catch(o){throw sr(r,e),o}}else xh(n,493),sr(n,e)}catch(r){ed(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new k("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?Ne:St}`):r}}function jh(){return new Promise((e,t)=>{let n=kh(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new k("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${Ne}`))),n.on("exit",r=>e(r??1))})}async function nd(e={}){let t=e.kind??mn(),n=await Oh(),r=js(Q,n),o={installed:Q,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{d(r?`snoutdata ${Q} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${Q} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${Q}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new k("usage",`this snoutdata (${Q}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?Ne:St}`);case"npm":{d(`Upgrading snoutdata ${Q} to ${n} with npm\u2026`);let s=await jh();if(s!==0)throw new k("failed",`npm exited ${s}. Run it yourself: ${Ne}`);return g({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Rh(process.platform,process.arch);if(!s)throw new k("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${Ne}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Ah}/cli-v${n}`,[l,i]=await Promise.all([td(`${a}/${s}`),td(`${a}/SHA256SUMS`)]),u=Th(i.toString("utf8"),s);if(!u)throw new k("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=vh("sha256").update(l).digest("hex");if(c!==u)throw new k("failed",`checksum mismatch for ${s}. Refusing to install.
630
+ `),d(w(`Default settings: ${Object.entries(c.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return Sh(e,i);case"advise":case"advice":{let c=ze(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return g(m,()=>xh(m,process.stdout)),0}case"jobs":return kh(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return g({nodes:c.nodes??[]},()=>sd(c)),0}break;case"plan":{let c=td(n.slice(1),e,l),m=await i().plan(c.body);return g(m,()=>we(m,process.stdout)),0}}let u=td(n,e,l);if(A(e,"plan")){let c=await i().plan(u.body);return g(c,()=>we(c,process.stdout)),0}return bh(u,e,i,t)}async function bh(e,t,n,r){let o=A(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!ae())throw new f(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(we(u,process.stderr),!await(r.confirm??bt)(`Run ${s} on ${a.target}?`))throw new v("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return g(l.answer,()=>d(l.done)),0;if(A(t,"no-wait"))return g({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
631
+ `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await ns(a,l.job,r);return rs(i)}function wt(e,t){if(e===void 0||e==="")throw new f(t);return e}async function bt(e){let t=ph({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 _h(e){let t=[...Yr,...si],n={};for(let r of ar(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 f(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new f(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function Sh(e,t){let n={};for(let s of Yr)Object.assign(n,ze(e,es[s],s));let r=$(e,"set");if(r!==void 0&&Object.assign(n,_h(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return g({settings:a},()=>process.stdout.write(`${M(Object.entries(a).map(([l,i])=>[l,String(i)]))}
632
+ `)),0}let o=await t().settings(n);return g(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function kh(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return g({jobs:s},()=>Oh(s)),0}case"show":{let s=await r.job(sr(t,"show"));return g(s,()=>Nh(s)),0}case"watch":{let s=await ns(r,sr(t,"watch"),o);return rs(s)}case"cancel":{let s=sr(t,"cancel");if(!A(n,"yes")){if(!o.confirm&&!ae())throw new f(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??bt)(`Cancel job ${s} on ${r.target}?`))throw new v("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return g(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=sr(t,"resume"),a=await r.resume(s);return A(n,"no-wait")?(g(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):rs(await ns(r,s,o))}default:throw new f(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function ns(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(w(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Ot(a.state))return a;if(Date.now()>=o)throw new v("timeout",`job ${t} is still ${a.state}; it carries on without this command. Follow it again: snoutdata cluster jobs watch ${t}`,{job:a});await new Promise(l=>setTimeout(l,r))}}function rs(e){if(e.state==="done")return g(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
633
+ `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata cluster jobs resume ${e.id}`:"was cancelled";throw new v("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function T(e,t=""){e.write(`${t}
634
+ `)}function os(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function we(e,t){T(t,`${k(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&T(t,M([[" FROM","TO","TABLES","ROWS","SIZE","COPY","PAUSE"],...e.moves.map(n=>[` node ${n.source}`,n.targets.map(r=>`node ${r}`).join(", ")||"-",n.tables.join(", ")||"-",`~${n.estimated_rows.toLocaleString("en-US")}`,os(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),T(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${os(e.estimated_bytes)}, ~${e.estimated_copy_seconds}s to copy, expected write pause ~${e.expected_pause_ms}ms (ceiling ${e.max_write_pause_ms}ms)`),T(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)T(t,` warning: ${n}`);e.assumptions&&T(t,w(` ${e.assumptions}`))}function ke(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function vh(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${ke(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${ke(t.at)}`:""}${t.to!==void 0?` --to ${ke(t.to)}`:""}`;case"range.move":return`${n} range move ${ke(`${t.keyspace}:${t.range}`)} --to ${ke(t.to)}`;case"tenant.pin":return`${n} tenant pin ${ke(t.keyspace)} --value ${ke(t.value)}${t.node!==void 0?` --node ${ke(t.node)}`:""}`;case"node.add":return`${n} nodes add ${ke(t.name)} --host ${t.host?ke(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${ke(t.keyspace)}`:""}`;default:return null}}function xh(e,t){T(t,e.summary),e.advice.forEach((n,r)=>{T(t),T(t,`${k(`${r+1}. ${n.op}`)} (${n.metric})`),T(t,` ${n.reason}`);let o=vh(n.request);if(T(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;T(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${os(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])T(t,` warning: ${a}`)}else n.needs.length>0?T(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&T(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(T(t),T(t,w(e.assumptions)))}function nd(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function sd(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}T(process.stdout,M([["ID","NAME","ADDRESS","STATE","HOME","RANGES","POSTGRES"],...t.map(n=>[String(n.id),n.name,`${n.host}:${n.port}/${n.dbname}`,n.state,n.home?"yes":"",String(n.ranges.length),n.server_version_num?String(Math.floor(n.server_version_num/1e4)):"?"])]))}function $h(e){let t=process.stdout;if(e.cloud&&(Th(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){T(t,"The home node has no Lepis catalog yet (schema lepis).");return}T(t,`${k("Cluster")} at epoch ${e.epoch}`),T(t),sd(e);for(let s of e.keyspaces??[]){T(t),T(t,`${k(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),T(t,M([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,nd(e,a.node)])]));for(let a of s.pins)T(t,` pinned: ${a.value} on ${nd(e,a.node)}`)}let n=e.tables??[];n.length>0&&(T(t),T(t,M([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(T(t),T(t,`Routers: ${r.map(s=>`${s.id} (epoch ${s.epoch}${s.live?"":", not heard from"}${s.current?"":", behind"})`).join("; ")}`));let o=e.jobs??[];o.length>0&&T(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(w(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function Ph(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 Eh(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function rd(e){return e.severity.toUpperCase()}function ad(e,t,n,r="<ref>"){let o=e.filter(a=>a.resolvedAt===null),s=e.filter(a=>a.resolvedAt!==null);if(!n){for(let a of o)T(t,`${k(rd(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&T(t,w(` See them all, and mark one as seen: snoutdata cluster alerts --project ${r}`));return}if(e.length===0){d("No alerts. Nothing has happened to this cluster that needs you.");return}for(let a of o)T(t,`${k(rd(a))} ${a.message}`),T(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&T(t),T(t,"Resolved in the last day:");for(let a of s)T(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Ah(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function Rh(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 Th(e,t){let n=Rh(e);if(!e.sharded){T(t,`${k(e.ref)} is not a cluster (${n}).`),Ah(e)>0&&T(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}ad(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(l=>l.desiredState!=="deleted"),o=1+r.filter(l=>!l.replicaOf).length,s=r.length-(o-1);if(T(t,`${k(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let l=e.pods.some(i=>i.replicaOf!==void 0);T(t,M([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,Ph(e,i),...l?[Eh(i)]:[]])]))}e.router==="starting"?T(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&T(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&T(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?T(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&T(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),T(t)}function Oh(e){if(e.length===0){d("No jobs yet.");return}T(process.stdout,M([["ID","OPERATION","STATE","STEPS","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function Nh(e){let t=process.stdout;T(t,`${k(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),T(t,M([[" STEP","KIND","STATE","PHASE"],...e.steps.map(n=>[` ${n.n+1}`,n.kind,n.state,typeof n.detail?.phase=="string"?n.detail.phase:""])])),e.plan&&Array.isArray(e.plan.steps)&&(T(t),we(e.plan,t))}se();W();vt();import{spawn as jh}from"node:child_process";import{createHash as Ch}from"node:crypto";import{chmodSync as Ih,renameSync as ir,rmSync as id,writeFileSync as Dh}from"node:fs";import{dirname as Mh,join as Lh}from"node:path";var cr=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Uh="https://github.com/snoutdata/studio/releases/download";function Fh(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 Bh(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 Hh(){let e;try{e=await fetch(`${cr}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new v("network",`could not reach ${cr} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new v("server",`${cr} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new v("server",`${cr} did not say which version is newest`);return t.version}async function cd(e){let t;try{t=await fetch(e)}catch(n){throw new v("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new v("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Gh(e,t){let n=Lh(Mh(e),`.snoutdata-upgrade-${process.pid}`);Dh(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;id(r,{force:!0}),ir(e,r);try{ir(n,e)}catch(o){throw ir(r,e),o}}else Ih(n,493),ir(n,e)}catch(r){id(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new v("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?je:kt}`):r}}function qh(){return new Promise((e,t)=>{let n=jh(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new v("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${je}`))),n.on("exit",r=>e(r??1))})}async function ld(e={}){let t=e.kind??pn(),n=await Hh(),r=Is(ne,n),o={installed:ne,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{d(r?`snoutdata ${ne} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ne} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ne}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new v("usage",`this snoutdata (${ne}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?je:kt}`);case"npm":{d(`Upgrading snoutdata ${ne} to ${n} with npm\u2026`);let s=await qh();if(s!==0)throw new v("failed",`npm exited ${s}. Run it yourself: ${je}`);return g({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Fh(process.platform,process.arch);if(!s)throw new v("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${je}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Uh}/cli-v${n}`,[l,i]=await Promise.all([cd(`${a}/${s}`),cd(`${a}/SHA256SUMS`)]),u=Bh(i.toString("utf8"),s);if(!u)throw new v("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=Ch("sha256").update(l).digest("hex");if(c!==u)throw new v("failed",`checksum mismatch for ${s}. Refusing to install.
635
635
  expected ${u}
636
- actual ${c}`);return Nh(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}bn();Ie();kt();var rd=`snoutdata ${Q} \u2014 hosted Postgres, from a terminal or an agent
636
+ actual ${c}`);return Gh(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Sn();Me();vt();var dd=`snoutdata ${ne} \u2014 hosted Postgres, from a terminal or an agent
637
637
 
638
638
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
639
639
  --env writes DATABASE_URL into ./.env (and adds
@@ -786,11 +786,11 @@ that re-signs Postgres traffic.
786
786
  Docs: https://docs.snoutdata.com/developers/cli
787
787
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
788
788
  Source: https://github.com/snoutdata/snout-cli
789
- `,Ch=["yes","token"];async function Dh(e){let[t,n,...r]=e.command;if(E(e,"version")||t==="version")return g({version:Q},()=>process.stdout.write(`${Q}
790
- `)),0;if(E(e,"help")&&t&&t!=="help"){let i=cs(t,n);if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,lr(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${m}
789
+ `,Kh=["yes","token"];async function Jh(e){let[t,n,...r]=e.command;if(A(e,"version")||t==="version")return g({version:ne},()=>process.stdout.write(`${ne}
790
+ `)),0;if(A(e,"help")&&t&&t!=="help"){let i=ds(t,n);if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,ur(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${m}
791
791
  ${u.summary}
792
- `);let h=Math.max(0,...u.flags.map(x=>x.length));for(let x of u.flags)process.stdout.write(` ${x.padEnd(h)} ${lr(u.name,x)}
792
+ `);let h=Math.max(0,...u.flags.map(x=>x.length));for(let x of u.flags)process.stdout.write(` ${x.padEnd(h)} ${ur(u.name,x)}
793
793
  `)}process.stdout.write(`
794
794
  Every command also takes --json, --quiet and --help.
795
- `)}),0}let o=E(e,"help")||t==="help";if(o||!t)return g({version:Q,usage:rd.trim(),commands:cr},()=>process.stdout.write(rd)),o?0:Oe.usage;let s=()=>{let i=be(e,"timeout");if(i!==void 0){if(!(i>0))throw new f("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Zt({flag:$(e,"ref")});if(!i)throw new f("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Zt({flag:$(e,"ref")});return i?Ce(i):null};for(let i of Ch)if(e.flags[i]!==void 0&&t!=="cluster")throw new f(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Xl(e,{timeoutMs:s()});case"login":{if(E(e,"device")){let m=await pn({name:sn(),platform:process.platform});g({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(y("Waiting for that to be approved\u2026"))});let h=await fn(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ve({accessToken:h.token});let x=$(e,"email"),U=x?(await Ye()).email??void 0:void 0;return x&&xr(x,{accessToken:h.token,email:U}),g({ok:!0,name:h.name??null,email:U??null},()=>d(`Signed in${U?` as ${U}`:""}.`)),0}let i=$(e,"domain"),u=E(e,"sso")||i!==void 0;if(u&&$(e,"provider"))throw new f("--sso and --provider are two different doors: pass one or the other");let c=await wn({provider:$(e,"provider"),sso:u,domain:i,noBrowser:E(e,"no-browser")||!se(),timeoutMs:s(),email:$(e,"email")});return g({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{fs();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(y(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Ye(),u=Xt()?.expectedEmail,c=!!(u&&i.email&&!$r(u,i.email));return c&&X(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),g({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
796
- `),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 ba(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("tokens create needs --name (what is it for, and where does it live)");return await wa({name:i,expiresInDays:be(e,"expires"),project:$(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new f("tokens revoke needs the token's id or its sdt_ prefix");return await _a(i),0}default:throw new f(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await pa(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("projects create needs --name");let u=$(e,"team");return await Nr({teamId:u?await ya(u):void 0,name:i,region:$(e,"region"),wait:!E(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:E(e,"show-url")}),0}case"pause":case"resume":case"delete":return await fa(n,$(e,"ref")??r[0]??a(),{wait:!E(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=$(e,"ref")??r[0]??a(),u=Ce(i);u?Xs(u):await yi(i)}return 0;default:throw new f(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await aa(i,r[0],{dir:$(e,"dir"),verifyJwt:!E(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return ia(i),0;if((n==="delete"||n==="remove")&&r[0])return await ca(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await Ea(a(),u,{dir:$(e,"dir"),entrypoint:$(e,"entrypoint"),verifyJwt:!E(e,"no-verify-jwt")}),0}case void 0:case"list":return await Ta(a()),0;case"size":{let u=r[0];if(!u)throw new f("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await Ra(a(),u,{memoryMb:be(e,"memory"),concurrency:be(e,"concurrency"),reset:E(e,"reset")}),0}case"logs":return await Oa(a(),r[0],{errorsOnly:E(e,"errors"),limit:be(e,"tail"),follow:E(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new f("functions delete needs a name");return await Na(a(),u),0}default:throw new f(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=$(e,"dockerfile");if(i&&c){if(u)throw new f("containers deploy takes an image or --dockerfile, not both");if($(e,"registry-user")||E(e,"clear-registry"))throw new f("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Ha(a(),i,{path:c,port:be(e,"port"),wait:!E(e,"no-wait")}),0}if(!i||!u)throw new f("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let m=$(e,"registry-user");if(m&&!E(e,"stdin"))throw new f('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await Ua(a(),i,{image:u,port:be(e,"port"),registryUser:m,registryPassword:m?await On():void 0,clearCredential:E(e,"clear-registry"),wait:!E(e,"no-wait")}),0}case void 0:case"list":return await Ga(a()),0;case"logs":return await Ja(a(),r[0],{errorsOnly:E(e,"errors"),limit:be(e,"tail"),follow:E(e,"follow")}),0;case"restart":{if(!r[0])throw new f("containers restart needs a name");return await qa(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new f("containers delete needs a name");return await Ka(a(),r[0]),0}default:throw new f(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await da(i,await Va(r,{stdin:E(e,"stdin")})),0;if(n===void 0||n==="list")return ua(i),0;if((n==="unset"||n==="remove")&&r[0])return await ma(i,r[0]),0}switch(n){case"set":return await Ya(a(),r,{stdin:E(e,"stdin")}),0;case void 0:case"list":return await Za(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new f("secrets unset needs a name");return await Xa(a(),u),0}default:throw new f(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await Sa(a()),0;case"rotate":return await ka(a(),{force:E(e,"force")}),0;default:throw new f(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await mi(a()),0;if(n==="enable"||n==="disable")return await pi(a(),ui(r[0]),n==="enable"),0;throw new f(`unknown command: products ${n}. products [enable|disable ${Dn.join("|")}]`)}case"push":{if(n!=="credentials")throw new f("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await Gi(a()),0;if(i==="set"){let u=ho(r[1]),c=u==="apns"?await Bi({p8:$(e,"p8"),keyId:$(e,"key-id"),teamId:$(e,"team-id"),topic:$(e,"topic"),environment:$(e,"environment")}):await Hi($(e,"file"));return await qi(a(),u,c),0}if(i==="remove"||i==="unset")return await Ki(a(),ho(r[1])),0;throw new f(`unknown command: push credentials ${i}`)}case"auth":return await Ii(a(),n,r,{clientId:$(e,"client-id"),stdin:E(e,"stdin"),siteUrl:$(e,"site-url"),allow:$(e,"allow"),file:$(e,"file"),subject:$(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Oi(a(),{channel:$(e,"channel"),watch:E(e,"watch")});if(n==="logs")return Ni(a(),{channel:$(e,"channel"),since:$(e,"since")});throw new f(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await fi(a(),n,r[0]),0;case"teams":return await ha(),0;case"link":{if(E(e,"local")){let c;try{c=bs(n)}catch(h){throw new k("not-found",h instanceof Error?h.message:String(h))}let m=ot(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return g({ref:c.ref,local:c.folder,api:c.apiUrl,path:m},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${m}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=$(e,"ref")??n;if(!i)throw new f("link needs --ref");let u=ot(process.cwd(),{ref:i});return g({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await lo(a()),0;case"psql":return xi(a(),r);case"reset-password":return await ga(a()),0;case"access":return await Ai(a(),r,{level:$(e,"level")}),0;case"push":return Ui(a(),{dir:$(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return E(e,"window")?(await gi(a()),0):$(e,"at")?(await hi(a(),$(e,"at"),$(e,"name")),0):Ei(a(),{file:$(e,"file"),force:!!e.flags.force});case"export":return Pi(a(),{timeoutMs:s(),out:$(e,"out"),statusOnly:!!e.flags.status});default:throw new f(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new f(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=E(e,"local")?await Hl(await Vt()):null,u=i?i.dbUrl:$(e,"db-url");return Vi(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:($(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:$(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Vs(i)}return Ul(await Vt(),{port:be(e,"port"),dir:$(e,"dir"),noMigrations:E(e,"no-migrations"),outOfOrder:E(e,"out-of-order")});case"stop":{let i=l();return i?Ys(i):Fl(await Vt())}case"status":{let i=l();return i?Ws(i):Bl(await Vt())}case"usage":return zl(a(),{days:be(e,"days"),history:!!e.flags.history});case"mcp":return Kl({version:Q,allowDelete:!!e.flags["allow-delete"]});case"init":return Ih(e);case"upgrade":return nd({check:E(e,"check")});default:throw new f(`unknown command: ${t}`)}}async function Ih(e){let t=ys({flag:$(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),E(e,"env")?await od(o):await lo(o),t.kind==="link"){let s=Ce(o),a=ot(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=$(e,"name")??Mh(process.cwd());d(`Creating a project called "${n}"\u2026`),await Nr({name:n,region:$(e,"region"),wait:!0,link:process.cwd(),showUrl:!E(e,"env")});let r=Zt({cwd:process.cwd()});return r&&E(e,"env")&&await od(r),0}async function od(e){let{connection:t}=await Promise.resolve().then(()=>(Y(),Ee)),n=await t(e),r=$i(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 Mh(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Lh(e){if(e instanceof k)return Fe(e.code,e.message,e.details),e.exitCode;if(e instanceof f)return Fe("usage",e.message),Oe.usage;if(e instanceof Be)return Fe("not-signed-in",e.message),Oe["not-signed-in"];if(e instanceof ge){let r=We(e.status,e.serverCode);return Fe(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Oe[r]}let t=ks(e);if(t)return Fe("usage",t),Oe.usage;let n=tn(e);return Fe(n,e instanceof Error?e.message:String(e)),Oe[n]}function rs(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Uh(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Fh(){xt(Uh(process.argv.slice(2)));let e;try{e=as(process.argv.slice(2))}catch(t){Fe("usage",String(t instanceof Error?t.message:t)),rs(Oe.usage);return}xt(e.json),Is(e.flags.quiet===!0),vs(nn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{rs(await Dh(e))}catch(t){rs(Lh(t))}}Fh();
795
+ `)}),0}let o=A(e,"help")||t==="help";if(o||!t)return g({version:ne,usage:dd.trim(),commands:dr},()=>process.stdout.write(dd)),o?0:Ne.usage;let s=()=>{let i=be(e,"timeout");if(i!==void 0){if(!(i>0))throw new f("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Qt({flag:$(e,"ref")});if(!i)throw new f("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Qt({flag:$(e,"ref")});return i?Ie(i):null};for(let i of Kh)if(e.flags[i]!==void 0&&t!=="cluster")throw new f(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return od(e,{timeoutMs:s()});case"login":{if(A(e,"device")){let m=await fn({name:an(),platform:process.platform});g({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let h=await gn(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});xe({accessToken:h.token});let x=$(e,"email"),L=x?(await Xe()).email??void 0:void 0;return x&&Pr(x,{accessToken:h.token,email:L}),g({ok:!0,name:h.name??null,email:L??null},()=>d(`Signed in${L?` as ${L}`:""}.`)),0}let i=$(e,"domain"),u=A(e,"sso")||i!==void 0;if(u&&$(e,"provider"))throw new f("--sso and --provider are two different doors: pass one or the other");let c=await _n({provider:$(e,"provider"),sso:u,domain:i,noBrowser:A(e,"no-browser")||!ae(),timeoutMs:s(),email:$(e,"email")});return g({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{hs();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Xe(),u=Zt()?.expectedEmail,c=!!(u&&i.email&&!Er(u,i.email));return c&&X(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),g({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
796
+ `),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 _a(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("tokens create needs --name (what is it for, and where does it live)");return await ba({name:i,expiresInDays:be(e,"expires"),project:$(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new f("tokens revoke needs the token's id or its sdt_ prefix");return await Sa(i),0}default:throw new f(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await fa(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("projects create needs --name");let u=$(e,"team");return await Cr({teamId:u?await wa(u):void 0,name:i,region:$(e,"region"),wait:!A(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:A(e,"show-url")}),0}case"pause":case"resume":case"delete":return await ga(n,$(e,"ref")??r[0]??a(),{wait:!A(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=$(e,"ref")??r[0]??a(),u=Ie(i);u?Zs(u):await bi(i)}return 0;default:throw new f(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await ia(i,r[0],{dir:$(e,"dir"),verifyJwt:!A(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return ca(i),0;if((n==="delete"||n==="remove")&&r[0])return await la(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await Aa(a(),u,{dir:$(e,"dir"),entrypoint:$(e,"entrypoint"),verifyJwt:!A(e,"no-verify-jwt")}),0}case void 0:case"list":return await Oa(a()),0;case"size":{let u=r[0];if(!u)throw new f("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await Ta(a(),u,{memoryMb:be(e,"memory"),concurrency:be(e,"concurrency"),reset:A(e,"reset")}),0}case"logs":return await Na(a(),r[0],{errorsOnly:A(e,"errors"),limit:be(e,"tail"),follow:A(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new f("functions delete needs a name");return await ja(a(),u),0}default:throw new f(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=$(e,"dockerfile");if(i&&c){if(u)throw new f("containers deploy takes an image or --dockerfile, not both");if($(e,"registry-user")||A(e,"clear-registry"))throw new f("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Ga(a(),i,{path:c,port:be(e,"port"),wait:!A(e,"no-wait")}),0}if(!i||!u)throw new f("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let m=$(e,"registry-user");if(m&&!A(e,"stdin"))throw new f('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await Fa(a(),i,{image:u,port:be(e,"port"),registryUser:m,registryPassword:m?await jn():void 0,clearCredential:A(e,"clear-registry"),wait:!A(e,"no-wait")}),0}case void 0:case"list":return await qa(a()),0;case"logs":return await za(a(),r[0],{errorsOnly:A(e,"errors"),limit:be(e,"tail"),follow:A(e,"follow")}),0;case"restart":{if(!r[0])throw new f("containers restart needs a name");return await Ka(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new f("containers delete needs a name");return await Ja(a(),r[0]),0}default:throw new f(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await ua(i,await Ya(r,{stdin:A(e,"stdin")})),0;if(n===void 0||n==="list")return ma(i),0;if((n==="unset"||n==="remove")&&r[0])return await pa(i,r[0]),0}switch(n){case"set":return await Xa(a(),r,{stdin:A(e,"stdin")}),0;case void 0:case"list":return await Qa(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new f("secrets unset needs a name");return await Za(a(),u),0}default:throw new f(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await ka(a()),0;case"rotate":return await va(a(),{force:A(e,"force")}),0;default:throw new f(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await fi(a()),0;if(n==="enable"||n==="disable")return await gi(a(),pi(r[0]),n==="enable"),0;throw new f(`unknown command: products ${n}. products [enable|disable ${Mn.join("|")}]`)}case"push":{if(n!=="credentials")throw new f("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await Ki(a()),0;if(i==="set"){let u=wo(r[1]),c=u==="apns"?await Gi({p8:$(e,"p8"),keyId:$(e,"key-id"),teamId:$(e,"team-id"),topic:$(e,"topic"),environment:$(e,"environment")}):await qi($(e,"file"));return await Ji(a(),u,c),0}if(i==="remove"||i==="unset")return await zi(a(),wo(r[1])),0;throw new f(`unknown command: push credentials ${i}`)}case"auth":return await Li(a(),n,r,{clientId:$(e,"client-id"),stdin:A(e,"stdin"),siteUrl:$(e,"site-url"),allow:$(e,"allow"),file:$(e,"file"),subject:$(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return ji(a(),{channel:$(e,"channel"),watch:A(e,"watch")});if(n==="logs")return Ci(a(),{channel:$(e,"channel"),since:$(e,"since")});throw new f(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await hi(a(),n,r[0]),0;case"teams":return await ya(),0;case"link":{if(A(e,"local")){let c;try{c=Ss(n)}catch(h){throw new v("not-found",h instanceof Error?h.message:String(h))}let m=st(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return g({ref:c.ref,local:c.folder,api:c.apiUrl,path:m},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${m}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=$(e,"ref")??n;if(!i)throw new f("link needs --ref");let u=st(process.cwd(),{ref:i});return g({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await mo(a()),0;case"psql":return Pi(a(),r);case"reset-password":return await ha(a()),0;case"access":return await Ti(a(),r,{level:$(e,"level")}),0;case"push":return Bi(a(),{dir:$(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return A(e,"window")?(await yi(a()),0):$(e,"at")?(await wi(a(),$(e,"at"),$(e,"name")),0):Ri(a(),{file:$(e,"file"),force:!!e.flags.force});case"export":return Ai(a(),{timeoutMs:s(),out:$(e,"out"),statusOnly:!!e.flags.status});default:throw new f(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new f(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=A(e,"local")?await Vl(await Yt()):null,u=i?i.dbUrl:$(e,"db-url");return Xi(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:($(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:$(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Ys(i)}return Jl(await Yt(),{port:be(e,"port"),dir:$(e,"dir"),noMigrations:A(e,"no-migrations"),outOfOrder:A(e,"out-of-order")});case"stop":{let i=l();return i?Xs(i):zl(await Yt())}case"status":{let i=l();return i?Vs(i):Wl(await Yt())}case"usage":return ed(a(),{days:be(e,"days"),history:!!e.flags.history});case"mcp":return Zl({version:ne,allowDelete:!!e.flags["allow-delete"]});case"init":return zh(e);case"upgrade":return ld({check:A(e,"check")});default:throw new f(`unknown command: ${t}`)}}async function zh(e){let t=bs({flag:$(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),A(e,"env")?await ud(o):await mo(o),t.kind==="link"){let s=Ie(o),a=st(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=$(e,"name")??Wh(process.cwd());d(`Creating a project called "${n}"\u2026`),await Cr({name:n,region:$(e,"region"),wait:!0,link:process.cwd(),showUrl:!A(e,"env")});let r=Qt({cwd:process.cwd()});return r&&A(e,"env")&&await ud(r),0}async function ud(e){let{connection:t}=await Promise.resolve().then(()=>(Y(),Ae)),n=await t(e),r=Ei(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 Wh(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Vh(e){if(e instanceof v)return Be(e.code,e.message,e.details),e.exitCode;if(e instanceof f)return Be("usage",e.message),Ne.usage;if(e instanceof He)return Be("not-signed-in",e.message),Ne["not-signed-in"];if(e instanceof ge){let r=Ve(e.status,e.serverCode);return Be(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ne[r]}let t=xs(e);if(t)return Be("usage",t),Ne.usage;let n=nn(e);return Be(n,e instanceof Error?e.message:String(e)),Ne[n]}function ss(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Yh(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Xh(){$t(Yh(process.argv.slice(2)));let e;try{e=cs(process.argv.slice(2))}catch(t){Be("usage",String(t instanceof Error?t.message:t)),ss(Ne.usage);return}$t(e.json),Ls(e.flags.quiet===!0),$s(rn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{ss(await Jh(e))}catch(t){ss(Vh(t))}}Xh();