snoutdata 0.17.5 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/snoutdata.mjs +222 -180
  2. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- var qd=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var vs=(e,t)=>{for(var n in t)qd(e,n,{get:t[n],enumerable:!0})};function $s(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]=Jd(o);if(Gd.has(s)){let l=a??e[++r];if(l===void 0)throw new g(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new g(`--${s} does not take a value`);if(!Kd.has(s))throw new g(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Jd(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function x(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function O(e,t){return e.flags[t]===!0}function $e(e,t){let n=x(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new g(`--${t} must be a whole number, not "${n}"`);return r}var g,Gd,Kd,he=j(()=>{"use strict";g=class extends Error{},Gd=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"]);Kd=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 zd,existsSync as Ps,mkdirSync as As,readFileSync as Rs,rmSync as Wd,writeFileSync as Ts}from"node:fs";import{homedir as rn}from"node:os";import{dirname as kr,join as Os,parse as Vd,resolve as Yd}from"node:path";function vr(e=rn()){return Os(e,".snoutdata","auth.json")}function on(e=rn()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=vr(e);if(!Ps(n))return null;try{let r=JSON.parse(Rs(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function Ae(e,t=rn()){let n=vr(t);As(kr(n),{recursive:!0}),Ts(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),zd(n,384)}function Ns(e=rn()){Wd(vr(e),{force:!0})}function Cs(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function js(e){return Os(e,".snoutdata","project.json")}function ct(e){let t=Yd(e),n=Vd(t).root;for(;;){let r=js(t);if(Ps(r))try{let o=JSON.parse(Rs(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return typeof o.local=="string"&&o.local?{ref:o.ref,directory:t,local:o.local}:{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=kr(t)}}function lt(e,t){let n=js(e);return As(kr(n),{recursive:!0}),Ts(n,`${JSON.stringify(t,null,2)}
4
- `),n}function sn(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:ct(e.cwd??process.cwd())?.ref??null}function Is(e){let t=e.environment??process.env,n=ct(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Fe=j(()=>{"use strict"});import{existsSync as $r,readFileSync as Ds}from"node:fs";import{homedir as Xd}from"node:os";import{isAbsolute as Zd,join as Be,resolve as dt}from"node:path";function Qd(e=process.env,t=process.platform,n=Xd()){let r;return t==="win32"?r=e.APPDATA??Be(n,"AppData","Roaming"):t==="darwin"?r=Be(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Be(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Be(r,o,"localProjects.json"))}function an(e=Qd()){let t=[];for(let n of e)if($r(n))try{let r=JSON.parse(Ds(n,"utf8"));for(let o of r.projects??[])typeof o.folder=="string"&&o.folder&&!t.some(s=>s.folder===o.folder)&&t.push({name:o.name??null,folder:o.folder,ref:o.ref??null})}catch{}return t}function cn(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function Et(e){return $r(Be(e,"compose.yaml"))&&$r(Be(e,".env"))}function He(e,t=null){let n=dt(e);if(!Et(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=cn(Ds(Be(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Be(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function Ms(e,t=process.cwd(),n=an()){if(!e){if(Et(t))return He(t);let[i]=n;if(n.length===1&&i)return He(i.folder,i.name);throw n.length===0?new Error("there is no local project to link: set one up in Studio (Projects, Local), or pass the folder of a stack you made with snout-stack"):new Error(`there are ${n.length} local projects; name one: ${n.map(u=>u.name??u.folder).join(", ")}`)}let r=Zd(e)?e:dt(t,e);if(Et(r)){let i=n.find(u=>dt(u.folder)===dt(r));return He(r,i?.name??null)}let o=e.toLowerCase(),s=n.filter(i=>i.name?.toLowerCase()===o||i.ref===e),[a]=s;if(s.length===1&&a)return He(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function Ls(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 Us(e,t,n=an()){if(t&&Et(t))try{let r=n.find(s=>dt(s.folder)===dt(t)),o=He(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&Et(r.folder))return He(r.folder,r.name);return null}var Pt=j(()=>{"use strict"});function U(e,t,n){throw new v(e,t,n)}function et(e,t){return t==="no-capacity"?"no-capacity":e===400&&t==="region"?"usage":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function Fs(e){let t=e;return!t||typeof t!="object"||t.code!=="ENOENT"||typeof t.path!="string"||t.syscall==="spawn"?null:`no file at ${t.path}`}function ln(e){let t=[e],n=e;for(let r=0;r<4&&n instanceof Error&&n.cause;r+=1)n=n.cause,t.push(n);for(let r of t){let o=typeof r=="object"&&r&&"code"in r?String(r.code):"";if(eu.has(o))return"network";if(o==="ABORT_ERR"||o==="ETIMEDOUT")return"timeout"}return e instanceof Error&&/fetch failed|network|socket hang up/i.test(e.message)?"network":"failed"}var Ie,v,eu,pe=j(()=>{"use strict";Ie={ok:0,failed:1,usage:2,"not-signed-in":3,"not-ready":4,forbidden:5,"not-found":6,conflict:7,quota:8,network:9,timeout:10,outdated:11,"no-capacity":12,"tool-missing":127,server:1},v=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ie[this.code]}};eu=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function dn(e){let t=e.env.SNOUTDATA_NO_INTERACTIVE;if(t!==void 0&&t!==""&&t!=="0"&&t!=="false")return{canAsk:!1,reason:"opted-out"};if(e.json)return{canAsk:!1,reason:"json"};for(let n of tu){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function ut(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function Bs(e){xr=e}function me(){return(xr??dn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function pt(){return(xr??dn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var tu,xr,tt=j(()=>{"use strict";tu=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];xr=null});import{createHmac as nu,randomBytes as ru,timingSafeEqual as ou}from"node:crypto";import{existsSync as su,readFileSync as au}from"node:fs";import{homedir as Er,hostname as iu}from"node:os";import{join as mt}from"node:path";function cu(e,t,n){if(t==="win32"){let r=n.APPDATA??mt(Er(),"AppData","Roaming");return mt(r,e)}return t==="darwin"?mt(Er(),"Library","Application Support",e):mt(n.XDG_CONFIG_HOME??mt(Er(),".config"),e)}function qs(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>mt(cu(n,e,t),"mcp.json"))}function lu(e=process.platform,t=process.env){return qs(e,t)[0]}function du(e=lu()){if(!su(e))return null;try{let t=JSON.parse(au(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function uu(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function mu(e,t,n){return nu("sha256",e).update(`${pu}
2
+ var Hu=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var Hs=(e,t)=>{for(var n in t)Hu(e,n,{get:t[n],enumerable:!0})};function Ks(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]=Gu(o);if(Ku.has(s)){let l=a??e[++r];if(l===void 0)throw new m(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new m(`--${s} does not take a value`);if(!qu.has(s))throw new m(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Gu(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function k(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function N(e,t){return e.flags[t]===!0}function _e(e,t){let n=k(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new m(`--${t} must be a whole number, not "${n}"`);return r}var m,Ku,qu,be=j(()=>{"use strict";m=class extends Error{},Ku=new Set(["name","sort","at","region","ref","file","provider","domain","expires","project","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint","memory","concurrency","tail","registry-user","dockerfile","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level","admin","token","host","dbname","sslmode","peer-host","to","key-type","ranges","seed","nodes","column","keyspace","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms","source","model","base-url","price-in","price-out","access-key-id","auth-header","models","amount","period","by","prompt"]);qu=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","clear-registry","quiet","yes","plan","status","no-wait","no-join","queue","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch","soft"])});import{chmodSync as zu,existsSync as zs,mkdirSync as Ws,readFileSync as Js,rmSync as Wu,writeFileSync as Vs}from"node:fs";import{homedir as mn}from"node:os";import{dirname as Dr,join as Ys,parse as Ju,resolve as Vu}from"node:path";function Mr(e=mn()){return Ys(e,".snoutdata","auth.json")}function fn(e=mn()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=Mr(e);if(!zs(n))return null;try{let r=JSON.parse(Js(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function Oe(e,t=mn()){let n=Mr(t);Ws(Dr(n),{recursive:!0}),Vs(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),zu(n,384)}function Xs(e=mn()){Wu(Mr(e),{force:!0})}function Zs(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Qs(e){return Ys(e,".snoutdata","project.json")}function mt(e){let t=Vu(e),n=Ju(t).root;for(;;){let r=Qs(t);if(zs(r))try{let o=JSON.parse(Js(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 ft(e,t){let n=Qs(e);return Ws(Dr(n),{recursive:!0}),Vs(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function gn(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:mt(e.cwd??process.cwd())?.ref??null}function ea(e){let t=e.environment??process.env,n=mt(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var qe=j(()=>{"use strict"});import{existsSync as Lr,readFileSync as ta}from"node:fs";import{homedir as Yu}from"node:os";import{isAbsolute as Xu,join as Ge,resolve as gt}from"node:path";function Zu(e=process.env,t=process.platform,n=Yu()){let r;return t==="win32"?r=e.APPDATA??Ge(n,"AppData","Roaming"):t==="darwin"?r=Ge(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Ge(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Ge(r,o,"localProjects.json"))}function hn(e=Zu()){let t=[];for(let n of e)if(Lr(n))try{let r=JSON.parse(ta(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 yn(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 Nt(e){return Lr(Ge(e,"compose.yaml"))&&Lr(Ge(e,".env"))}function ze(e,t=null){let n=gt(e);if(!Nt(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=yn(ta(Ge(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Ge(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 na(e,t=process.cwd(),n=hn()){if(!e){if(Nt(t))return ze(t);let[i]=n;if(n.length===1&&i)return ze(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=Xu(e)?e:gt(t,e);if(Nt(r)){let i=n.find(u=>gt(u.folder)===gt(r));return ze(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 ze(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 ra(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 oa(e,t,n=hn()){if(t&&Nt(t))try{let r=n.find(s=>gt(s.folder)===gt(t)),o=ze(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&Nt(r.folder))return ze(r.folder,r.name);return null}var It=j(()=>{"use strict"});function H(e,t,n){throw new P(e,t,n)}function Le(e,t){return t==="no-capacity"?"no-capacity":e===400&&t==="region"?"usage":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429||t==="provider_billing"?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function sa(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 wn(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(Qu.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 Me,P,Qu,me=j(()=>{"use strict";Me={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},P=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Me[this.code]}};Qu=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function bn(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 ep){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 ht(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 aa(e){Ur=e}function ye(){return(Ur??bn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function yt(){return(Ur??bn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var ep,Ur,st=j(()=>{"use strict";ep=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];Ur=null});import{createHmac as tp,randomBytes as np,timingSafeEqual as rp}from"node:crypto";import{existsSync as op,readFileSync as sp}from"node:fs";import{homedir as Fr,hostname as ap}from"node:os";import{join as wt}from"node:path";function ip(e,t,n){if(t==="win32"){let r=n.APPDATA??wt(Fr(),"AppData","Roaming");return wt(r,e)}return t==="darwin"?wt(Fr(),"Library","Application Support",e):wt(n.XDG_CONFIG_HOME??wt(Fr(),".config"),e)}function ca(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>wt(ip(n,e,t),"mcp.json"))}function cp(e=process.platform,t=process.env){return ca(e,t)[0]}function lp(e=cp()){if(!op(e))return null;try{let t=JSON.parse(sp(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function dp(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function pp(e,t,n){return tp("sha256",e).update(`${up}
5
5
  ${t}
6
- ${n}`).digest("base64url")}function fu(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&ou(n,r)}async function Ks(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=ru(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(Gs)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&fu(o.proof,mu(e.token,t,n))}catch{return!1}}async function Ar(e,t,n,r=Ks){if(!await r(e).catch(()=>!1))throw new un(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function pn(e,t=Ks){if(e!==void 0)return Hs(e,t);let n="no-config";for(let r of qs()){let o=du(r);if(!o)continue;let s=await Hs(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 Hs(e,t){let n=uu(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Ar(r,"/cli/whoami",{signal:AbortSignal.timeout(Gs)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof un?{available:!1,skip:"not-verified"}:{available:!1,skip:"not-running"}}return o.signedIn?o.canMintToken?{available:!0,who:o,config:r}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function Js(e,t){let n=await Ar(e,"/cli/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client:"snoutdata-cli",command:t}),signal:AbortSignal.timeout(6e4)}),r=await n.json().catch(()=>({}));return n.ok&&r.ok&&r.token?{token:r.token,name:r.name??"SnoutData CLI"}:{declined:!0,reason:r.error??`SnoutData Studio refused (${n.status})`}}function mn(){return iu()}async function Pr(e,t,n,r){let o=await Ar(e,"/mcp",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:t,params:n}),signal:AbortSignal.timeout(r)});if(!o.ok)throw new Error(`the app answered ${o.status}`);let s=await o.json();if(s.error)throw new Error(s.error.message??"SnoutData Studio refused");return s.result}async function zs(e){return await Pr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await Pr(e,"tools/list",{},3e3)).tools??[]}async function Ws(e,t,n){return Pr(e,"tools/call",{name:t,arguments:n},12e4)}var Gs,pu,un,fn=j(()=>{"use strict";Gs=1500,pu="snoutdata-studio-proof-v1";un=class extends Error{constructor(t){super(`the process on 127.0.0.1:${t} did not prove it is SnoutData Studio, so Studio's token was not sent to it`),this.name="PeerNotVerified"}}});function Rr(e){try{return new URL(e).host}catch{return e}}function hn(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function qe(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function yu(e){return gn.has(Rr(e))}function wu(e,t,n){return t===void 0?e:{...e,[n?hu:gu]:t}}async function Re(e,t,n={},r=fetch){let o=Rr(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:wu(s,n.key,u),body:n.body})}if(gn.has(o))return a(!0);let l=await a(!1);if(l.status!==404)return l;let i=await a(!0);return i.status===404?l:(gn.add(o),i)}async function Ys(e,t,n,r=fetch){if(!gn.has(Rr(e)))try{await Re(e,qe("/health","/health"),{key:n},r)}catch{}return`${e}${yu(e)?t.oldPath:t.path}`}var gu,hu,gn,yn=j(()=>{"use strict";gu="Snout-Key",hu="apikey",gn=new Set});import{basename as bu}from"node:path";function bn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=bu(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 Su(e=bn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?De:At}}function Tr(e={}){let t=e.kind??bn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?De:`${At} (or ${De})`;return`snoutdata ${ie} is installed and ${r}. Run \`${Su(t,n)}\`, or install again: ${o}`}function Xs(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 ie,wn,At,De,Rt=j(()=>{"use strict";ie="0.17.5",wn=`snoutdata-cli/${ie}`,At="curl -fsSL https://snoutdata.com/install.sh | sh",De="npm install -g snoutdata@latest"});async function Zs(e,t){let n=await Re(Te,hn(e),{method:"POST",key:Oe,headers:{"Content-Type":"application/json","x-client-info":wn},body:JSON.stringify(t)});kn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw vn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function Sn(e={}){let{status:t,body:n}=await Zs("cloud-device-start",{client:e});return t===429&&U("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&U("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)&&U("server",n.error??`could not start a pairing (${t})`),n}async function _n(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&U("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 Zs("cloud-device-poll",{secret:e});switch(o===404&&U("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&U("usage",s.error??"that is not a pairing secret"),o!==200&&U("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":U("forbidden",s.error??"that pairing was refused in the browser");case"used":U("conflict",s.error??"that pairing has already been completed");case"expired":U("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var Or=j(()=>{"use strict";ee();yn();pe();Rt()});function Ot(e){Tt=e}function ea(e){Qs=e}function $n(){return Tt}function w(e){return ta?`\x1B[2m${e}\x1B[0m`:e}function $(e){return ta?`\x1B[1m${e}\x1B[0m`:e}function h(e,t){if(Tt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
- `);return}t()}function d(e){!Tt&&!Qs&&process.stderr.write(`${e}
8
- `)}function Ge(e,t,n){process.stderr.write(`${t}
9
- `),Tt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
- `)}function te(e){process.stderr.write(`${e}
11
- `)}function H(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
12
- `)}function Me(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var Tt,Qs,ta,Q=j(()=>{"use strict";Tt=!1,Qs=!1;ta=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as oa,randomBytes as Nr}from"node:crypto";import{createServer as ku}from"node:http";import{createInterface as vu}from"node:readline/promises";import{spawn as $u}from"node:child_process";function xn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function na(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.":Bu(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
6
+ ${n}`).digest("base64url")}function mp(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&rp(n,r)}async function da(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=np(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(la)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&mp(o.proof,pp(e.token,t,n))}catch{return!1}}async function Hr(e,t,n,r=da){if(!await r(e).catch(()=>!1))throw new _n(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function kn(e,t=da){if(e!==void 0)return ia(e,t);let n="no-config";for(let r of ca()){let o=lp(r);if(!o)continue;let s=await ia(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 ia(e,t){let n=dp(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Hr(r,"/cli/whoami",{signal:AbortSignal.timeout(la)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof _n?{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 ua(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 ap()}async function Br(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 pa(e){return await Br(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await Br(e,"tools/list",{},3e3)).tools??[]}async function ma(e,t,n){return Br(e,"tools/call",{name:t,arguments:n},12e4)}var la,up,_n,vn=j(()=>{"use strict";la=1500,up="snoutdata-studio-proof-v1";_n=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 Kr(e){try{return new URL(e).host}catch{return e}}function xn(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function We(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function hp(e){return $n.has(Kr(e))}function yp(e,t,n){return t===void 0?e:{...e,[n?gp:fp]:t}}async function Ne(e,t,n={},r=fetch){let o=Kr(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:yp(s,n.key,u),body:n.body})}if($n.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:($n.add(o),i)}async function ga(e,t,n,r=fetch){if(!$n.has(Kr(e)))try{await Ne(e,We("/health","/health"),{key:n},r)}catch{}return`${e}${hp(e)?t.oldPath:t.path}`}var fp,gp,$n,Pn=j(()=>{"use strict";fp="Snout-Key",gp="apikey",$n=new Set});import{basename as wp}from"node:path";function An(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=wp(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 bp(e=An(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?Ue:Ct}}function qr(e={}){let t=e.kind??An(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?Ue:`${Ct} (or ${Ue})`;return`snoutdata ${ue} is installed and ${r}. Run \`${bp(t,n)}\`, or install again: ${o}`}function ha(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 ue,En,Ct,Ue,jt=j(()=>{"use strict";ue="0.18.1",En=`snoutdata-cli/${ue}`,Ct="curl -fsSL https://snoutdata.com/install.sh | sh",Ue="npm install -g snoutdata@latest"});async function ya(e,t){let n=await Ne(Ie,xn(e),{method:"POST",key:Ce,headers:{"Content-Type":"application/json","x-client-info":En},body:JSON.stringify(t)});On(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw Nn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function Rn(e={}){let{status:t,body:n}=await ya("cloud-device-start",{client:e});return t===429&&H("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&H("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)&&H("server",n.error??`could not start a pairing (${t})`),n}async function Tn(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&H("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 ya("cloud-device-poll",{secret:e});switch(o===404&&H("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&H("usage",s.error??"that is not a pairing secret"),o!==200&&H("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":H("forbidden",s.error??"that pairing was refused in the browser");case"used":H("conflict",s.error??"that pairing has already been completed");case"expired":H("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 Gr=j(()=>{"use strict";re();Pn();me();jt()});function Mt(e){Dt=e}function ba(e){wa=e}function In(){return Dt}function w(e){return _a?`\x1B[2m${e}\x1B[0m`:e}function x(e){return _a?`\x1B[1m${e}\x1B[0m`:e}function h(e,t){if(Dt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
+ `);return}t()}function d(e){!Dt&&!wa&&process.stderr.write(`${e}
8
+ `)}function Je(e,t,n){process.stderr.write(`${t}
9
+ `),Dt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
+ `)}function Q(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 Fe(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var Dt,wa,_a,te=j(()=>{"use strict";Dt=!1,wa=!1;_a=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as va,randomBytes as zr}from"node:crypto";import{createServer as kp}from"node:http";import{createInterface as Sp}from"node:readline/promises";import{spawn as vp}from"node:child_process";function Cn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ka(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.":Fp(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
13
13
  <meta name="viewport" content="width=device-width,initial-scale=1"></head>
14
14
  <body style="font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;background:#050403;color:#e9e4dc;display:grid;place-items:center;min-height:100vh;margin:0">
15
15
  <div style="text-align:center;max-width:460px;padding:40px 24px">
@@ -21,12 +21,12 @@ ${n}`).digest("base64url")}function fu(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 Cr(){let e=xn(Nr(32)),t=xn(oa("sha256").update(e).digest());return{verifier:e,challenge:t}}function xu(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 Eu(e){let{command:t,args:n,verbatim:r}=xu(process.platform,e),o=$u(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function jr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Ru(){return"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"}function Tu(e){let t=e??xn(Nr(32));return{raw:t,hashed:oa("sha256").update(t).digest("hex")}}function Ou(e){return`${Pu}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:jr(),redirect_uri:e.redirectUri,response_type:"code",scope:"openid email profile",code_challenge:e.challenge,code_challenge_method:"S256",nonce:e.hashedNonce,state:e.state,prompt:"select_account"}).toString()}`}function Nu(e,t){let n=new URL(e,"http://127.0.0.1");if(!n.searchParams.has("code")&&!n.searchParams.has("error"))return{kind:"ignore"};let r=n.searchParams.get("error_description")??n.searchParams.get("error");if(r)return{kind:"error",message:r};let o=n.searchParams.get("code");return o?t!==void 0&&n.searchParams.get("state")!==t?{kind:"error",message:"the browser came back with a state that did not match"}:{kind:"ok",code:o}:{kind:"error",message:"the browser came back without a code"}}async function Ir(e){return new Promise((t,n)=>{let r="",o=ku((a,l)=>{let i=Nu(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"?na("ok"):na("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."),Eu(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 sa(e,t){let n=await Re(Te,qe("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Oe,headers:{"Content-Type":"application/json"},body:JSON.stringify({auth_code:e,code_verifier:t})});if(!n.ok)throw new Error(`could not exchange the code: ${(await n.text()).slice(0,200)}`);return aa(await n.json())}async function Cu(e,t,n){let r=await fetch(Au,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:jr(),client_secret:Ru(),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 ju(e){if(!jr())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=Cr(),{raw:r,hashed:o}=Tu(),s=xn(Nr(16)),{code:a,redirectUri:l}=await Ir({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>Ou({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Cu(a,t,l),u=await Re(Te,qe("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Oe,headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:i,nonce:r})});if(!u.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await u.text()).slice(0,200)}`);return aa(await u.json())}function aa(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return Ae(n),n}function ra(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 Iu(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 Du(e,t,n){let r=Iu(t);return e===404||/no sso provider/i.test(r)?new v("not-found",`No single sign-on is set up for ${n}. Check the address, or ask whoever administers your team to connect your identity provider.`,{domain:n}):/saml.*(disabled|not enabled)/i.test(r)?new v("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new v(et(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Mu(e){let t=await Re(Te,qe("/sso/start","/sso"),{method:"POST",key:Oe,headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:e.domain,redirect_to:e.redirectTo,skip_http_redirect:!0,code_challenge:e.challenge,code_challenge_method:"s256"})}),n=await t.text();if(!t.ok)throw Du(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 Lu(e){if(e!==void 0){let r=ra(e);if(!r)throw new g(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!me()){let r=pt();throw new g(`login --sso needs --domain <your work email or company domain>${r?`, because ${ut(r)}`:""}`)}let t=vu({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new g("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=ra(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new g("no domain to sign in with: pass --domain <your work email or company domain>")}async function Uu(e){let t=await Lu(e.domain),{verifier:n,challenge:r}=Cr();d(`Signing in with the identity provider for ${$(t)}.`);let{code:o}=await Ir({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Mu({domain:t,redirectTo:s,challenge:r})});return sa(o,n)}async function En(e={}){return Dr(e.email,await Fu(e))}function Dr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return Ae(n),t.email&&!Mr(t.email,e)&&te(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Mr(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Fu(e){if(e.sso)return Uu({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return ju(e);let{verifier:n,challenge:r}=Cr(),{code:o}=await Ir({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Ys(Te,qe("/provider/start","/authorize"),Oe));return a.searchParams.set("provider",t),a.searchParams.set("redirect_to",s),a.searchParams.set("code_challenge",r),a.searchParams.set("code_challenge_method","s256"),a.searchParams.set("prompt","select_account"),a.toString()}});return sa(o,n)}function Bu(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Pu,Au,Pn=j(()=>{"use strict";ee();yn();he();Fe();pe();tt();Q();Pu="https://accounts.google.com/o/oauth2/v2/auth",Au="https://oauth2.googleapis.com/token"});import{createInterface as Hu}from"node:readline/promises";async function Lr(e){let t=Hu({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 qu(){let e=await pn();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(w("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(d(""),d(`SnoutData Studio is open here, signed in as ${$(t)}.`),d(w("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(w("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Lr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await Js(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return d(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return Ae(r),d(`Signed in with "${n.name}".`),r}async function Gu(){if(!await Lr("Sign in with a browser?"))return null;try{let e=await En({});return d(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return d(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function Ku(){if(!await Lr("Sign in by typing a code into a browser on another machine?"))return null;let e=await Sn({name:mn(),platform:process.platform});d(""),d(` Open ${$(e.verifyUrl)}`),d(` Type ${$(e.code)}`),d(""),d(w("The code is typed rather than in the link on purpose: it is what proves the person")),d(w("at the browser is the person at this terminal. Waiting\u2026"));let t=await _n(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return Ae(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function ia(){if(!me())return null;let e=await qu();if(e)return e;let t=await Gu();return t||Ku()}var ca=j(()=>{"use strict";Fe();fn();Or();tt();Pn();Q()});var Ne={};vs(Ne,{ACCOUNTS_URL:()=>Te,APP_KEY:()=>Oe,ApiError:()=>Se,NotSignedIn:()=>Ke,call:()=>S,connection:()=>we,errorSentence:()=>ua,exportStatus:()=>ft,isSettled:()=>da,listProjects:()=>_e,listTeams:()=>Rn,localStack:()=>Le,noticeFrom:()=>kn,outdated:()=>vn,requestExport:()=>Nt,unreadableAnswer:()=>pa,waitForReady:()=>jt,waitForSettled:()=>Ct,whoami:()=>nt,withKeyNames:()=>An});function Ju(){let e=pt();return e?`not signed in, and ${ut(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function zu(){let e=on();if(e||(e=await ia()),!e)throw new Ke;if(!Cs(e)||!e.refreshToken)return e.accessToken;let t=await Re(Te,qe("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Oe,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Ke;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return Ae(r),r.accessToken}async function S(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Le(r);if(u)throw new v("usage",`${r} is a local project (${u.folder}), and this command is about SnoutData Cloud. Locally: db url, db psql, db push, gen types, keys, status, start, stop, functions, secrets.`,{ref:r,local:u.folder})}let o=await zu(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await Re(Te,hn(e,s),{method:n,key:Oe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":wn},body:n==="GET"?void 0:JSON.stringify(t)});kn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new Se(a.status,pa(a.status,a.statusText,l))}if(!a.ok){let u=ua(i)??`${a.status} ${a.statusText}`;if(a.status===401)throw new Ke(u);if(a.status===410)throw vn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new Se(a.status,u,c,i)}return i}function kn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||la||(la=!0,te(`${t} ${Tr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function vn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new v("outdated",`${t.replace(/\s+$/,"")} ${Tr({minimum:n})}`,{status:410,installed:ie,minimum:n??null})}async function nt(){return S("cloud-whoami")}async function _e(){return S("cloud-project-list")}async function we(e){let t=Le(e);return t?Ls(t):An(await S("cloud-project-connection",{ref:e}))}function An(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Le(e){return Us(e,ct(process.cwd())?.local??null)}async function Rn(){return S("cloud-team-list")}async function Nt(e){return S("cloud-project-export",{ref:e})}async function ft(e){return S("cloud-project-export",{ref:e},"GET")}async function Ct(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await _e(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),da(t,a))return s;if(Date.now()>=r)throw new Se(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function da(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function jt(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await _e(),o=r.find(s=>s.ref===e);if(!o)throw new Se(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new Se(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}function ua(e){if(typeof e!="object"||!e)return null;let t=e;return typeof t.error=="string"&&t.error?t.error:typeof t.message=="string"&&t.message?typeof t.hint=="string"&&t.hint?`${t.message} ${t.hint}`:t.message:null}function pa(e,t,n){return/^\s*</.test(n)||!n.trim()?`SnoutData Cloud answered ${e}${t?` ${t}`:""} with no message. Try again in a minute.`:n.slice(0,300)}var Te,Oe,Se,Ke,la,ee=j(()=>{"use strict";Fe();Pt();pe();tt();ca();Q();Rt();yn();Te=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Oe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",Se=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Ke=class extends Error{constructor(t){super(t??Ju())}};la=!1});function Do(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Ec(e,t,n){let r="";for(;r.length<n;)r+=Do(e,t);return r}var Io,xc,Pc=j(()=>{"use strict";Io="0123456789abcdefghjkmnpqrstvwxyz",xc="abcdefghjkmnpqrstvwxyz"});function Tc(e){return Do(e,Ac)+Ec(e,Io,Rc-1)}function $g(e){return vg.test(e)}function ce(e){if(!$g(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function B(e,t=xg){return ce(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Ac,Rc,vg,xg,xe=j(()=>{"use strict";Pc();Ac=xc,Rc=13,vg=new RegExp(`^[${Ac}][${Io}]{${Rc-1}}$`);xg="db.snoutdata.com"});var Oc=j(()=>{"use strict";xe()});function Cc(e,t){let n=Nc(e,7),r=Nc(t,24);return Math.max(2,Math.ceil(n*24/r))}function Nc(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Mo=j(()=>{"use strict"});function Lo(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Uo=j(()=>{"use strict"});var jc=j(()=>{"use strict"});function Dc(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 Mc(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 Eg,Ic,Fo=j(()=>{"use strict";xe();Eg=20,Ic=`select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming' and (reply_time is null or reply_time > now() - interval '${Eg} seconds')), '')`});function Uc(e,t){let n=new Map;for(let[o,s]of t)n.set(s,o);let r=new Map;for(let o of e.split(`
27
- `)){let[s,a]=o.trim().split(/\s+/),l=s?n.get(s):void 0;l&&(a==="0"||a==="1"?r.set(l,!0):a==="2"&&r.set(l,!1))}return r}var Lc,Bo=j(()=>{"use strict";Lc='for h in "$@"; do ( pg_isready -q -h "$h" -p 5432 -U snoutpod_admin -d postgres -t 3; echo "$h $?" ) & done; wait'});function Ag(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function Bc(e){let[t,n]=(e.trim().split(`
28
- `)[0]??"").split("|");return t==="t"&&n!==void 0&&Ag(n)?n:null}var Pg,$_,Fc,Ho=j(()=>{"use strict";Pg="/var/lib/postgresql/data",$_=`set -u
29
- D=${Pg}
24
+ </body></html>`}function Wr(){let e=Cn(zr(32)),t=Cn(va("sha256").update(e).digest());return{verifier:e,challenge:t}}function $p(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 xp(e){let{command:t,args:n,verbatim:r}=$p(process.platform,e),o=vp(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function Jr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Ap(){return"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"}function Rp(e){let t=e??Cn(zr(32));return{raw:t,hashed:va("sha256").update(t).digest("hex")}}function Tp(e){return`${Pp}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:Jr(),redirect_uri:e.redirectUri,response_type:"code",scope:"openid email profile",code_challenge:e.challenge,code_challenge_method:"S256",nonce:e.hashedNonce,state:e.state,prompt:"select_account"}).toString()}`}function Op(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=kp((a,l)=>{let i=Op(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"?ka("ok"):ka("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."),xp(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 $a(e,t){let n=await Ne(Ie,We("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Ce,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 xa(await n.json())}async function Np(e,t,n){let r=await fetch(Ep,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Jr(),client_secret:Ap(),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 Ip(e){if(!Jr())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=Wr(),{raw:r,hashed:o}=Rp(),s=Cn(zr(16)),{code:a,redirectUri:l}=await Vr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>Tp({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Np(a,t,l),u=await Ne(Ie,We("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Ce,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 xa(await u.json())}function xa(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return Oe(n),n}function Sa(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 Cp(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 jp(e,t,n){let r=Cp(t);return e===404||/no sso provider/i.test(r)?new P("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 P("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new P(Le(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Dp(e){let t=await Ne(Ie,We("/sso/start","/sso"),{method:"POST",key:Ce,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 jp(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new P("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 Mp(e){if(e!==void 0){let r=Sa(e);if(!r)throw new m(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!ye()){let r=yt();throw new m(`login --sso needs --domain <your work email or company domain>${r?`, because ${ht(r)}`:""}`)}let t=Sp({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new m("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=Sa(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 m("no domain to sign in with: pass --domain <your work email or company domain>")}async function Lp(e){let t=await Mp(e.domain),{verifier:n,challenge:r}=Wr();d(`Signing in with the identity provider for ${x(t)}.`);let{code:o}=await Vr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Dp({domain:t,redirectTo:s,challenge:r})});return $a(o,n)}async function jn(e={}){return Yr(e.email,await Up(e))}function Yr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return Oe(n),t.email&&!Xr(t.email,e)&&Q(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Xr(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Up(e){if(e.sso)return Lp({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return Ip(e);let{verifier:n,challenge:r}=Wr(),{code:o}=await Vr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await ga(Ie,We("/provider/start","/authorize"),Ce));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 $a(o,n)}function Fp(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Pp,Ep,Dn=j(()=>{"use strict";re();Pn();be();qe();me();st();te();Pp="https://accounts.google.com/o/oauth2/v2/auth",Ep="https://oauth2.googleapis.com/token"});import{createInterface as Bp}from"node:readline/promises";async function Zr(e){let t=Bp({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 Hp(){let e=await kn();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 ${x(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 Zr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await ua(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 Oe(r),d(`Signed in with "${n.name}".`),r}async function Kp(){if(!await Zr("Sign in with a browser?"))return null;try{let e=await jn({});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 qp(){if(!await Zr("Sign in by typing a code into a browser on another machine?"))return null;let e=await Rn({name:Sn(),platform:process.platform});d(""),d(` Open ${x(e.verifyUrl)}`),d(` Type ${x(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 Tn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return Oe(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Pa(){if(!ye())return null;let e=await Hp();if(e)return e;let t=await Kp();return t||qp()}var Ea=j(()=>{"use strict";qe();vn();Gr();st();Dn();te()});var Ee={};Hs(Ee,{ACCOUNTS_URL:()=>Ie,APP_KEY:()=>Ce,ApiError:()=>ge,NotSignedIn:()=>Ve,call:()=>_,connection:()=>Se,errorSentence:()=>Ta,exportStatus:()=>bt,isSettled:()=>Ra,listProjects:()=>$e,listTeams:()=>Ln,localStack:()=>Be,noticeFrom:()=>On,outdated:()=>Nn,requestExport:()=>Lt,unreadableAnswer:()=>Un,waitForReady:()=>Ft,waitForSettled:()=>Ut,whoami:()=>at,withKeyNames:()=>Mn});function Gp(){let e=yt();return e?`not signed in, and ${ht(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 zp(){let e=fn();if(e||(e=await Pa()),!e)throw new Ve;if(!Zs(e)||!e.refreshToken)return e.accessToken;let t=await Ne(Ie,We("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Ce,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Ve;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return Oe(r),r.accessToken}async function _(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Be(r);if(u)throw new P("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 zp(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await Ne(Ie,xn(e,s),{method:n,key:Ce,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":En},body:n==="GET"?void 0:JSON.stringify(t)});On(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(a.status,Un(a.status,a.statusText,l))}if(!a.ok){let u=Ta(i)??`${a.status} ${a.statusText}`;if(a.status===401)throw new Ve(u);if(a.status===410)throw Nn(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 On(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Aa||(Aa=!0,Q(`${t} ${qr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function Nn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new P("outdated",`${t.replace(/\s+$/,"")} ${qr({minimum:n})}`,{status:410,installed:ue,minimum:n??null})}async function at(){return _("cloud-whoami")}async function $e(){return _("cloud-project-list")}async function Se(e){let t=Be(e);return t?ra(t):Mn(await _("cloud-project-connection",{ref:e}))}function Mn(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Be(e){return oa(e,mt(process.cwd())?.local??null)}async function Ln(){return _("cloud-team-list")}async function Lt(e){return _("cloud-project-export",{ref:e})}async function bt(e){return _("cloud-project-export",{ref:e},"GET")}async function Ut(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await $e(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ra(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 Ra(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Ft(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await $e(),o=r.find(s=>s.ref===e);if(!o)throw new 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))}}function Ta(e){if(typeof e!="object"||!e)return null;let t=e;return typeof t.error=="string"&&t.error?t.error:typeof t.message=="string"&&t.message?typeof t.hint=="string"&&t.hint?`${t.message} ${t.hint}`:t.message:null}function Un(e,t,n){return/^\s*</.test(n)||!n.trim()?`SnoutData Cloud answered ${e}${t?` ${t}`:""} with no message. Try again in a minute.`:n.slice(0,300)}var Ie,Ce,ge,Ve,Aa,re=j(()=>{"use strict";qe();It();me();st();Ea();te();jt();Pn();Ie=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Ce=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}},Ve=class extends Error{constructor(t){super(t??Gp())}};Aa=!1});function Zo(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function wl(e,t,n){let r="";for(;r.length<n;)r+=Zo(e,t);return r}var Xo,yl,bl=j(()=>{"use strict";Xo="0123456789abcdefghjkmnpqrstvwxyz",yl="abcdefghjkmnpqrstvwxyz"});function Sl(e){return Zo(e,_l)+wl(e,Xo,kl-1)}function Uh(e){return Lh.test(e)}function le(e){if(!Uh(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function G(e,t=Fh){return le(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var _l,kl,Lh,Fh,Ae=j(()=>{"use strict";bl();_l=yl,kl=13,Lh=new RegExp(`^[${_l}][${Xo}]{${kl-1}}$`);Fh="db.snoutdata.com"});var vl=j(()=>{"use strict";Ae()});function xl(e,t){let n=$l(e,7),r=$l(t,24);return Math.max(2,Math.ceil(n*24/r))}function $l(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Qo=j(()=>{"use strict"});function es(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var ts=j(()=>{"use strict"});var Pl=j(()=>{"use strict"});function Rl(e){let n=(e.trim().split(`
26
+ `)[0]??"").split("|");if(n.length!==7||n[0]!=="t"&&n[0]!=="f"||n[5]!=="t"&&n[5]!=="f")return null;let r=Number(n[1]);return!Number.isFinite(r)||r<0?null:{inRecovery:n[0]==="t",checksumFailures:r,syncNames:n[2],streaming:n[3]===""?[]:n[3].split(","),lastArchivedWal:/^[0-9A-F]{24}$/.test(n[4])?n[4]:"",keeperReady:n[5]==="t",certSha256:/^[0-9a-f]{64}$/.test(n[6])?n[6]:""}}function Ol(e){if(e!==""&&!Hh.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 Bh,et,El,Al,Tl,Hh,ns=j(()=>{"use strict";Ae();Bh=20,et="snoutpod_keeper",El=/^SCRAM-SHA-256\$[0-9]{4,6}:[A-Za-z0-9+/]+=*\$[A-Za-z0-9+/]+=*:[A-Za-z0-9+/]+=*$/,Al=`select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming' and (reply_time is null or reply_time > now() - interval '${Bh} seconds')), ''), coalesce((select last_archived_wal from pg_stat_archiver), ''), exists (select 1 from pg_roles where rolname = '${et}' and rolreplication) and exists (select 1 from pg_hba_file_rules where database = '{replication}' and '${et}' = any (user_name)) and current_setting('ssl') = 'on', case when current_setting('ssl') = 'on' then encode(sha256(pg_read_binary_file(current_setting('ssl_cert_file'))), 'hex') else '' end`;Tl=/^-----BEGIN CERTIFICATE-----\n[A-Za-z0-9+/=\n]{64,12000}-----END CERTIFICATE-----\n$/,Hh=/^(?:"[a-z0-9-]+"|(?:ANY|FIRST) [1-9] \("[a-z0-9-]+"(?:, "[a-z0-9-]+")+\))$/});function Il(e,t){let n=new Map;for(let[o,s]of t)n.set(s,o);let r=new Map;for(let o of e.split(`
27
+ `)){let[s,a]=o.trim().split(/\s+/),l=s?n.get(s):void 0;l&&(a==="0"||a==="1"?r.set(l,!0):a==="2"&&r.set(l,!1))}return r}var Nl,rs=j(()=>{"use strict";Nl='for h in "$@"; do ( pg_isready -q -h "$h" -p 5432 -U snoutpod_admin -d postgres -t 3; echo "$h $?" ) & done; wait'});function qh(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function jl(e){let[t,n]=(e.trim().split(`
28
+ `)[0]??"").split("|");return t==="t"&&n!==void 0&&qh(n)?n:null}var Kh,VS,Cl,os=j(()=>{"use strict";Kh="/var/lib/postgresql/data",VS=`set -u
29
+ D=${Kh}
30
30
  W="$D/pg_wal"
31
31
  fail() { echo "salvage-error $*"; exit 3; }
32
32
  [ -d "$W" ] || fail "the volume has no pg_wal"
@@ -65,22 +65,22 @@ for s in $segs; do
65
65
  echo "salvage-pushed $s"
66
66
  done
67
67
  echo "salvage-done"
68
- `;Fc="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var P_,Hc=j(()=>{"use strict";xe();P_=7*24});var R_,qc=j(()=>{"use strict";R_=7*24*60*60*1e3});var Rg,Gc=j(()=>{"use strict";qc();Rg=60*60*1e3});var Kc=j(()=>{"use strict"});var qo,D_,Jc=j(()=>{"use strict";qo="snoutdata_realtime",D_=`do $snoutpod$
68
+ `;Cl="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var ZS,Dl=j(()=>{"use strict";Ae();ZS=7*24});var ev,Ml=j(()=>{"use strict";ev=7*24*60*60*1e3});var Gh,Ll=j(()=>{"use strict";Ml();Gh=60*60*1e3});var Ul=j(()=>{"use strict"});var ss,iv,Fl=j(()=>{"use strict";ss="snoutdata_realtime",iv=`do $snoutpod$
69
69
  begin
70
- if exists (select 1 from pg_publication where pubname = '${qo}') then
70
+ if exists (select 1 from pg_publication where pubname = '${ss}') then
71
71
  return;
72
72
  end if;
73
- create publication ${qo};
74
- execute format('alter publication ${qo} owner to %I',
73
+ create publication ${ss};
74
+ execute format('alter publication ${ss} owner to %I',
75
75
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
76
76
  end
77
77
  $snoutpod$;
78
- `});var L_,U_,zc=j(()=>{"use strict";L_=2*1024*1024,U_=64*1024});var Wc=j(()=>{"use strict"});function Go(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function nr(e){let t=["network","create"],n=e.allow,r=e.deny;if(n!=null&&r!=null)throw new Error("a network is policed by an allow-list or by a deny-list, not by both");if(r!=null)return Og(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 Og(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(Go("the subnet",n,Vc),Go("the sink",r,Tg),!Xc(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Xc(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",`${Go("a denied prefix",s,Vc)},${r}`);return o.push(e.name),o}function Yc(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Xc(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 Qc(e){return e?.internal===!0}var Zc,Vc,Tg,Ko=j(()=>{"use strict";Zc=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],Vc=/^((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])$/,Tg=/^((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 Ng,rr=j(()=>{"use strict";Ng=7*24*60*60});function Dg(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 jg,Ig,nk,el=j(()=>{"use strict";xe();zc();Wc();Ko();rr();jg="10.93.0.0/24",Ig="10.93.0.1",nk=[...Zc,"100.64.0.0/10",...Dg(jg,Ig)]});var tl=j(()=>{"use strict"});import{createHash as Mg}from"node:crypto";function or(e){return`${B(e).pod}-lepis-tls`}function Ug(e){return Mg("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function sl(e,t){let n={...e},r=n[Jo],o=n[zo];delete n[Jo],delete n[zo];for(let[s,a,l]of[[Jo,r,t],[zo,o,Ug(t)]])if(a!==void 0){if(!Lg.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var zt,Jo,zo,nl,rl,Wo,ol,Lg,Vo=j(()=>{"use strict";xe();tl();zt="lepis",Jo="SNOUTPOD_ADMIN_PASSWORD_ENV",zo="SNOUTPOD_ADMIN_TOKEN_ENV",nl=7433,rl=1e3;Wo="/tls",ol=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
79
- `),Lg=/^[A-Z][A-Z0-9_]{0,63}$/});var fk,gk,al=j(()=>{"use strict";xe();rr();fk=6*60*60,gk=5*60});var il=j(()=>{"use strict"});var cl=j(()=>{"use strict"});var bk,ll=j(()=>{"use strict";bk=2*1024**3});var dl=j(()=>{"use strict";cl();ll()});function pl(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"||!Bg.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Fg.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 ml(e,t){return`${e}.nspname !~ '^pg_'
80
- and ${e}.nspname <> all (array[${ul.map(n=>`'${n}'`).join(", ")}]::text[])
78
+ `});var lv,dv,Bl=j(()=>{"use strict";lv=2*1024*1024,dv=64*1024});var Hl=j(()=>{"use strict"});function as(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function hr(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 Wh(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 Wh(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(as("the subnet",n,Kl),as("the sink",r,zh),!Gl(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Gl(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",`${as("a denied prefix",s,Kl)},${r}`);return o.push(e.name),o}function ql(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Gl(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(ql(e)&s)>>>0===(ql(n??"")&s)>>>0}function Wl(e){return e?.internal===!0}var zl,Kl,zh,is=j(()=>{"use strict";zl=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],Kl=/^((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])$/,zh=/^((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 Jh,yr=j(()=>{"use strict";Jh=7*24*60*60});function Zh(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 Yh,Xh,Ev,Jl=j(()=>{"use strict";Ae();Bl();Hl();is();yr();Yh="10.93.0.0/24",Xh="10.93.0.1",Ev=[...zl,"100.64.0.0/10",...Zh(Yh,Xh)]});var Vl=j(()=>{"use strict"});import{createHash as Qh}from"node:crypto";function wr(e){return`${G(e).pod}-lepis-tls`}function ty(e){return Qh("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Ql(e,t){let n={...e},r=n[cs],o=n[ls];delete n[cs],delete n[ls];for(let[s,a,l]of[[cs,r,t],[ls,o,ty(t)]])if(a!==void 0){if(!ey.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var en,cs,ls,Yl,Xl,ds,Zl,ey,us=j(()=>{"use strict";Ae();Vl();en="lepis",cs="SNOUTPOD_ADMIN_PASSWORD_ENV",ls="SNOUTPOD_ADMIN_TOKEN_ENV",Yl=7433,Xl=1e3;ds="/tls",Zl=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
79
+ `),ey=/^[A-Z][A-Z0-9_]{0,63}$/});var Uv,Fv,ed=j(()=>{"use strict";Ae();yr();Uv=6*60*60,Fv=5*60});var td=j(()=>{"use strict"});var nd=j(()=>{"use strict"});var qv,rd=j(()=>{"use strict";qv=2*1024**3});var od=j(()=>{"use strict";nd();rd()});function ad(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"||!ry.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!ny.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 id(e,t){return`${e}.nspname !~ '^pg_'
80
+ and ${e}.nspname <> all (array[${sd.map(n=>`'${n}'`).join(", ")}]::text[])
81
81
  and not ${t}.rolsuper
82
- and ${t}.rolname !~ '${Hg.source}'`}function gl(e){let t=[];for(let n of e.split(`
83
- `)){let r=/^(created-group|created|applied|dropped-group|dropped|refused|ignored) (\S+)$/.exec(n.trim());r&&t.push({action:r[1],role:r[2]})}return t}function hl(e,t){return ce(e),JSON.stringify(pl(t).map(n=>({role:n.role,level:n.level})))}var Fg,_t,Bg,V,ul,Hg,ar,qg,sr,Gg,fl,Yo=j(()=>{"use strict";xe();Fg=["full","read"],_t="snout_oauth",Bg=/^oauth_[0-9a-f]{32}$/,V="snout_oauth_read",ul=["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"],Hg=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;ar="SNOUTPOD_DB_ACCESS",qg=/^[a-z_][a-z0-9_]*$/;for(let e of ul)if(!qg.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);sr="snoutpod_oauth_read_new_schema",Gg=`do $home$
82
+ and ${t}.rolname !~ '${oy.source}'`}function ld(e){let t=[];for(let n of e.split(`
83
+ `)){let r=/^(created-group|created|applied|dropped-group|dropped|refused|ignored) (\S+)$/.exec(n.trim());r&&t.push({action:r[1],role:r[2]})}return t}function dd(e,t){return le(e),JSON.stringify(ad(t).map(n=>({role:n.role,level:n.level})))}var ny,Et,ry,X,sd,oy,_r,sy,br,ay,cd,ps=j(()=>{"use strict";Ae();ny=["full","read"],Et="snout_oauth",ry=/^oauth_[0-9a-f]{32}$/,X="snout_oauth_read",sd=["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"],oy=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;_r="SNOUTPOD_DB_ACCESS",sy=/^[a-z_][a-z0-9_]*$/;for(let e of sd)if(!sy.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);br="snoutpod_oauth_read_new_schema",ay=`do $home$
84
84
  declare
85
85
  -- snout_ext in the own layout, extensions in a database not yet renamed (docs/cloud/OWN-LAYOUT.md).
86
86
  home text := case
@@ -89,7 +89,7 @@ declare
89
89
  end;
90
90
  begin
91
91
  execute pg_catalog.format('create schema if not exists %I', home);
92
- execute pg_catalog.replace($fn$create or replace function @home@.${sr}() returns event_trigger
92
+ execute pg_catalog.replace($fn$create or replace function @home@.${br}() returns event_trigger
93
93
  language plpgsql
94
94
  security invoker
95
95
  set search_path = pg_catalog, pg_temp
@@ -98,7 +98,7 @@ declare
98
98
  created record;
99
99
  owner_role text := pg_catalog.current_database() || '_owner';
100
100
  begin
101
- if not exists (select from pg_catalog.pg_roles where rolname = '${V}') then
101
+ if not exists (select from pg_catalog.pg_roles where rolname = '${X}') then
102
102
  return;
103
103
  end if;
104
104
  for created in
@@ -109,29 +109,29 @@ begin
109
109
  where c.command_tag = 'CREATE SCHEMA'
110
110
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
111
111
  and not c.in_extension
112
- and ${ml("n","r")}
112
+ and ${id("n","r")}
113
113
  loop
114
114
  begin
115
- execute pg_catalog.format('grant usage on schema %I to ${V}', created.nspname);
116
- execute pg_catalog.format('grant select on all tables in schema %I to ${V}', created.nspname);
117
- execute pg_catalog.format('grant select on all sequences in schema %I to ${V}', created.nspname);
118
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${V}', owner_role, created.nspname);
119
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${V}', owner_role, created.nspname);
115
+ execute pg_catalog.format('grant usage on schema %I to ${X}', created.nspname);
116
+ execute pg_catalog.format('grant select on all tables in schema %I to ${X}', created.nspname);
117
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${X}', created.nspname);
118
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${X}', owner_role, created.nspname);
119
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${X}', owner_role, created.nspname);
120
120
  exception when others then
121
121
  raise warning 'snoutpod: read access reaches schema % at the next apply, not now: %', created.nspname, sqlerrm;
122
122
  end;
123
123
  end loop;
124
124
  end
125
125
  $snoutpod$$fn$, '@home@', pg_catalog.quote_ident(home));
126
- if not exists (select from pg_catalog.pg_event_trigger where evtname = '${sr}') then
127
- execute pg_catalog.format('create event trigger ${sr} on ddl_command_end
126
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${br}') then
127
+ execute pg_catalog.format('create event trigger ${br} on ddl_command_end
128
128
  when tag in (''CREATE SCHEMA'')
129
- execute function %I.${sr}()', home);
129
+ execute function %I.${br}()', home);
130
130
  end if;
131
131
  end
132
132
  $home$;
133
- `,fl=`\\getenv db_access ${ar}
134
- ${Gg}create function pg_temp.snoutpod_member(member_name text, group_name text)
133
+ `,cd=`\\getenv db_access ${_r}
134
+ ${ay}create function pg_temp.snoutpod_member(member_name text, group_name text)
135
135
  returns boolean
136
136
  language sql
137
137
  stable
@@ -152,7 +152,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
152
152
  set search_path = pg_catalog, pg_temp
153
153
  as $snoutpod$
154
154
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
155
- select ${ml("n","r")}
155
+ select ${id("n","r")}
156
156
  from pg_namespace n
157
157
  join pg_roles r on r.oid = n.nspowner
158
158
  where n.oid = schema_oid
@@ -187,9 +187,9 @@ begin
187
187
  end if;
188
188
  any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
189
189
 
190
- if not exists (select from pg_roles where rolname = '${_t}') then
191
- create role ${_t} nologin;
192
- change := 'created-group'; role_name := '${_t}'; return next;
190
+ if not exists (select from pg_roles where rolname = '${Et}') then
191
+ create role ${Et} nologin;
192
+ change := 'created-group'; role_name := '${Et}'; return next;
193
193
  end if;
194
194
 
195
195
  -- Taken away: every member of the group the list does not name.
@@ -198,7 +198,7 @@ begin
198
198
  from pg_auth_members am
199
199
  join pg_roles g on g.oid = am.roleid
200
200
  join pg_roles m on m.oid = am.member
201
- where g.rolname = '${_t}'
201
+ where g.rolname = '${Et}'
202
202
  order by 1
203
203
  loop
204
204
  continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
@@ -217,21 +217,21 @@ begin
217
217
 
218
218
  -- What a read person may read: the customer's schemas, and nothing of ours (dbAccess.ts).
219
219
  if any_read then
220
- if not exists (select from pg_roles where rolname = '${V}') then
221
- create role ${V} nologin;
222
- change := 'created-group'; role_name := '${V}'; return next;
220
+ if not exists (select from pg_roles where rolname = '${X}') then
221
+ create role ${X} nologin;
222
+ change := 'created-group'; role_name := '${X}'; return next;
223
223
  end if;
224
224
  -- Stated whole, as a person's role is, and a member of nothing: whatever it held would reach
225
225
  -- every read person.
226
- alter role ${V} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
226
+ alter role ${X} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
227
227
  for target in
228
228
  select g.rolname as of_role, pg_get_userbyid(am.grantor) as grantor
229
229
  from pg_auth_members am
230
230
  join pg_roles g on g.oid = am.roleid
231
231
  join pg_roles m on m.oid = am.member
232
- where m.rolname = '${V}'
232
+ where m.rolname = '${X}'
233
233
  loop
234
- execute pg_catalog.format('revoke %I from ${V} granted by %I', target.of_role, target.grantor);
234
+ execute pg_catalog.format('revoke %I from ${X} granted by %I', target.of_role, target.grantor);
235
235
  end loop;
236
236
  for target in
237
237
  select n.nspname, pg_temp.snoutpod_customer_schema(n.oid) as customer
@@ -240,16 +240,16 @@ begin
240
240
  order by 1
241
241
  loop
242
242
  if target.customer then
243
- execute pg_catalog.format('grant usage on schema %I to ${V}', target.nspname);
244
- execute pg_catalog.format('grant select on all tables in schema %I to ${V}', target.nspname);
245
- execute pg_catalog.format('grant select on all sequences in schema %I to ${V}', target.nspname);
246
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${V}', owner_role, target.nspname);
247
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${V}', owner_role, target.nspname);
243
+ execute pg_catalog.format('grant usage on schema %I to ${X}', target.nspname);
244
+ execute pg_catalog.format('grant select on all tables in schema %I to ${X}', target.nspname);
245
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${X}', target.nspname);
246
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${X}', owner_role, target.nspname);
247
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${X}', owner_role, target.nspname);
248
248
  else
249
249
  -- Ours. Nothing was ever granted here unless the list or an owner changed since.
250
- execute pg_catalog.format('revoke all on schema %I from ${V}', target.nspname);
251
- execute pg_catalog.format('revoke all on all tables in schema %I from ${V}', target.nspname);
252
- execute pg_catalog.format('revoke all on all sequences in schema %I from ${V}', target.nspname);
250
+ execute pg_catalog.format('revoke all on schema %I from ${X}', target.nspname);
251
+ execute pg_catalog.format('revoke all on all tables in schema %I from ${X}', target.nspname);
252
+ execute pg_catalog.format('revoke all on all sequences in schema %I from ${X}', target.nspname);
253
253
  end if;
254
254
  end loop;
255
255
  for target in
@@ -258,10 +258,10 @@ begin
258
258
  from pg_default_acl d
259
259
  join pg_namespace n on n.oid = d.defaclnamespace
260
260
  where not pg_temp.snoutpod_customer_schema(n.oid)
261
- and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${V}'::regrole)
261
+ and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${X}'::regrole)
262
262
  loop
263
263
  continue when target.kind is null;
264
- execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${V}', target.for_role, target.nspname, target.kind);
264
+ execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${X}', target.for_role, target.nspname, target.kind);
265
265
  end loop;
266
266
  end if;
267
267
 
@@ -270,7 +270,7 @@ begin
270
270
  wanted_role := entry->>'role';
271
271
  wanted_level := entry->>'level';
272
272
  if exists (select from pg_roles where rolname = wanted_role) then
273
- if not pg_temp.snoutpod_member(wanted_role, '${_t}') then
273
+ if not pg_temp.snoutpod_member(wanted_role, '${Et}') then
274
274
  -- The name is right and the role is not ours: made by somebody else, who holds
275
275
  -- ADMIN on it. Never adopted (see dbAccess.ts).
276
276
  change := 'refused'; role_name := wanted_role; return next;
@@ -278,7 +278,7 @@ begin
278
278
  end if;
279
279
  change := 'applied';
280
280
  else
281
- execute pg_catalog.format('create role %I login in role ${_t}', wanted_role);
281
+ execute pg_catalog.format('create role %I login in role ${Et}', wanted_role);
282
282
  change := 'created';
283
283
  end if;
284
284
  -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
@@ -291,8 +291,8 @@ begin
291
291
  execute pg_catalog.format('revoke pg_read_all_data from %I', wanted_role);
292
292
  end if;
293
293
  if wanted_level = 'full' then
294
- if pg_temp.snoutpod_member(wanted_role, '${V}') then
295
- execute pg_catalog.format('revoke ${V} from %I', wanted_role);
294
+ if pg_temp.snoutpod_member(wanted_role, '${X}') then
295
+ execute pg_catalog.format('revoke ${X} from %I', wanted_role);
296
296
  end if;
297
297
  execute pg_catalog.format('alter role %I nobypassrls', wanted_role);
298
298
  if not pg_temp.snoutpod_member(wanted_role, owner_role) then
@@ -307,8 +307,8 @@ begin
307
307
  execute pg_catalog.format('revoke %I from %I', owner_role, wanted_role);
308
308
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = wanted_role;
309
309
  end if;
310
- if not pg_temp.snoutpod_member(wanted_role, '${V}') then
311
- execute pg_catalog.format('grant ${V} to %I with inherit true, set false', wanted_role);
310
+ if not pg_temp.snoutpod_member(wanted_role, '${X}') then
311
+ execute pg_catalog.format('grant ${X} to %I with inherit true, set false', wanted_role);
312
312
  end if;
313
313
  execute pg_catalog.format('alter role %I bypassrls', wanted_role);
314
314
  execute pg_catalog.format('alter role %I in database %I reset role', wanted_role, db);
@@ -319,30 +319,30 @@ begin
319
319
 
320
320
  -- Nobody reads any more: the group goes, and with it every grant and default it held, so a
321
321
  -- project that stops using the level carries nothing of it.
322
- if not any_read and exists (select from pg_roles where rolname = '${V}') then
322
+ if not any_read and exists (select from pg_roles where rolname = '${X}') then
323
323
  begin
324
324
  for target in
325
325
  select m.rolname as member_role, pg_get_userbyid(am.grantor) as grantor
326
326
  from pg_auth_members am
327
327
  join pg_roles g on g.oid = am.roleid
328
328
  join pg_roles m on m.oid = am.member
329
- where g.rolname = '${V}'
329
+ where g.rolname = '${X}'
330
330
  loop
331
- execute pg_catalog.format('revoke ${V} from %I granted by %I', target.member_role, target.grantor);
331
+ execute pg_catalog.format('revoke ${X} from %I granted by %I', target.member_role, target.grantor);
332
332
  end loop;
333
- drop owned by ${V};
334
- drop role ${V};
335
- change := 'dropped-group'; role_name := '${V}'; return next;
333
+ drop owned by ${X};
334
+ drop role ${X};
335
+ change := 'dropped-group'; role_name := '${X}'; return next;
336
336
  exception when others then
337
337
  -- Holding something in another database, which this never grants. Kept, and said.
338
- raise warning 'snoutpod: could not drop ${V}: %', sqlerrm;
338
+ raise warning 'snoutpod: could not drop ${X}: %', sqlerrm;
339
339
  end;
340
340
  end if;
341
341
  end
342
342
  $snoutpod$;
343
343
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
344
- `});var yl,Sv,_v,kv,vv,$v,wl=j(()=>{"use strict";Oc();Mo();Uo();jc();Fo();Bo();Ho();Hc();Gc();Kc();Jc();el();Vo();al();il();dl();rr();Yo();yl=120,Sv=2*6e4,_v=15*60*1e3,kv=60*60*1e3,vv=5*60*1e3,$v=10*60*1e3});import{execFile as Kg}from"node:child_process";function bl(){return(e,t,n={})=>new Promise((r,o)=>{let s=Kg(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,l,i)=>{if(a&&typeof a.code=="string"){o(a);return}let u=a&&typeof a.code=="number"?a.code:0;r({code:u,stdout:String(l),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function z(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
345
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Wt=j(()=>{"use strict"});import{readdir as Jg,readFile as Rv}from"node:fs/promises";async function Sl(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??zg)(i)!=="empty":!1}async function zg(e){try{return(await Jg(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var _l=j(()=>{"use strict";xe();Wt()});import{createHash as Wg,createHmac as kl,pbkdf2Sync as Vg}from"node:crypto";function vl(e,t){let n=Yg.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=Vg(t.normalize("NFKC"),Buffer.from(o,"base64"),l,32,"sha256"),u=kl("sha256",i).update("Client Key").digest(),c=Wg("sha256").update(u).digest(),p=kl("sha256",i).update("Server Key").digest();return c.equals(Buffer.from(s,"base64"))&&p.equals(Buffer.from(a,"base64"))}var Yg,$l=j(()=>{"use strict";Yg=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function xl(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 Al(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Rl(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=${xl("the access key",e.key,Zg)}`),n.push("--env",El),e.token&&n.push("--env",Pl)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${xl("the endpoint",e.endpoint,Xg)}`),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 Tl(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{[El]:e.secret,...e.token?{[Pl]:e.token}:{}}}var El,Pl,Xg,Zg,Ol=j(()=>{"use strict";El="SNOUTPOD_S3_SECRET",Pl="SNOUTPOD_S3_TOKEN",Xg=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Zg=/^[A-Za-z0-9/+=_.-]{1,256}$/});var Nl,Cl,jl=j(()=>{"use strict";Nl=["snout_gate","authenticator"],Cl=`do $snoutpod$
344
+ `});var ud,G$,z$,W$,J$,V$,pd=j(()=>{"use strict";vl();Qo();ts();Pl();ns();rs();os();Dl();Ll();Ul();Fl();Jl();us();ed();td();od();yr();ps();ud=120,G$=2*6e4,z$=15*60*1e3,W$=60*60*1e3,J$=5*60*1e3,V$=10*60*1e3});import{execFile as iy}from"node:child_process";function md(){return(e,t,n={})=>new Promise((r,o)=>{let s=iy(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 W(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
345
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var tn=j(()=>{"use strict"});import{readdir as cy,readFile as ex}from"node:fs/promises";async function fd(e,t,n={}){let r=n.binary??"podman",o=G(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??ly)(i)!=="empty":!1}async function ly(e){try{return(await cy(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var gd=j(()=>{"use strict";Ae();tn()});import{createHash as dy,createHmac as hd,pbkdf2Sync as uy}from"node:crypto";function yd(e,t){let n=py.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=uy(t.normalize("NFKC"),Buffer.from(o,"base64"),l,32,"sha256"),u=hd("sha256",i).update("Client Key").digest(),c=dy("sha256").update(u).digest(),p=hd("sha256",i).update("Server Key").digest();return c.equals(Buffer.from(s,"base64"))&&p.equals(Buffer.from(a,"base64"))}var py,wd=j(()=>{"use strict";py=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function bd(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 Sd(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function vd(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=${bd("the access key",e.key,fy)}`),n.push("--env",_d),e.token&&n.push("--env",kd)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${bd("the endpoint",e.endpoint,my)}`),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 $d(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{[_d]:e.secret,...e.token?{[kd]:e.token}:{}}}var _d,kd,my,fy,xd=j(()=>{"use strict";_d="SNOUTPOD_S3_SECRET",kd="SNOUTPOD_S3_TOKEN",my=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,fy=/^[A-Za-z0-9/+=_.-]{1,256}$/});function Ad(e){if(!ms.includes(e))throw new Error(`not a role kept out of the query statistics: ${e}`);return`alter role ${e} set pg_stat_statements.track = 'none';`}var Pd,Ed,ms,Rd=j(()=>{"use strict";Pd=["snout_gate","authenticator"],Ed=`do $snoutpod$
346
346
  declare
347
347
  wanted text[];
348
348
  begin
@@ -360,8 +360,8 @@ begin
360
360
  end loop;
361
361
  end
362
362
  $snoutpod$;
363
- `});function Dl(e){let t=[];for(let n of e.split(`
364
- `)){let r=n.split(Zo);if(r.length!==4||!Xo.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 Xo,Zo,Il,Ml=j(()=>{"use strict";Xo=["snouttime"];for(let e of Xo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Zo="",Il=`
363
+ `,ms=["snout_psyche_admin"]});function Od(e){let t=[];for(let n of e.split(`
364
+ `)){let r=n.split(gs);if(r.length!==4||!fs.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 fs,gs,Td,Nd=j(()=>{"use strict";fs=["snouttime"];for(let e of fs)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);gs="",Td=`
365
365
  SET search_path = pg_catalog, pg_temp;
366
366
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
367
367
  DO $snoutpod$
@@ -375,7 +375,7 @@ BEGIN
375
375
  SELECT e.extname, e.extversion
376
376
  FROM pg_extension e
377
377
  JOIN pg_available_extensions a ON a.name = e.extname
378
- WHERE e.extname = ANY (ARRAY[${Xo.map(e=>`'${e}'`).join(", ")}])
378
+ WHERE e.extname = ANY (ARRAY[${fs.map(e=>`'${e}'`).join(", ")}])
379
379
  AND a.default_version IS DISTINCT FROM e.extversion
380
380
  LOOP
381
381
  BEGIN
@@ -406,106 +406,132 @@ BEGIN
406
406
  END
407
407
  $snoutpod$;
408
408
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
409
- `});function Ul(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 Ll,Fv,Bv,Fl=j(()=>{"use strict";Wt();Qo();Ll="SNOUTPOD_OAUTH_ISSUER",Fv=256*1024,Bv=5*6e4});import{createHash as ns,randomBytes as eh}from"node:crypto";import{access as th,constants as nh}from"node:fs/promises";import{delimiter as rh,join as oh}from"node:path";function sh(e,t){return ns("sha256").update(rs(e)).update(e.smtpSecretEnv&&t?ns("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function rs(e){if(Array.isArray(e))return`[${e.map(rs).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)}:${rs(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function ss(e){if(!ih.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Yt(e){if(!ch.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function lh(e,t,n){return`postgres://${Yt(t)}:${n}@127.0.0.1:5432/${ce(e)}`}function ql(e,t,n,r){let o=sl(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]=lh(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function Gl(e,t){return`${B(e).pod}-${ss(t)}`}function ir(e){return`snoutpod-host-${ss(e)}`}function dh(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return ns("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 uh(e,t){let n=[],r=os(t);for(let o of e??[])if(ss(o.name),cr(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){cr(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===zt){let s=o.hostPort+rl;cr(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${nl}`)}}return n}function os(e){let t=e===void 0?[]:typeof e=="string"?[e]:[...e];return["127.0.0.1",...new Set(t.filter(n=>n!=="127.0.0.1"))]}function cr(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Jl(e){let t=[];for(let n of Object.keys(e)){if(!ph.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function mh(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Yl.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(cr(e.hostPort,`${e.name}: hostPort`),["--label",`${as}=${e.hostPort}:${t}`])}function fh(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||!Yl.test(r)?null:{port:n,path:r}}async function hh(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>th(o,nh.X_OK));for(let o of n.split(rh))if(o)try{let s=oh(o,e);return await r(s),s}catch{}return null}function yh(){return["\\set ON_ERROR_STOP 0","set search_path = pg_catalog, pg_temp;","select 'create extension if not exists pg_prewarm schema public' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","select pg_catalog.format($snoutpod$with candidate as ("," select c.oid, pg_catalog.pg_relation_size(c.oid) as bytes"," from pg_class c join pg_namespace n on n.oid = c.relnamespace"," where c.relkind in ('r', 'i', 'm')"," and c.relpersistence = 'p'"," and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')","), budgeted as ("," select oid, bytes,"," sum(bytes) over (order by bytes, oid rows between unbounded preceding and current row) as running"," from candidate",")","select coalesce(sum(%I.pg_prewarm(oid::pg_catalog.regclass, 'buffer'::pg_catalog.text, 'main'::pg_catalog.text, null::pg_catalog.int8, null::pg_catalog.int8)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4$snoutpod$, n.nspname)","from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_prewarm' \\gexec",""].join(`
410
- `)}function bh(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Sh(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 is(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function _h(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 kh(e,t,n,r){return{image:await vh(e,t,r),data:await $h(e,t,n)}}async function vh(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 Sh(r,n)}async function $h(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?is(s.stdout):null}catch{return null}}function xh(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:zl;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,zl)}`),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 Eh(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??gh;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await hh(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Zl(e){let t=e.binary??"podman",n=e.exec,r=Eh(e),o=e.probe??Xl;async function s(m){try{await n(t,["exec",...ge,"--interactive",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"--quiet","--file","-"],{timeoutMs:wh,stdin:yh()})}catch{}}async function a(){let m=await z(n,t,["ps","--all","--filter",`label=${Ee}`,"--format","json"]),y=m.trim()?JSON.parse(m):[],b=new Map,N=new Map,R=new Map;for(let T of y){let ne=T.Labels?.[Ee],oe=T.Labels?.[at];if(!ne||!oe)continue;let ue=b.get(ne)??[];ue.push(oe),b.set(ne,ue),Vl(T.State)==="stopped"&&N.set(ne,[...N.get(ne)??[],oe]);let D=Number(T.Labels?.[ts]??0),ae=Number.isInteger(D)?D:0;R.set(ne,Math.max(R.get(ne)??ae,ae))}let G=y.filter(T=>T.Labels?.[Ee]&&!T.Labels?.[at]),q=G.map(T=>Vl(T.State)),K=await Ah(G,Ph,(T,ne)=>q[ne]==="running"?Promise.resolve(!0):e.hasData(T.Labels?.[Ee]??""));return G.map((T,ne)=>{let oe=T.Labels?.[Ee]??"",ue=Number(T.Labels?.[es]);return{ref:oe,status:q[ne]??"unknown",generation:Number.isInteger(ue)?ue:null,hasData:K[ne]??!1,services:b.get(oe)??[],servicesStopped:N.get(oe)??[],...R.has(oe)?{servicesGeneration:R.get(oe)}:{}}})}async function l(){let m=await z(n,t,["ps","--all","--filter",`label=${Ee}`,"--format","json"]);return(m.trim()?JSON.parse(m):[]).filter(b=>b.Labels?.[Ee]&&!b.Labels?.[at]).length}async function i(m){let y=await(e.majors??((b,N)=>kh(n,t,b,N)))(m.ref,m.image);return _h(m.ref,y)}async function u(m){let y=B(m.ref),b=await i(m);if(b)throw new Error(b);let N=e.credentials,R=await Al(e.repository,N?()=>N.forProject(m.ref,m.bucket):null),G={SNOUTPOD_OWNER_PASSWORD:m.ownerPassword,POSTGRES_PASSWORD:m.adminPassword,...Tl(R)},q=[...await r(),"run","--detach","--name",y.pod,"--label",`${Ee}=${m.ref}`,"--label",`${es}=${m.generation}`,"--userns=auto","--memory",`${m.memoryMb}m`,...Oh(m.cpus),"--restart","no",...e.network?["--network",e.network]:[],...m.port?os(e.podBind).flatMap(K=>["--publish",`${K}:${m.port}:5432`]):[],...uh(m.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${m.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${m.bucket}`,"--env",`SNOUTPOD_S3_REGION=${m.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...Th(m.oauth),...xh(m.tuning).flatMap(K=>["--env",K]),"--env",`SNOUTPOD_RETENTION_FULL=${Cc(m.backup?.retentionDays,m.backup?.fullIntervalHours)}`,...Rl(R),m.image,"snoutpod-postgres"];await z(n,t,q,{env:G})}async function c(m){let y=await z(n,t,["ps","--all","--filter",`label=${Ee}=${ce(m)}`,"--filter",`label=${at}`,"--format","json"]),b=y.trim()?JSON.parse(y):[],N=[];for(let R of b){let G=R.Labels?.[at];if(!G)continue;let q=Number(R.Labels?.[ts]??0);N.push({name:G,container:Gl(m,G),spec:R.Labels?.[Bl]??"",pod:R.Labels?.[Hl]??"",running:R.State==="running",generation:Number.isInteger(q)?q:0})}return N}async function p(m){let y=await n(t,["container","inspect","--format","{{.Id}}",B(m).pod]);return y.code===0?y.stdout.trim():""}async function f(m,y){let b=[];for(let K of y){let T=K.dbUriRole??null;K.dbUriEnv&&T&&!b.includes(T)&&b.push(Yt(T))}let N=new Map;if(b.length===0)return N;let R={},G=[],q=[];return b.some(K=>Nl.includes(K))&&q.push(Cl),b.forEach((K,T)=>{let ne=eh(24).toString("base64url"),oe=`SNOUTPOD_SERVICE_PASSWORD_${T}`;N.set(K,ne),R[oe]=ne,G.push("--env",oe),q.push(`\\getenv service_password_${T} ${oe}`),q.push(`\\set service_role_${T} ${K}`),q.push(`alter role :"service_role_${T}" with password :'service_password_${T}';`)}),q.push(""),await z(n,t,["exec",...ge,"--interactive",...G,B(m.ref).pod,"psql","--username","snoutpod_admin","--dbname",B(m.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:R,stdin:q.join(`
411
- `)}),N}async function E(m){let y=or(m.ref);await z(n,t,["volume","create","--ignore",y]),await z(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${Wo}`,"--entrypoint","bash",m.image,"-c",ol])}async function k(m){let y=await r();if((await n(t,[...y,"image","exists",m])).code===0)return;let b=await n(t,[...y,"pull","--quiet",m]);if(b.code===0)return;let N=bh(m);if(!N||!e.registryLogin)throw new Error(`${m} could not be pulled: ${(b.stderr||b.stdout).trim()}`);await e.registryLogin(N),await z(n,t,[...y,"pull","--quiet",m])}async function X(m){let y=m.services??[],b=await c(m.ref),N;y.some(D=>D.smtpSecretEnv)&&(N=await e.smtpSecret?.().catch(()=>{}));let R=y.length>0?await p(m.ref):"",G=new Map(b.map(D=>[D.name,D])),q=new Map(y.map(D=>[D.name,sh(D,N)])),K=new Set(y.filter(D=>{let ae=G.get(D.name);return!ae||!ae.running||ae.spec!==q.get(D.name)||R===""||ae.pod!==R}).map(D=>D.name)),T=m.servicesGeneration??0;if(K.size===0&&y.length>0&&!b.some(D=>D.generation>=T))for(let D of y)K.add(D.name);let ne=D=>D.dbUriEnv&&D.dbUriRole?D.dbUriRole:null;for(let D=!0;D;){D=!1;let ae=new Set(y.filter(Qe=>K.has(Qe.name)).map(ne));for(let Qe of y){let ks=ne(Qe);!K.has(Qe.name)&&ks!==null&&ae.has(ks)&&(K.add(Qe.name),D=!0)}}for(let D of new Set(y.filter(ae=>K.has(ae.name)).map(ae=>ae.image)))await k(D);for(let D of b)(K.has(D.name)||!q.has(D.name))&&await z(n,t,["rm","--force","--ignore",D.container]);let oe=y.filter(D=>K.has(D.name)),ue=await f(m,oe);oe.some(D=>D.name===zt)&&await E(m);for(let D of oe){let ae=Gl(m.ref,D.name),Qe=[...await r(),"run","--detach","--name",ae,"--label",`${Ee}=${m.ref}`,"--label",`${es}=${m.generation}`,"--label",`${at}=${D.name}`,"--label",`${ts}=${m.servicesGeneration??0}`,"--label",`${Bl}=${q.get(D.name)??""}`,"--label",`${Hl}=${R}`,"--network",`container:${B(m.ref).pod}`,"--userns",`container:${B(m.ref).pod}`,"--memory",`${D.memoryMb}m`,"--restart","no",...mh(D),...D.name===zt?["--volume",`${or(m.ref)}:${Wo}:ro`]:[],...Jl(ql(m,D,ue,N)),D.image];await z(n,t,Qe,{env:ql(m,D,ue,N)})}}async function L(m,y){if(y%6!==0)return!1;let b=await n(t,["container","inspect","--format","{{.State.Status}}",B(m).pod]);return b.code===0&&/^(exited|stopped|dead)$/.test(b.stdout.trim())}async function de(m){let y=await n(t,["exec",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",Fc]);return y.code===0?Bc(y.stdout):null}async function I(m){let y=await n(t,["exec",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let b=y.stdout.trim();return b==="t"?!0:b==="f"?!1:null}async function Z(m,y){let b=R=>n(t,["exec",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",R]),N=Date.now()+y;do{if((await b("checkpoint")).code!==0)return!1;let G=await b("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(G.code===0&&G.stdout.trim()==="t")return!0;await new Promise(q=>setTimeout(q,1e3))}while(Date.now()<N);return!1}async function P(m){let y=await n(t,["exec",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",Ic]);return y.code===0?Dc(y.stdout):null}async function le(m,y){let b=new Map;for(let R of y)ce(R),b.set(R,B(R).pod);if(b.size===0)return new Map;let N=await n(t,["exec",B(m).pod,"timeout",String(10),"sh","-c",Lc,"sh",...b.values()]);return N.code===0?Uc(N.stdout,b):null}async function J(m,y){return(await n(t,["exec",B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...Mc(y).flatMap(N=>["-c",N])])).code===0}return{list:a,inRecovery:I,replayLsn:de,settleTimeline:Z,podHealth:P,setSyncNames:J,nodesAnswer:le,preflight:i,create:u,createServices:X,async start(m){let y=(await c(m).catch(()=>[])).filter(b=>b.running).map(b=>b.container);y.length>0&&await z(n,t,["stop","--time","5",...y]),await z(n,t,["start",B(m).pod])},async startServices(m){let y=(await c(m)).map(b=>b.container);y.length>0&&await z(n,t,["start",...y])},async applyHostServices(m){let y=[],b=await z(n,t,["ps","--all","--filter",`label=${Vt}`,"--format","json"]),N=b.trim()?JSON.parse(b):[],R=new Map;for(let q of N){let K=q.Labels?.[Vt];K&&R.set(K,{spec:q.Labels?.[Kl]??"",state:q.State??""})}for(let q of m)try{await G(q)}catch(K){y.push({name:q.name,action:"failed",reason:K instanceof Error?K.message:String(K)})}async function G(q){let K=e.hostServiceLocal?.(q,m),T=K?{...q,...K.env&&Object.keys(K.env).length>0?{env:{...q.env,...K.env}}:{},...K.volumes?{volumes:K.volumes}:{},...K.network?{network:K.network}:{}}:q,ne=ir(T.name),oe=dh(T),ue=R.get(T.name);if(R.delete(T.name),ue&&ue.spec===oe){ue.state!=="running"&&(await z(n,t,["start",ne]),y.push({name:T.name,action:"started",reason:ue.state}));return}let D=ue?"the spec changed":"it was not here";await k(T.image),ue&&await z(n,t,["rm","--force","--ignore",ne]),await z(n,t,[...await r(),"run","--detach","--name",ne,"--label",`${Vt}=${T.name}`,"--label",`${Kl}=${oe}`,...T.healthPath?["--label",`${as}=${T.hostPort}:${T.healthPath}`]:[],"--memory",`${T.memoryMb}m`,"--restart","no",...T.network?["--network",T.network]:e.network?["--network",e.network]:[],...(T.volumes??[]).flatMap(ae=>["--volume",ae]),...os(e.podBind).flatMap(ae=>["--publish",`${ae}:${T.hostPort}:${T.port}`]),...T.adminPort&&T.adminHostPort?["--publish",`127.0.0.1:${T.adminHostPort}:${T.adminPort}`]:[],...Jl(T.env),T.image,...T.command??[]],{env:{...T.env}}),y.push({name:T.name,action:ue?"recreated":"created",reason:D})}for(let q of R.keys())await z(n,t,["rm","--force","--ignore",ir(q)]),y.push({name:q,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(m){let y=await z(n,t,["ps","--all","--filter",`label=${Vt}`,"--format","json"]),b=(y.trim()?JSON.parse(y):[]).map(R=>R.Labels?.[Vt]??"").filter(R=>R!=="");if(b.length===0)return[];let N=Date.now()+m;for(;;){let R=[];for(let G of b)R.push(await Wl(n,t,G,ir(G),o));if(R.every(G=>G.ok)||Date.now()>=N)return R;await new Promise(G=>setTimeout(G,500))}},async serviceHealth(m,y){let b=await c(m);if(b.length===0)return[];let N=Date.now()+y;for(;;){let R=await Promise.all(b.map(G=>Wl(n,t,G.name,G.container,o)));if(R.every(G=>G.ok)||Date.now()>=N)return R;await new Promise(G=>setTimeout(G,500))}},async stop(m,y){for(let b of await c(m))await z(n,t,["stop","--time",String(ah),b.container]);await z(n,t,["stop","--time",String(y),B(m).pod])},async remove(m){for(let y of await c(m))await z(n,t,["rm","--force","--ignore",y.container]);await z(n,t,["rm","--force","--ignore","--volumes",B(m).pod])},async removeData(m){await z(n,t,["volume","rm","--force",B(m).pod]);let y=or(m);(await n(t,["volume","exists",y])).code===0&&await z(n,t,["volume","rm","--force",y])},async applyOwnerPassword(m){let y=await n(t,["exec",...ge,B(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${ce(m.ref)}_owner'`]);if(y.code===0&&vl(y.stdout,m.ownerPassword))return;let b=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${m.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
412
- `);await z(n,t,["exec",...ge,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",B(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:m.ownerPassword},stdin:b})},async applyRolePassword(m,y,b){let N=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Yt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
413
- `);await z(n,t,["exec",...ge,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",B(m).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:b},stdin:N})},async updateExtensions(m){let y=B(m),b=await n(t,["exec",...ge,y.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${y.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${y.database}' desc, datname limit 1`]),N=b.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(b.stdout.trim())?b.stdout.trim():y.database,R=await z(n,t,["exec",...ge,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",N,"--quiet","-tA","-F",Zo,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Il});return Dl(R)},async applyDbAccess(m,y){let b=B(m),N=await z(n,t,["exec",...ge,"--interactive","--env",ar,b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[ar]:hl(m,y)},stdin:fl,timeoutMs:6e4});return gl(N)},async runProjectSql(m,y){let b=B(m);await z(n,t,["exec",...ge,"--interactive",b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(m,y,b){let N=ir(m),R=Yt(y),G=Yt(b),q=T=>["exec",N,"psql","--username",R,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",T];(await z(n,t,q(`select 1 from pg_database where datname = '${G}'`))).trim()!=="1"&&await z(n,t,q(`create database "${G}"`))},async renameProject(m,y){y!==m.ref&&await z(n,t,["exec",...ge,"--interactive",B(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Rh(y,m.ref)})},async waitReady(m,y){let b=B(m).pod,N=Date.now()+y,R=0;for(;;){let G=await n(t,["exec",...ge,b,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(G.code===0&&G.stdout.trim()==="t")return s(m),!0;if(Date.now()>=N||await L(m,++R))return!1;await new Promise(q=>setTimeout(q,500))}},async waitStandby(m,y){let b=Date.now()+y,N=0;for(;;){if(await I(m)===!0)return!0;if(Date.now()>=b||await L(m,++N))return!1;await new Promise(R=>setTimeout(R,500))}},async capacity(){let[m,y]=await Promise.all([l(),e.capacity()]);return{pods:m,...y}},async databaseBytes(m){let y=await n(t,["exec",...ge,B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tAc","select pg_database_size(current_database())"]);return y.code===0?Lo(y.stdout):null},async storageBytes(m){let y=await n(t,["exec",...ge,B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-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?Lo(y.stdout):null},async setReadOnly(m,y){let b=B(m);await z(n,t,["exec",...ge,b.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${b.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(m){let y=await n(t,["exec",...ge,B(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(y.code!==0)return null;let[b="",N=""]=y.stdout.trim().split(`
414
- `)[0]?.split("|")??[],R=Number(N);return{lastArchivedAt:b.trim()||null,failures:Number.isInteger(R)&&R>=0?R:0}}}}async function Wl(e,t,n,r,o=Xl){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let p=JSON.parse(s.stdout);a=p[0]?.State,l=p[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=fh(l?.[as]);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 Ah(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 Vl(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Rh(e,t){return[`\\set from_db ${e}`,`\\set to_db ${t}`,`\\set from_role ${e}_owner`,`\\set to_role ${t}_owner`,'alter database :"from_db" rename to :"to_db";','alter role :"from_role" rename to :"to_role";',"alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;",""].join(`
415
- `)}function Th(e){if(!e?.issuer)return[];try{return["--env",`${Ll}=${Ul(e.issuer)}`]}catch{return[]}}function Oh(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Ee,es,at,ts,Bl,Hl,ah,ih,ch,Vt,Kl,ph,Yl,as,Xl,gh,ge,wh,zl,Ph,Qo=j(()=>{"use strict";xe();Mo();Fo();Bo();$l();Wt();Ol();Uo();jl();Ml();Yo();Fl();Ho();Vo();Ee="snoutpod.ref",es="snoutpod.generation",at="snoutpod.service",ts="snoutpod.services-generation",Bl="snoutpod.service-spec",Hl="snoutpod.service-pod";ah=10,ih=/^[a-z][a-z0-9-]{0,30}$/;ch=/^[a-z][a-z0-9_]{0,62}$/;Vt="snoutpod.host-service",Kl="snoutpod.host-service-spec";ph=/^[A-Za-z_][A-Za-z0-9_]*$/;Yl=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,as="snoutpod.health";Xl=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()})},gh="crun";ge=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],wh=3e4;zl=200;Ph=8});var md={};vs(md,{DEFAULT_LOCAL_MEMORY_MB:()=>rd,DEFAULT_LOCAL_PORT:()=>Dh,ENGINE:()=>Y,ENGINE_NAME:()=>cs,IMAGE:()=>us,NETWORK:()=>Ye,NEW_PROJECT_MAJOR:()=>ds,NO_BLACKHOLE_ROUTES:()=>id,PUBLIC_IMAGE:()=>Fh,PUBLISHED_MAJORS:()=>kt,capacity:()=>ad,chooseLocalImage:()=>Qt,dropIfUnused:()=>ud,ensureImage:()=>Bh,ensureNetwork:()=>Jh,exec:()=>se,hasData:()=>pr,localImageFor:()=>sd,localPsql:()=>Vh,localRuntime:()=>Kh,localSpec:()=>dd,localStatus:()=>ps,localUri:()=>ld,networkPolicyInPlace:()=>cd,newLocalPassword:()=>Lh,newLocalRef:()=>Mh,podLog:()=>pd,podName:()=>vt,podmanReady:()=>qh,removeLocal:()=>Zh,staleImage:()=>ls,startLocal:()=>Wh,stopLocal:()=>Xh,volumePgVersion:()=>lr});import{randomBytes as td}from"node:crypto";import{existsSync as Ql}from"node:fs";import{platform as ur}from"node:os";import{delimiter as Nh,join as Ch}from"node:path";function jh(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=ur()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(Nh))if(t&&e.some(n=>Ql(Ch(t,n))))return"podman";return Ql("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Qt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Zt(ds);let n=Number(is(e));if(!kt.includes(n)){let r=e.trim()||"(empty)",o=kt.map(s=>Zt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${kt.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Zt(n)}function Zt(e){return{name:`${Ih}:${e}`,published:`${nd}:${e}`,major:e}}function Mh(){return Tc(e=>td(e))}function Lh(){return td(18).toString("base64url")}async function Bh(e,t=se,n=Qt(null)){let r=n.published;if(!r)return null;if((await t(Y,["image","exists",n.name])).code===0)return await Hh(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(Y,["pull",r],{timeoutMs:od}).catch(a=>({code:1,stdout:"",stderr:a instanceof Error?a.message:String(a)}));if(o.code!==0)return`could not fetch the database image ${r}: ${o.stderr.trim()||o.stdout.trim()||"the pull failed"}`;let s=await t(Y,["tag",r,n.name]);return s.code!==0?`fetched ${r} but could not tag it as ${n.name}: ${s.stderr.trim()}`:(e?.("Got it."),null)}async function Hh(e,t,n,r){let o=await t(Y,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
416
- `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(Y,["pull",r],{timeoutMs:od}).catch(f=>({code:1,stdout:"",stderr:f instanceof Error?f.message:String(f)})).finally(()=>clearTimeout(l));if(i.code!==0){let f=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
417
- `).at(-1);e?.(`Could not check for a newer database image (${f}); using the one on this machine.`);return}let u=await t(Y,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let p=await t(Y,["tag",r,n]);if(p.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${p.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function qh(e=Uh,t=us){if(cs!=="podman")return`SNOUTPOD_ENGINE=${cs}: 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 se(Y,["--version"])}catch{return`${Y} is not installed. macOS: brew install podman, then podman machine init && podman machine set --rootful=false && podman machine start. Windows: winget install RedHat.Podman, then the same three machine commands. Linux: your distribution's podman package.`}if(n.code!==0)return`${Y} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await se(Y,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await se(Y,["machine","inspect","--format","{{.Rootful}}"]);return o.code===0&&o.stdout.trim().startsWith("true")?"the podman machine is ROOTFUL, and the hosts are not: podman machine stop && podman machine set --rootful=false && podman machine start":null}function Gh(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 se(Y,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
418
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function pr(e){let t=ur()==="darwin"||ur()==="win32";return Sl(se,e,{binary:Y,look:t?Gh:void 0})}async function lr(e,t=se,n=ur()){let r=B(e).pod;if((await t(Y,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of kt){let l=Zt(a).name;if((await t(Y,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${kt.map(a=>Zt(a).published).join(" or ")}).`);o=await t(Y,["run","--rm","--pull=never","--network=none","--userns=host","--user=0","--entrypoint=cat","--volume",`${r}:/data:ro`,s,"/data/PG_VERSION"])}else{let s=await t(Y,["volume","inspect","--format","{{.Mountpoint}}",r]),a=s.code===0?s.stdout.trim().replace(/\/+$/,""):"";if(!a)throw new Error(`could not find where ${r} is: ${s.stderr.trim()||"no mountpoint"}`);o=await t(Y,["unshare","cat",`${a}/PG_VERSION`])}if(o.code===0)return o.stdout;if(/no such file/i.test(o.stderr))return null;throw new Error(`could not read the Postgres version in ${r}: ${o.stderr.trim()||o.stdout.trim()||`exit ${o.code}`}`)}function sd(e,t=lr){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Qt(null));ce(e);let n=t===lr?dr.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Qt(o)}catch(s){throw new Error(`${vt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===lr&&(dr.set(e,r),r.catch(()=>dr.delete(e))),r}async function ad(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Kh(e){return Zl({exec:se,binary:Y,network:Ye,hasData:pr,capacity:ad,repository:e})}function vt(e){return B(e).pod}async function Jh(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await zh()||await se(Y,nr({name:Ye}));return}let t=nr({name:Ye,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await cd())return;await e?.(),await se(Y,["network","rm","--force",Ye]).catch(()=>{});let o=await se(Y,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${id}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ye} with egress blocked${n?`: ${n}`:""}`)}async function zh(){return(await se(Y,["network","inspect",Ye,"--format","json"])).code===0}async function cd(){let e=await se(Y,["network","inspect",Ye,"--format","json"]);if(e.code!==0)return!1;try{return Qc(JSON.parse(e.stdout)?.[0])}catch{return!1}}function ld(e){let t=B(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function dd(e,t={}){return ce(e.ref),{ref:e.ref,generation:1,image:t.image??us,memoryMb:t.memoryMb??rd,cpus:null,bucket:"",region:"us-east-1",ownerPassword:e.ownerPassword,adminPassword:e.adminPassword,port:e.port,tuning:t.tuning??null,backup:null,services:t.services??null}}function Xt(e,t){e.onProgress?.(t)}async function Wh(e,t,n={}){let r=n.image??(await sd(t.ref,n.pgVersion)).name,o=dd(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??pr,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??ls)(t.ref,r))?(Xt(n,"Starting the database on the newer image. Its data stays as it is."),await e.remove(t.ref),await e.create(o),await(n.dropImage??ud)(i)):l.status==="stopped"?(Xt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Xt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??ls)(t.ref,r)&&Xt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Xt(n,await a(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(o)),!await e.waitReady(t.ref,s))throw new Error(await Yh(t.ref,s,n.podLog??pd));return await e.applyOwnerPassword(o),await e.createServices(o),ps(e,t,n)}function ed(e){return e.trim().replace(/^sha256:/,"")}async function ls(e,t){let[n,r]=await Promise.all([se(Y,["container","inspect","--format","{{.Image}}",vt(e)]).catch(()=>null),se(Y,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=ed(n.stdout),s=ed(r.stdout);return o&&s&&o!==s?o:null}async function ud(e){let t=await se(Y,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await se(Y,["rmi",e]).catch(()=>{})}async function Vh(e,t){let n=B(e.ref);return se(Y,["exec","--interactive","-e","PGPASSWORD",vt(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 pd(e){let t=await se(Y,["logs","--tail","20",vt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Yh(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${vt(e)} never became ready in ${t}ms
409
+ `});function Cd(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 Id,ux,px,jd=j(()=>{"use strict";tn();hs();Id="SNOUTPOD_OAUTH_ISSUER",ux=256*1024,px=5*6e4});function Dd(e,t){let n=null,r=!1;for(let o of e.split(`
410
+ `)){let s=o.trim();s.startsWith("swap-error")?n=s.slice(10).trim()||"the swap failed":s==="swap-done"&&(r=!0)}return n||(r&&t===0?null:`the swap ended without finishing (exit ${t})`)}var gy,ys,Md,Ld=j(()=>{"use strict";gy='snoutpod-pgbackrest archive-get %f "%p"',ys=String.raw`set -u
411
+ A=$1
412
+ B=$2
413
+ fail() { echo "swap-error $*"; exit 3; }
414
+ [ -d "$A" ] || fail "the home's volume has no data directory"
415
+ [ -f "$B/PG_VERSION" ] || fail "the replica's volume holds no cluster"
416
+ [ -f "$B/standby.signal" ] || fail "the replica's data is not a standby's"
417
+ [ ! -e "$A.swap" ] || fail "an earlier swap was left half done: $A.swap exists"
418
+ mv "$A" "$A.swap" || fail "the home's data could not be moved aside"
419
+ if ! mv "$B" "$A"; then
420
+ mv "$A.swap" "$A"
421
+ fail "the replica's data could not be moved into the home's volume"
422
+ fi
423
+ mv "$A.swap" "$B" || fail "the home's old data is at $A.swap, not in the replica's volume"
424
+ C="$A/postgresql.auto.conf"
425
+ kept=""
426
+ if [ -f "$C" ]; then
427
+ kept=$(grep -v -E '^[[:space:]]*(primary_conninfo|primary_slot_name|restore_command|synchronous_standby_names)[[:space:]]*=' "$C")
428
+ fi
429
+ { [ -n "$kept" ] && printf '%s\n' "$kept"; printf '%s\n' "restore_command = '`+gy.replace(/"/g,'\\"')+String.raw`'"; } > "$C" || fail "postgresql.auto.conf could not be written"
430
+ echo "swap-done"
431
+ `;Md=["select pg_promote(true, 120)","alter system reset synchronous_standby_names","alter system reset primary_conninfo","alter system reset primary_slot_name","alter system reset restore_command","select pg_reload_conf()"]});import{createHash as ks,randomBytes as yy}from"node:crypto";import{access as wy,constants as by}from"node:fs/promises";import{delimiter as _y,join as ky}from"node:path";function Sy(e,t){return ks("sha256").update(Ss(e)).update(e.smtpSecretEnv&&t?ks("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function Ss(e){if(Array.isArray(e))return`[${e.map(Ss).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)}:${Ss(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function $s(e){if(!$y.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function rn(e){if(!xy.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Py(e,t,n){return`postgres://${rn(t)}:${n}@127.0.0.1:5432/${le(e)}`}function Bd(e,t,n,r){let o=Ql(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]=Py(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function Hd(e,t){return`${G(e).pod}-${$s(t)}`}function kr(e){return`snoutpod-host-${$s(e)}`}function Ey(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return ks("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 Ay(e,t){let n=[],r=vs(t);for(let o of e??[])if($s(o.name),Sr(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Sr(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===en){let s=o.hostPort+Xl;Sr(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Yl}`)}}return n}function vs(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 Sr(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function qd(e){let t=[];for(let n of Object.keys(e)){if(!Ry.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Ty(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Jd.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Sr(e.hostPort,`${e.name}: hostPort`),["--label",`${xs}=${e.hostPort}:${t}`])}function Oy(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||!Jd.test(r)?null:{port:n,path:r}}async function Iy(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>wy(o,by.X_OK));for(let o of n.split(_y))if(o)try{let s=ky(o,e);return await r(s),s}catch{}return null}function Cy(){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(`
432
+ `)}function Dy(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function My(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 Ps(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Ly(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 Uy(e,t,n,r){return{image:await Fy(e,t,r),data:await By(e,t,n)}}async function Fy(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 My(r,n)}async function By(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",G(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?Ps(s.stdout):null}catch{return null}}function Hy(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:Gd;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Gd)}`),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 Ky(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Ny;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Iy(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Yd(e){let t=e.binary??"podman",n=e.exec,r=Ky(e),o=e.probe??Vd;async function s(g){try{await n(t,["exec",...he,"--interactive",G(g).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",g,"--quiet","--file","-"],{timeoutMs:jy,stdin:Cy()})}catch{}}async function a(){let g=await W(n,t,["ps","--all","--filter",`label=${Re}`,"--format","json"]),y=g.trim()?JSON.parse(g):[],b=new Map,T=new Map,A=new Map;for(let E of y){let ne=E.Labels?.[Re],oe=E.Labels?.[dt];if(!ne||!oe)continue;let de=b.get(ne)??[];de.push(oe),b.set(ne,de),Wd(E.State)==="stopped"&&T.set(ne,[...T.get(ne)??[],oe]);let M=Number(E.Labels?.[bs]??0),ae=Number.isInteger(M)?M:0;A.set(ne,Math.max(A.get(ne)??ae,ae))}let q=y.filter(E=>E.Labels?.[Re]&&!E.Labels?.[dt]),B=q.map(E=>Wd(E.State)),F=await Gy(q,qy,(E,ne)=>B[ne]==="running"?Promise.resolve(!0):e.hasData(E.Labels?.[Re]??""));return q.map((E,ne)=>{let oe=E.Labels?.[Re]??"",de=Number(E.Labels?.[ws]);return{ref:oe,status:B[ne]??"unknown",generation:Number.isInteger(de)?de:null,hasData:F[ne]??!1,services:b.get(oe)??[],servicesStopped:T.get(oe)??[],...A.has(oe)?{servicesGeneration:A.get(oe)}:{}}})}async function l(){let g=await W(n,t,["ps","--all","--filter",`label=${Re}`,"--format","json"]);return(g.trim()?JSON.parse(g):[]).filter(b=>b.Labels?.[Re]&&!b.Labels?.[dt]).length}async function i(g){let y=await(e.majors??((b,T)=>Uy(n,t,b,T)))(g.ref,g.image);return Ly(g.ref,y)}async function u(g){let y=G(g.ref),b=await i(g);if(b)throw new Error(b);let T=e.credentials,A=await Sd(e.repository,T?()=>T.forProject(g.ref,g.bucket):null),q={SNOUTPOD_OWNER_PASSWORD:g.ownerPassword,POSTGRES_PASSWORD:g.adminPassword,...$d(A)},B=[...await r(),"run","--detach","--name",y.pod,"--label",`${Re}=${g.ref}`,"--label",`${ws}=${g.generation}`,"--userns=auto","--memory",`${g.memoryMb}m`,...Jy(g.cpus),"--restart","no",...e.network?["--network",e.network]:[],...g.port?vs(e.podBind).flatMap(F=>["--publish",`${F}:${g.port}:5432`]):[],...Ay(g.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${g.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${g.bucket}`,"--env",`SNOUTPOD_S3_REGION=${g.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...Wy(g.oauth),...Hy(g.tuning).flatMap(F=>["--env",F]),"--env",`SNOUTPOD_RETENTION_FULL=${xl(g.backup?.retentionDays,g.backup?.fullIntervalHours)}`,...vd(A),g.image,"snoutpod-postgres"];await W(n,t,B,{env:q})}async function c(g){let y=await W(n,t,["ps","--all","--filter",`label=${Re}=${le(g)}`,"--filter",`label=${dt}`,"--format","json"]),b=y.trim()?JSON.parse(y):[],T=[];for(let A of b){let q=A.Labels?.[dt];if(!q)continue;let B=Number(A.Labels?.[bs]??0);T.push({name:q,container:Hd(g,q),spec:A.Labels?.[Ud]??"",pod:A.Labels?.[Fd]??"",running:A.State==="running",generation:Number.isInteger(B)?B:0})}return T}async function p(g){let y=await n(t,["container","inspect","--format","{{.Id}}",G(g).pod]);return y.code===0?y.stdout.trim():""}async function f(g,y){let b=[];for(let F of y){let E=F.dbUriRole??null;F.dbUriEnv&&E&&!b.includes(E)&&b.push(rn(E))}let T=new Map;if(b.length===0)return T;let A={},q=[],B=[];b.some(F=>Pd.includes(F))&&B.push(Ed);for(let F of b.filter(E=>ms.includes(E)))B.push(Ad(F));return b.forEach((F,E)=>{let ne=yy(24).toString("base64url"),oe=`SNOUTPOD_SERVICE_PASSWORD_${E}`;T.set(F,ne),A[oe]=ne,q.push("--env",oe),B.push(`\\getenv service_password_${E} ${oe}`),B.push(`\\set service_role_${E} ${F}`),B.push(`alter role :"service_role_${E}" with password :'service_password_${E}';`)}),B.push(""),await W(n,t,["exec",...he,"--interactive",...q,G(g.ref).pod,"psql","--username","snoutpod_admin","--dbname",G(g.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:A,stdin:B.join(`
433
+ `)}),T}async function S(g){let y=wr(g.ref);await W(n,t,["volume","create","--ignore",y]),await W(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${ds}`,"--entrypoint","bash",g.image,"-c",Zl])}async function v(g){let y=await r();if((await n(t,[...y,"image","exists",g])).code===0)return;let b=await n(t,[...y,"pull","--quiet",g]);if(b.code===0)return;let T=Dy(g);if(!T||!e.registryLogin)throw new Error(`${g} could not be pulled: ${(b.stderr||b.stdout).trim()}`);await e.registryLogin(T),await W(n,t,[...y,"pull","--quiet",g])}async function V(g){let y=g.services??[],b=await c(g.ref),T;y.some(M=>M.smtpSecretEnv)&&(T=await e.smtpSecret?.().catch(()=>{}));let A=y.length>0?await p(g.ref):"",q=new Map(b.map(M=>[M.name,M])),B=new Map(y.map(M=>[M.name,Sy(M,T)])),F=new Set(y.filter(M=>{let ae=q.get(M.name);return!ae||!ae.running||ae.spec!==B.get(M.name)||A===""||ae.pod!==A}).map(M=>M.name)),E=g.servicesGeneration??0;if(F.size===0&&y.length>0&&!b.some(M=>M.generation>=E))for(let M of y)F.add(M.name);let ne=M=>M.dbUriEnv&&M.dbUriRole?M.dbUriRole:null;for(let M=!0;M;){M=!1;let ae=new Set(y.filter(ot=>F.has(ot.name)).map(ne));for(let ot of y){let Bs=ne(ot);!F.has(ot.name)&&Bs!==null&&ae.has(Bs)&&(F.add(ot.name),M=!0)}}for(let M of new Set(y.filter(ae=>F.has(ae.name)).map(ae=>ae.image)))await v(M);for(let M of b)(F.has(M.name)||!B.has(M.name))&&await W(n,t,["rm","--force","--ignore",M.container]);let oe=y.filter(M=>F.has(M.name)),de=await f(g,oe);oe.some(M=>M.name===en)&&await S(g);for(let M of oe){let ae=Hd(g.ref,M.name),ot=[...await r(),"run","--detach","--name",ae,"--label",`${Re}=${g.ref}`,"--label",`${ws}=${g.generation}`,"--label",`${dt}=${M.name}`,"--label",`${bs}=${g.servicesGeneration??0}`,"--label",`${Ud}=${B.get(M.name)??""}`,"--label",`${Fd}=${A}`,"--network",`container:${G(g.ref).pod}`,"--userns",`container:${G(g.ref).pod}`,"--memory",`${M.memoryMb}m`,"--restart","no",...Ty(M),...M.name===en?["--volume",`${wr(g.ref)}:${ds}:ro`]:[],...qd(Bd(g,M,de,T)),M.image];await W(n,t,ot,{env:Bd(g,M,de,T)})}}async function C(g,y){if(y%6!==0)return!1;let b=await n(t,["container","inspect","--format","{{.State.Status}}",G(g).pod]);return b.code===0&&/^(exited|stopped|dead)$/.test(b.stdout.trim())}function Y(g){return["exec",...he,G(g).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres"]}async function R(g){let y=await n(t,[...Y(g),"-tAc",Cl]);return y.code===0?jl(y.stdout):null}async function ee(g){let y=await n(t,[...Y(g),"-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let b=y.stdout.trim();return b==="t"?!0:b==="f"?!1:null}async function O(g,y){let b=A=>n(t,[...Y(g),"-tAc",A]),T=Date.now()+y;do{if((await b("checkpoint")).code!==0)return!1;let q=await b("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(q.code===0&&q.stdout.trim()==="t")return!0;await new Promise(B=>setTimeout(B,1e3))}while(Date.now()<T);return!1}async function pe(g){let y=await n(t,[...Y(g),"-tAc",Al],{timeoutMs:_s});return y.code===0?Rl(y.stdout):null}async function z(g,y){let b=new Map;for(let A of y)le(A),b.set(A,G(A).pod);if(b.size===0)return new Map;let T=await n(t,["exec",G(g).pod,"timeout",String(10),"sh","-c",Nl,"sh",...b.values()]);return T.code===0?Il(T.stdout,b):null}async function ce(g,y){le(y);let b=[];for(let B of[g,y]){let F=G(B).pod,E=await n(t,["container","inspect","--format","{{.State.Status}}",F]);if(E.code===0&&E.stdout.trim()==="running")throw new Error(`${F} is still running, so its data was not moved`);let ne=await n(t,["volume","inspect","--format","{{.Mountpoint}}",F]),oe=ne.code===0?ne.stdout.trim().replace(/\/+$/,""):"";if(!oe)throw new Error(`the volume ${F} could not be found, so nothing was moved`);b.push(oe)}let A=typeof process.getuid!="function"||process.getuid()!==0?await n(t,["unshare","sh","-c",ys,"sh",b[0],b[1]]):await n("sh",["-c",ys,"sh",b[0],b[1]]),q=Dd(A.stdout,A.code);if(q)throw new Error(q)}async function un(g){return(await n(t,[...Y(g),"-v","ON_ERROR_STOP=1",...Md.flatMap(b=>["-c",b])])).code===0}async function ut(g,y){return(await n(t,[...Y(g),"-v","ON_ERROR_STOP=1",...Ol(y).flatMap(T=>["-c",T])],{timeoutMs:_s})).code===0}async function pn(g){let y=await n(t,[...Y(g),"-tAc","select pg_read_file(current_setting('ssl_cert_file')) where current_setting('ssl') = 'on'"]),b=`${y.stdout.trim()}
434
+ `;return y.code===0&&Tl.test(b)?b:null}async function Bu(g,y){if(!El.test(y))throw new Error("not a SCRAM-SHA-256 verifier");let b=G(g).pod,T=[...Y(g),"-v","ON_ERROR_STOP=1"];if((await n(t,[...T,"-c",`do $$ begin if not exists (select 1 from pg_roles where rolname = '${et}') then create role ${et} with login replication; end if; end $$`,"-c",`alter role ${et} with login replication noinherit password '${y}'`],{timeoutMs:_s})).code!==0)return!1;let q=await n(t,[...T,"-tAc","show hba_file"]),B=q.stdout.trim();if(q.code!==0||!/^\/[A-Za-z0-9/_.-]+$/.test(B))return!1;let F=`hostssl replication ${et} all scram-sha-256`;if((await n(t,["exec",b,"sh","-c",'grep -Eq "^host(ssl)?[[:space:]]+replication[[:space:]]+$2[[:space:]]" "$1" || printf "%s\\n" "$3" >> "$1"',"sh",B,et,F])).code!==0)return!1;if((await n(t,[...T,"-tAc","show ssl"])).stdout.trim()!=="on"){let de=B.replace(/\/[^/]+$/,"");if((await n(t,["exec","--user","root",b,"sh","-c",'[ -s "$1/server.key" ] || { openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj "/CN=$2" -keyout "$1/server.key" -out "$1/server.crt" >/dev/null 2>&1 && chown postgres:postgres "$1/server.key" "$1/server.crt" && chmod 0600 "$1/server.key"; }',"sh",de,b])).code!==0||(await n(t,[...T,"-c","alter system set ssl_cert_file = 'server.crt'","-c","alter system set ssl_key_file = 'server.key'","-c","alter system set ssl = on"])).code!==0)return!1}return(await n(t,[...T,"-tAc","select pg_reload_conf()"])).code===0}return{list:a,inRecovery:ee,replayLsn:R,settleTimeline:O,podHealth:pe,setSyncNames:ut,ensureKeeperRole:Bu,podCertificate:pn,swapHomeData:ce,promoteStandby:un,nodesAnswer:z,preflight:i,create:u,createServices:V,async start(g){let y=(await c(g).catch(()=>[])).filter(b=>b.running).map(b=>b.container);y.length>0&&await W(n,t,["stop","--time","5",...y]),await W(n,t,["start",G(g).pod])},async startServices(g){let y=(await c(g)).map(b=>b.container);y.length>0&&await W(n,t,["start",...y])},async applyHostServices(g){let y=[],b=await W(n,t,["ps","--all","--filter",`label=${nn}`,"--format","json"]),T=b.trim()?JSON.parse(b):[],A=new Map;for(let B of T){let F=B.Labels?.[nn];F&&A.set(F,{spec:B.Labels?.[Kd]??"",state:B.State??""})}for(let B of g)try{await q(B)}catch(F){y.push({name:B.name,action:"failed",reason:F instanceof Error?F.message:String(F)})}async function q(B){let F=e.hostServiceLocal?.(B,g),E=F?{...B,...F.env&&Object.keys(F.env).length>0?{env:{...B.env,...F.env}}:{},...F.volumes?{volumes:F.volumes}:{},...F.network?{network:F.network}:{}}:B,ne=kr(E.name),oe=Ey(E),de=A.get(E.name);if(A.delete(E.name),de&&de.spec===oe){de.state!=="running"&&(await W(n,t,["start",ne]),y.push({name:E.name,action:"started",reason:de.state}));return}let M=de?"the spec changed":"it was not here";await v(E.image),de&&await W(n,t,["rm","--force","--ignore",ne]),await W(n,t,[...await r(),"run","--detach","--name",ne,"--label",`${nn}=${E.name}`,"--label",`${Kd}=${oe}`,...E.healthPath?["--label",`${xs}=${E.hostPort}:${E.healthPath}`]:[],"--memory",`${E.memoryMb}m`,"--restart","no",...E.network?["--network",E.network]:e.network?["--network",e.network]:[],...(E.volumes??[]).flatMap(ae=>["--volume",ae]),...vs(e.podBind).flatMap(ae=>["--publish",`${ae}:${E.hostPort}:${E.port}`]),...E.adminPort&&E.adminHostPort?["--publish",`127.0.0.1:${E.adminHostPort}:${E.adminPort}`]:[],...qd(E.env),E.image,...E.command??[]],{env:{...E.env}}),y.push({name:E.name,action:de?"recreated":"created",reason:M})}for(let B of A.keys())await W(n,t,["rm","--force","--ignore",kr(B)]),y.push({name:B,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(g){let y=await W(n,t,["ps","--all","--filter",`label=${nn}`,"--format","json"]),b=(y.trim()?JSON.parse(y):[]).map(A=>A.Labels?.[nn]??"").filter(A=>A!=="");if(b.length===0)return[];let T=Date.now()+g;for(;;){let A=[];for(let q of b)A.push(await zd(n,t,q,kr(q),o));if(A.every(q=>q.ok)||Date.now()>=T)return A;await new Promise(q=>setTimeout(q,500))}},async serviceHealth(g,y){let b=await c(g);if(b.length===0)return[];let T=Date.now()+y;for(;;){let A=await Promise.all(b.map(q=>zd(n,t,q.name,q.container,o)));if(A.every(q=>q.ok)||Date.now()>=T)return A;await new Promise(q=>setTimeout(q,500))}},async stop(g,y){for(let b of await c(g))await W(n,t,["stop","--time",String(vy),b.container]);await W(n,t,["stop","--time",String(y),G(g).pod])},async remove(g){for(let y of await c(g))await W(n,t,["rm","--force","--ignore",y.container]);await W(n,t,["rm","--force","--ignore","--volumes",G(g).pod])},async removeData(g){await W(n,t,["volume","rm","--force",G(g).pod]);let y=wr(g);(await n(t,["volume","exists",y])).code===0&&await W(n,t,["volume","rm","--force",y])},async applyOwnerPassword(g){let y=await n(t,["exec",...he,G(g.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${le(g.ref)}_owner'`]);if(y.code===0&&yd(y.stdout,g.ownerPassword))return;let b=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${g.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
435
+ `);await W(n,t,["exec",...he,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",G(g.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:g.ownerPassword},stdin:b})},async applyRolePassword(g,y,b){let T=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${rn(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
436
+ `);await W(n,t,["exec",...he,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",G(g).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:b},stdin:T})},async updateExtensions(g){let y=G(g),b=await n(t,["exec",...he,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`]),T=b.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(b.stdout.trim())?b.stdout.trim():y.database,A=await W(n,t,["exec",...he,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",T,"--quiet","-tA","-F",gs,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Td});return Od(A)},async applyDbAccess(g,y){let b=G(g),T=await W(n,t,["exec",...he,"--interactive","--env",_r,b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[_r]:dd(g,y)},stdin:cd,timeoutMs:6e4});return ld(T)},async runProjectSql(g,y){let b=G(g);await W(n,t,["exec",...he,"--interactive",b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(g,y,b){let T=kr(g),A=rn(y),q=rn(b),B=E=>["exec",T,"psql","--username",A,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",E];(await W(n,t,B(`select 1 from pg_database where datname = '${q}'`))).trim()!=="1"&&await W(n,t,B(`create database "${q}"`))},async renameProject(g,y){y!==g.ref&&await W(n,t,["exec",...he,"--interactive",G(g.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:zy(y,g.ref)})},async waitReady(g,y){let b=G(g).pod,T=Date.now()+y,A=0;for(;;){let q=await n(t,["exec",...he,b,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(q.code===0&&q.stdout.trim()==="t")return s(g),!0;if(Date.now()>=T||await C(g,++A))return!1;await new Promise(B=>setTimeout(B,500))}},async waitStandby(g,y){let b=Date.now()+y,T=0;for(;;){if(await ee(g)===!0)return!0;if(Date.now()>=b||await C(g,++T))return!1;await new Promise(A=>setTimeout(A,500))}},async capacity(){let[g,y]=await Promise.all([l(),e.capacity()]);return{pods:g,...y}},async databaseBytes(g){let y=await n(t,["exec",...he,G(g).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",g,"-tAc","select pg_database_size(current_database())"]);return y.code===0?es(y.stdout):null},async storageBytes(g){let y=await n(t,["exec",...he,G(g).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",g,"-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?es(y.stdout):null},async setReadOnly(g,y){let b=G(g);await W(n,t,["exec",...he,b.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${b.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(g){let y=await n(t,["exec",...he,G(g).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",g,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(y.code!==0)return null;let[b="",T=""]=y.stdout.trim().split(`
437
+ `)[0]?.split("|")??[],A=Number(T);return{lastArchivedAt:b.trim()||null,failures:Number.isInteger(A)&&A>=0?A:0}}}}async function zd(e,t,n,r,o=Vd){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let p=JSON.parse(s.stdout);a=p[0]?.State,l=p[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=Oy(l?.[xs]);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 Gy(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 Wd(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function zy(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(`
438
+ `)}function Wy(e){if(!e?.issuer)return[];try{return["--env",`${Id}=${Cd(e.issuer)}`]}catch{return[]}}function Jy(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Re,ws,dt,bs,Ud,Fd,vy,$y,xy,nn,Kd,Ry,Jd,xs,Vd,Ny,he,_s,jy,Gd,qy,hs=j(()=>{"use strict";Ae();Qo();ns();rs();wd();tn();xd();ts();Rd();Nd();ps();jd();os();Ld();us();Re="snoutpod.ref",ws="snoutpod.generation",dt="snoutpod.service",bs="snoutpod.services-generation",Ud="snoutpod.service-spec",Fd="snoutpod.service-pod";vy=10,$y=/^[a-z][a-z0-9-]{0,30}$/;xy=/^[a-z][a-z0-9_]{0,62}$/;nn="snoutpod.host-service",Kd="snoutpod.host-service-spec";Ry=/^[A-Za-z_][A-Za-z0-9_]*$/;Jd=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,xs="snoutpod.health";Vd=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()})},Ny="crun";he=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp -c synchronous_commit=local"],_s=3e4,jy=3e4;Gd=200;qy=8});var uu={};Hs(uu,{DEFAULT_LOCAL_MEMORY_MB:()=>tu,DEFAULT_LOCAL_PORT:()=>Qy,ENGINE:()=>Z,ENGINE_NAME:()=>Es,IMAGE:()=>Ts,NETWORK:()=>tt,NEW_PROJECT_MAJOR:()=>Rs,NO_BLACKHOLE_ROUTES:()=>su,PUBLIC_IMAGE:()=>rw,PUBLISHED_MAJORS:()=>At,capacity:()=>ou,chooseLocalImage:()=>an,dropIfUnused:()=>lu,ensureImage:()=>ow,ensureNetwork:()=>lw,exec:()=>ie,hasData:()=>Pr,localImageFor:()=>ru,localPsql:()=>pw,localRuntime:()=>cw,localSpec:()=>cu,localStatus:()=>Os,localUri:()=>iu,networkPolicyInPlace:()=>au,newLocalPassword:()=>tw,newLocalRef:()=>ew,podLog:()=>du,podName:()=>Rt,podmanReady:()=>aw,removeLocal:()=>gw,staleImage:()=>As,startLocal:()=>uw,stopLocal:()=>fw,volumePgVersion:()=>vr});import{randomBytes as Qd}from"node:crypto";import{existsSync as Xd}from"node:fs";import{platform as xr}from"node:os";import{delimiter as Vy,join as Yy}from"node:path";function Xy(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=xr()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(Vy))if(t&&e.some(n=>Xd(Yy(t,n))))return"podman";return Xd("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function an(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return sn(Rs);let n=Number(Ps(e));if(!At.includes(n)){let r=e.trim()||"(empty)",o=At.map(s=>sn(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${At.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return sn(n)}function sn(e){return{name:`${Zy}:${e}`,published:`${eu}:${e}`,major:e}}function ew(){return Sl(e=>Qd(e))}function tw(){return Qd(18).toString("base64url")}async function ow(e,t=ie,n=an(null)){let r=n.published;if(!r)return null;if((await t(Z,["image","exists",n.name])).code===0)return await sw(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(Z,["pull",r],{timeoutMs:nu}).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 sw(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(`
439
+ `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(Z,["pull",r],{timeoutMs:nu}).catch(f=>({code:1,stdout:"",stderr:f instanceof Error?f.message:String(f)})).finally(()=>clearTimeout(l));if(i.code!==0){let f=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
440
+ `).at(-1);e?.(`Could not check for a newer database image (${f}); 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 p=await t(Z,["tag",r,n]);if(p.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${p.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function aw(e=nw,t=Ts){if(Es!=="podman")return`SNOUTPOD_ENGINE=${Es}: 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 ie(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 ie(Z,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await ie(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 iw(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 ie(Z,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
441
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Pr(e){let t=xr()==="darwin"||xr()==="win32";return fd(ie,e,{binary:Z,look:t?iw:void 0})}async function vr(e,t=ie,n=xr()){let r=G(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 At){let l=sn(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 (${At.map(a=>sn(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 ru(e,t=vr){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(an(null));le(e);let n=t===vr?$r.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return an(o)}catch(s){throw new Error(`${Rt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===vr&&($r.set(e,r),r.catch(()=>$r.delete(e))),r}async function ou(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function cw(e){return Yd({exec:ie,binary:Z,network:tt,hasData:Pr,capacity:ou,repository:e})}function Rt(e){return G(e).pod}async function lw(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await dw()||await ie(Z,hr({name:tt}));return}let t=hr({name:tt,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await au())return;await e?.(),await ie(Z,["network","rm","--force",tt]).catch(()=>{});let o=await ie(Z,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${su}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${tt} with egress blocked${n?`: ${n}`:""}`)}async function dw(){return(await ie(Z,["network","inspect",tt,"--format","json"])).code===0}async function au(){let e=await ie(Z,["network","inspect",tt,"--format","json"]);if(e.code!==0)return!1;try{return Wl(JSON.parse(e.stdout)?.[0])}catch{return!1}}function iu(e){let t=G(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function cu(e,t={}){return le(e.ref),{ref:e.ref,generation:1,image:t.image??Ts,memoryMb:t.memoryMb??tu,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 on(e,t){e.onProgress?.(t)}async function uw(e,t,n={}){let r=n.image??(await ru(t.ref,n.pgVersion)).name,o=cu(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Pr,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??As)(t.ref,r))?(on(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??lu)(i)):l.status==="stopped"?(on(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(on(n,"The database is already running."),l.status==="running"&&await(n.staleImage??As)(t.ref,r)&&on(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(on(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 mw(t.ref,s,n.podLog??du));return await e.applyOwnerPassword(o),await e.createServices(o),Os(e,t,n)}function Zd(e){return e.trim().replace(/^sha256:/,"")}async function As(e,t){let[n,r]=await Promise.all([ie(Z,["container","inspect","--format","{{.Image}}",Rt(e)]).catch(()=>null),ie(Z,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Zd(n.stdout),s=Zd(r.stdout);return o&&s&&o!==s?o:null}async function lu(e){let t=await ie(Z,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await ie(Z,["rmi",e]).catch(()=>{})}async function pw(e,t){let n=G(e.ref);return ie(Z,["exec","--interactive","-e","PGPASSWORD",Rt(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 du(e){let t=await ie(Z,["logs","--tail","20",Rt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function mw(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${Rt(e)} never became ready in ${t}ms
419
442
  --- pod log ---
420
- ${r}`}async function Xh(e,t,n={}){ce(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,yl),ps(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Zh(e,t,n){ce(t),await e.remove(t),n&&(await e.removeData(t),dr.delete(t))}async function ps(e,t,n={}){ce(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??pr;return{ref:t.ref,exists:!!r,status:r?.status??"absent",ready:o?await e.waitReady(t.ref,1):!1,hasData:r?.hasData??await s(t.ref),port:t.port,databaseBytes:o?await e.databaseBytes(t.ref):null,services:r?.services??[],uri:t.port>0&&t.ownerPassword?ld(t):null}}var cs,Y,ds,kt,Ih,nd,us,Ye,Dh,rd,se,Uh,Fh,od,dr,id,fd=j(()=>{"use strict";wl();Wt();_l();Ko();Qo();xe();cs=process.env.SNOUTPOD_ENGINE??"podman";Y=jh(),ds=18,kt=[17,18],Ih="snoutpod/postgres",nd="ghcr.io/snoutdata/snoutpod-postgres";us=Qt(null).name,Ye=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Dh=54322,rd=512,se=bl();Uh="cd packages/snoutpod && npm run image:build",Fh=`${nd}:${ds}`;od=20*60*1e3;dr=new Map;id="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});he();var be=["--admin","--token","--project"],br=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],it=[...be,"--plan","--no-wait","--timeout",...br],ye=[...it,"--yes"],Sr=[{name:"init",summary:"Create or link a project for this folder, and print its DATABASE_URL or write it to .env",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, with --sso through your company's identity provider, or with --device by typing a code into one anywhere",flags:["--email","--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to, and whether it is the account login --email asked for",flags:[]},{name:"upgrade",summary:"Install the newest CLI the way this one was installed (binary or npm), checksum verified",flags:["--check"]},{name:"tokens list",summary:"Long-lived access tokens on this account",flags:[]},{name:"tokens create",summary:"Mint an sdt_ token for CI or an agent",flags:["--name","--expires","--project"]},{name:"tokens revoke",args:"<id|sdt_prefix>",summary:"Revoke one by id or prefix",flags:[]},{name:"projects list",summary:"Every project on this account",flags:[]},{name:"projects create",summary:"Make a hosted database, and print its ref",flags:["--name","--region","--team","--no-wait","--timeout","--show-url"]},{name:"projects pause",summary:"Stop a project, and wait until it is paused",flags:["--ref","--no-wait","--timeout"]},{name:"projects resume",summary:"Start a paused project, and wait until it is ready",flags:["--ref","--no-wait","--timeout"]},{name:"projects delete",summary:"Delete a project, and wait until it is gone",flags:["--ref","--no-wait","--timeout"]},{name:"projects show",summary:"One project: state, products (Realtime included), functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage, the data API and push: on or off. Realtime is always on, and listed",flags:["--ref"]},{name:"products enable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push off",flags:["--ref"]},{name:"push credentials",summary:"Push keys: what is set for APNs, FCM and Web Push (never the keys)",flags:["--ref"]},{name:"push credentials set",args:"apns|fcm",summary:"Set the APNs key (a .p8) or the FCM service account, checked before it is stored",flags:["--ref","--p8","--key-id","--team-id","--topic","--environment","--file"]},{name:"push credentials remove",args:"apns|fcm",summary:"Remove the APNs or FCM credentials",flags:["--ref"]},{name:"auth",summary:"Auth settings: Google sign-in and redirect addresses",flags:["--ref"]},{name:"auth google",summary:"Sign in with Google, with your own client (secret on stdin)",flags:["--ref","--client-id","--stdin"]},{name:"auth anonymous",args:"on|off",summary:"Guest sign-in on or off: guestSignIn() gives a browser a session with no email or password",flags:["--ref"]},{name:"auth redirects",summary:"The site URL and the addresses a sign-in may return to",flags:["--ref","--site-url","--allow"]},{name:"auth templates",summary:"The five auth emails, ours or your own",flags:["--ref"]},{name:"auth template",args:"KIND",summary:"Use your own subject and HTML for one, or reset it",flags:["--ref","--file","--subject"]},{name:"realtime inspect",summary:"The channels open now: each client, its roster entry, when it was last heard, and the last minute of messages",flags:["--channel","--watch","--ref"]},{name:"realtime logs",summary:"The connection log: connects, joins, leaves and disconnects, with the reason each ended",flags:["--since","--channel","--ref"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The app key and server key for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...be]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...be]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...be]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...it]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...it,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...ye,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ye]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...it,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...it]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ye,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ye]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ye]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ye,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ye]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ye,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ye,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ye,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ye,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...it,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ye,"--node"]},{name:"cluster dissolve",summary:"A standalone cluster of the home node alone: check the data, take the fences off and forget every keyspace and table, then stop the router yourself",flags:[...ye]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...be,...br]},{name:"cluster jobs",summary:"The latest jobs",flags:[...be]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...be]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...be,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...be,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...be,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...be,...br,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...be,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],xs={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","functions logs --errors":"Only what was printed with console.error or console.warn, and the errors that stopped a request","functions logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","functions logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","containers deploy --port":"The port the container listens on, also given to it as PORT (default 8080)","containers deploy --dockerfile":"A folder with a Dockerfile (or the Dockerfile): built for linux/arm64 with your Docker and uploaded to the project","containers deploy --registry-user":"The user name for a private registry. The token comes on stdin, with --stdin","containers deploy --stdin":"Read the registry token from a pipe, so it is not in shell history or ps","containers deploy --clear-registry":"Forget the stored registry login, for an image that is public now","containers deploy --no-wait":"Answer once it is recorded, without waiting for the host to start it","containers logs --errors":"Only what was printed to stderr","containers logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","containers logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","--name":"The project's name","tokens create --name":"What the token is for and where it lives, shown in tokens list","db restore --name":"The name of the new project the restore makes","--region":"Where the project runs. Defaults to the nearest","--env":"Write DATABASE_URL into .env in this folder, and add .env to .gitignore since it holds the password. Without it the URL is printed","--email":"The account to sign in as. The browser offers it, and a different account is reported at once and by whoami","--provider":"google (the default) or github","--sso":"Sign in with your company's identity provider","--domain":"With --sso: your work email or company domain. Only the domain is sent","--no-browser":"Print the sign-in URL instead of opening a browser","--device":"Sign in by typing a code into a browser on any machine: SSH, containers, agents","--timeout":"Seconds to wait before giving up","--check":"Say whether a newer version exists, and install nothing","--expires":"Days until the token stops working. Without it, it works until revoked","--project":"A SnoutData Cloud project ref: its cluster, through the control plane, as you (instead of --admin)","tokens create --project":"Limit the token to one project ref","--team":"Create it in a team (name or id), shared with its members","--no-wait":"Return as soon as it is asked for, without waiting for it to be true","--show-url":"Also print the connection string, which holds the password. Otherwise db url prints it when wanted","--p8":"The APNs .p8 key file (a path, never the key itself)","--key-id":"The APNs key id","--team-id":"Your Apple developer team id","--topic":"The app's bundle id","--environment":"production or sandbox","--file":"A file to read","push credentials set --file":"The FCM service-account JSON","auth template --file":"The email's HTML body","db restore --file":"The dump to restore (from db export or pg_dump -Fc)","--client-id":"Your Google OAuth client id; the secret is read from stdin with --stdin","--stdin":"Read the secret value from a pipe, so it is not in shell history","--site-url":"Where sign-in emails send people by default","--allow":"Comma-separated addresses a sign-in may return to","--subject":"The email's subject line","--dir":"The folder to read from","db push --dir":"The migrations folder (default ./migrations)","start --dir":"The migrations folder (default ./migrations)","functions deploy --dir":"The function's folder (default ./functions/<name>)","--entrypoint":"The file the runtime starts, when it is not index.ts","--no-verify-jwt":"Let anybody with the URL call it (webhooks). By default a caller needs a project key or a user session","--memory":"Memory per worker, in MB, within the plan","--concurrency":"How many workers it may run at once, within the plan","--reset":"Back to the plan's default size","--force":"Do it although it cannot be undone","--days":"How many days of history","--history":"Print the day-by-day table too","--port":"The port the local database listens on (default 54322)","--no-migrations":"Start the database and apply nothing","--out-of-order":"Apply a new migration that sorts before one already applied","--db-url":"Read a postgres:// database instead of a project","--local":"Read the database snoutdata start is running","--schema":"Comma-separated schemas (default public)","--out":"Write to this file instead of stdout","db export --out":"Where to save the dump","--status":"Show the last export without taking another","--dry-run":"Show what would run, and change nothing","--at":"The moment to restore to (an ISO time), into a NEW project","--window":"How far back a point-in-time restore can go","--allow-delete":"Let the agent's delete_project actually delete","--channel":"Only this channel","--since":"How far back, like 10m, 2h or 1d (default 1h)","--watch":"Keep printing what changes until interrupted","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","cluster range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","cluster cleanup --node":"Only this node. Default: every node","cluster replica add --node":"The node pod's ref, as cluster status lists it (or as the first word)","cluster replica remove --node":"The node pod's ref, as cluster status lists it (or as the first word)","--value":"The key value to pin","--add":"Comma-separated nodes to add, each name=host[:port]","--remove":"How many of the emptiest nodes to drain and remove","cluster scale --nodes":"SnoutData Cloud: how many nodes the project should have, the home included","--max-write-pause-ms":"The longest a cutover may pause writes; one that would take longer is postponed","--drain-timeout-ms":"How long a cutover waits for open transactions to finish","--set":"Settings by their API names, name=value,... (the advisor's advice_* thresholds among them)","--sample-ms":"How long to measure write rates over, in ms (default: the advice_sample_ms setting; 0 skips them)","--ack-timeout-ms":"How long a cutover waits for every router to acknowledge it","--copy-mb-per-s":"The copy rate a plan estimates with (chosen, not measured)","--level":"full (everything the project password can do) or read (reads the project's own tables, never the auth or storage schemas, and writes nothing). Default read"};function _r(e,t){return xs[`${e} ${t}`]??xs[t]??""}function Es(e,t){let n=Sr.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}ee();pe();Q();import{spawn as Wu}from"node:child_process";function Vu(e){return`snout-${e}`}function gt(e,t,n){return new Promise((r,o)=>{let s=Wu("docker",["compose","-p",Vu(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 Yu(e){let t=await gt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new v("failed",t.err.trim()||"docker compose ps failed. Is Docker running?");let n=t.out.trim();return(n.startsWith("[")?JSON.parse(n):n.split(/\r?\n/).filter(Boolean).map(o=>JSON.parse(o))).map(o=>({service:o.Service??"?",state:o.State==="exited"&&o.ExitCode===0?"done":o.State??"?",health:o.Health??""})).sort((o,s)=>o.service.localeCompare(s.service))}async function ma(e){let t=await Yu(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return h({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${$(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${H([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
421
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function fa(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await gt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new v("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function ga(e){let t=await gt(e,["stop"],!0);if(t.code!==0)throw new v("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function ha(e){h({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${H([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
422
- `)})}Pt();import{cpSync as ba,existsSync as Dt,readdirSync as op,readFileSync as Sa,rmSync as _a,statSync as sp,writeFileSync as ka}from"node:fs";import{join as ke,resolve as Tn}from"node:path";var Xu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Zu(e){return Xu.test(e)}function rt(e){if(!Zu(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 Qu=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function ep(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!==".."&&Qu.test(n))}var Ur=2*1024*1024,ya=200;function wa(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>ya)throw new Error(`a function may hold ${ya} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!ep(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=tp(o);if(s.length>Ur)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Ur/1024} KB. A function is source code; put large files in Storage.`);return o}function tp(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 np=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var rp=["SNOUTDATA_","SNOUT_"];function It(e){if(!np.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=rp.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 ib=64*1024;pe();he();Q();function va(e){let t=ke(e.folder,"functions");return Dt(t)?op(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&sp(ke(t,n)).isDirectory()).sort():[]}async function On(e){d(w(" docker compose run --rm functions-deploy"));let t=await gt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new v("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function $a(e,t,n){let r=ke(e.folder,".env"),o=Sa(r,"utf8"),s=(cn(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return ka(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
443
+ ${r}`}async function fw(e,t,n={}){le(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,ud),Os(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function gw(e,t,n){le(t),await e.remove(t),n&&(await e.removeData(t),$r.delete(t))}async function Os(e,t,n={}){le(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Pr;return{ref:t.ref,exists:!!r,status:r?.status??"absent",ready:o?await e.waitReady(t.ref,1):!1,hasData:r?.hasData??await s(t.ref),port:t.port,databaseBytes:o?await e.databaseBytes(t.ref):null,services:r?.services??[],uri:t.port>0&&t.ownerPassword?iu(t):null}}var Es,Z,Rs,At,Zy,eu,Ts,tt,Qy,tu,ie,nw,rw,nu,$r,su,pu=j(()=>{"use strict";pd();tn();gd();is();hs();Ae();Es=process.env.SNOUTPOD_ENGINE??"podman";Z=Xy(),Rs=18,At=[17,18],Zy="snoutpod/postgres",eu="ghcr.io/snoutdata/snoutpod-postgres";Ts=an(null).name,tt=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Qy=54322,tu=512,ie=md();nw="cd packages/snoutpod && npm run image:build",rw=`${eu}:${Rs}`;nu=20*60*1e3;$r=new Map;su="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});be();var ve=["--admin","--token","--project"],Ir=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],pt=[...ve,"--plan","--no-wait","--timeout",...Ir],ke=[...pt,"--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:"ai",summary:"The project's own AI gateway: on or off, its models, budgets and the last 30 days' spend",flags:["--ref"]},{name:"ai enable",summary:"Switch the AI gateway on. Its container joins the pod, which restarts the database once",flags:["--ref"]},{name:"ai disable",summary:"Switch it off. Its providers, models, keys and log stay in the project's database",flags:["--ref"]},{name:"ai models",summary:"The models it serves: provider, upstream id, price and whether each is served",flags:["--ref"]},{name:"ai models add",args:"[name]",summary:"Add a model, with its provider and key when the provider is new. The key comes on stdin and the provider proves it first; without --model, print the steps to find the key and the model's id",flags:["--ref","--source","--model","--provider","--base-url","--region","--price-in","--price-out","--access-key-id","--auth-header","--stdin"]},{name:"ai models remove",args:"<name>",summary:"Stop serving a model",flags:["--ref"]},{name:"ai providers",summary:"Where its models run, and whether each one's key is stored (never the key)",flags:["--ref"]},{name:"ai providers remove",args:"<name>",summary:"Remove a provider no model uses",flags:["--ref"]},{name:"ai routes",summary:"Names that stand for several models, and what each tries in which order",flags:["--ref"]},{name:"ai routes set",args:"<name>",summary:"Make or replace a route: its models, tried in turn when one fails for a reason that is not the caller's",flags:["--ref","--models","--sort"]},{name:"ai routes remove",args:"<name>",summary:"Remove a route; its models stay",flags:["--ref"]},{name:"ai keys",summary:"Keys a server calls the gateway with: created, last used, revoked",flags:["--ref"]},{name:"ai keys create",summary:"Make a key for a server, shown once",flags:["--ref","--name","--models","--expires"]},{name:"ai keys revoke",args:"<id>",summary:"Refuse a key from now on",flags:["--ref"]},{name:"ai budgets",summary:"What may be spent, and by whom",flags:["--ref"]},{name:"ai budgets set",args:"project|users",summary:"The project's budget, or every signed-in user's. A browser call needs both, hard",flags:["--ref","--amount","--period","--soft"]},{name:"ai budgets remove",args:"<id>",summary:"Remove a budget",flags:["--ref"]},{name:"ai usage",summary:"Requests, tokens and cost, grouped",flags:["--ref","--by","--days"]},{name:"ai test",summary:"One chat call through the project's door, with a key made for it and revoked after",flags:["--ref","--model","--prompt"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The app key and server key for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...ve]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...ve]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...ve]},{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:[...pt]},{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:[...pt,"--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:[...ke,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ke]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...pt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...pt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ke,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ke]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ke]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ke,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ke]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ke,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ke,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ke,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ke,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...pt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ke,"--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:[...ke]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...ve,...Ir]},{name:"cluster jobs",summary:"The latest jobs",flags:[...ve]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...ve]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...ve,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...ve,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...ve,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...ve,...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:[...ve,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],qs={"--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","--source":"Where the model runs: bedrock, anthropic, openai, openrouter, ollama or compatible","--model":"The model's id, as the provider names it","ai test --model":"The model to call (default: the first chat model served)","ai models add --provider":"The provider to add it to, or the name of a new one (default: the one already connected for --source)","ai models add --region":"Bedrock: the AWS region the model runs in","ai models add --stdin":"Read the provider's key from a pipe (AWS: the secret access key), so it is in neither the shell history nor ps","--base-url":"The provider's OpenAI-compatible address, usually ending in /v1","--price-in":"Dollars per million input tokens. Both rates, or neither to take the shipped catalogue's","--price-out":"Dollars per million output tokens","--access-key-id":"Bedrock with AWS credentials: the access key id (the secret on stdin)","--auth-header":"A header the server checks for its key, instead of a Bearer token","--models":"Comma-separated models the key may call (default: every one)","ai keys create --name":"What the key is for, shown in ai keys","ai routes set --models":"The models to try, comma-separated, in order","ai routes set --sort":"order (as written, the default) or price (the cheapest first)","ai keys create --expires":"Days until the key stops working. Without it, it works until revoked","--amount":"Dollars","--period":"day (the default) or month","--soft":"Warn in the log when it is passed, instead of refusing","--by":"Group by day, model, provider, operation, key, user or group (default model)","ai usage --days":"How many days, up to 366 (default 30)","--prompt":"What to ask (default: a five-word hello)","--channel":"Only this channel","--since":"How far back, like 10m, 2h or 1d (default 1h)","--watch":"Keep printing what changes until interrupted","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--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 jr(e,t){return qs[`${e} ${t}`]??qs[t]??""}function Gs(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}re();me();te();import{spawn as Wp}from"node:child_process";function Jp(e){return`snout-${e}`}function _t(e,t,n){return new Promise((r,o)=>{let s=Wp("docker",["compose","-p",Jp(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 P("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 P("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Vp(e){let t=await _t(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new P("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 Oa(e){let t=await Vp(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return h({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${x(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])])}
444
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Na(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await _t(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new P("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function Ia(e){let t=await _t(e,["stop"],!0);if(t.code!==0)throw new P("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Ca(e){h({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}`]])}
445
+ `)})}It();import{cpSync as Ma,existsSync as Ht,readdirSync as rm,readFileSync as La,rmSync as Ua,statSync as om,writeFileSync as Fa}from"node:fs";import{join as xe,resolve as Fn}from"node:path";var Yp=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Xp(e){return Yp.test(e)}function it(e){if(!Xp(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 Zp=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Qp(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!==".."&&Zp.test(n))}var Qr=2*1024*1024,ja=200;function Da(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>ja)throw new Error(`a function may hold ${ja} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Qp(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=em(o);if(s.length>Qr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Qr/1024} KB. A function is source code; put large files in Storage.`);return o}function em(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 tm=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var nm=["SNOUTDATA_","SNOUT_"];function Bt(e){if(!tm.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=nm.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 $_=64*1024;me();be();te();function Ba(e){let t=xe(e.folder,"functions");return Ht(t)?rm(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&om(xe(t,n)).isDirectory()).sort():[]}async function Bn(e){d(w(" docker compose run --rm functions-deploy"));let t=await _t(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new P("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Ha(e,t,n){let r=xe(e.folder,".env"),o=La(r,"utf8"),s=(yn(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 Fa(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
423
446
  `)}${l}
424
- `),!0}async function Fr(e,t,n){rt(t);let r=Tn(n.dir??ke(process.cwd(),"functions",t));if(!Dt(ke(r,"index.ts")))throw new g(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ke(e.folder,"functions",t);if(Tn(o)!==r){_a(o,{recursive:!0,force:!0}),ba(r,o,{recursive:!0});let a=ke(r,"..","_shared");Dt(a)&&Tn(a)!==Tn(ke(e.folder,"functions","_shared"))&&ba(a,ke(e.folder,"functions","_shared"),{recursive:!0})}let s=$a(e,t,!n.verifyJwt);if(await On(e),s){let a=await gt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new v("failed",`docker compose up failed after changing who may call ${t}: ${a.err.trim().split(/\r?\n/).slice(-2).join(" ")}`)}return{ref:e.ref,name:t,url:Br(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function xa(e,t,n){let r=await Fr(e,t,n);h(r,()=>{d(`${$(t)} deployed locally: ${r.url}`),n.verifyJwt||d(w(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Br(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Nn(e){return{ref:e.ref,local:e.folder,functions:va(e).map(t=>({name:t,url:Br(e,t)}))}}function Ea(e){let t=va(e);h(Nn(e),()=>{if(t.length===0){d(`No functions in ${ke(e.folder,"functions")}.`);return}process.stdout.write(`${H([["NAME","URL"],...t.map(n=>[n,Br(e,n)])])}
425
- `)})}async function Hr(e,t){rt(t);let n=ke(e.folder,"functions",t);if(!Dt(n))throw new v("not-found",`this local project has no function called ${t}`);return _a(n,{recursive:!0,force:!0}),$a(e,t,!1),await On(e),{ref:e.ref,name:t,removed:!0}}async function Pa(e,t){h(await Hr(e,t),()=>d(`${t} is gone.`))}function Cn(e){return ke(e.folder,"functions",".env")}function qr(e){let t=Cn(e);return Dt(t)?cn(Sa(t,"utf8")):new Map}function Aa(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;ka(Cn(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
447
+ `),!0}async function eo(e,t,n){it(t);let r=Fn(n.dir??xe(process.cwd(),"functions",t));if(!Ht(xe(r,"index.ts")))throw new m(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=xe(e.folder,"functions",t);if(Fn(o)!==r){Ua(o,{recursive:!0,force:!0}),Ma(r,o,{recursive:!0});let a=xe(r,"..","_shared");Ht(a)&&Fn(a)!==Fn(xe(e.folder,"functions","_shared"))&&Ma(a,xe(e.folder,"functions","_shared"),{recursive:!0})}let s=Ha(e,t,!n.verifyJwt);if(await Bn(e),s){let a=await _t(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new P("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:to(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function Ka(e,t,n){let r=await eo(e,t,n);h(r,()=>{d(`${x(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 to(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Hn(e){return{ref:e.ref,local:e.folder,functions:Ba(e).map(t=>({name:t,url:to(e,t)}))}}function qa(e){let t=Ba(e);h(Hn(e),()=>{if(t.length===0){d(`No functions in ${xe(e.folder,"functions")}.`);return}process.stdout.write(`${L([["NAME","URL"],...t.map(n=>[n,to(e,n)])])}
448
+ `)})}async function no(e,t){it(t);let n=xe(e.folder,"functions",t);if(!Ht(n))throw new P("not-found",`this local project has no function called ${t}`);return Ua(n,{recursive:!0,force:!0}),Ha(e,t,!1),await Bn(e),{ref:e.ref,name:t,removed:!0}}async function Ga(e,t){h(await no(e,t),()=>d(`${t} is gone.`))}function Kn(e){return xe(e.folder,"functions",".env")}function ro(e){let t=Kn(e);return Ht(t)?yn(La(t,"utf8")):new Map}function za(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Fa(Kn(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
426
449
  `)+(t.size?`
427
- `:""))}async function Ra(e,t){for(let r of t){let o=It(r.name);if(o)throw new g(o)}let n=qr(e);for(let r of t)n.set(r.name,r.value);Aa(e,n),await On(e),h({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${$(r.name)} set in ${Cn(e)}`)})}function jn(e){return{ref:e.ref,local:e.folder,secrets:[...qr(e).keys()].sort().map(t=>({name:t}))}}function Ta(e){let t=jn(e),n=t.secrets.map(r=>r.name);h(t,()=>{d(n.length?n.join(`
428
- `):`No function secrets in ${Cn(e)}.`)})}async function Oa(e,t){let n=qr(e);if(!n.delete(t))throw new v("not-found",`${t} is not set for this local project`);Aa(e,n),await On(e),h({ref:e.ref,unset:t},()=>d(`${t} unset.`))}pe();tt();Fe();Or();fn();Q();Pn();ee();ee();pe();Fe();Q();async function Na(){let{projects:e,allowance:t}=await _e();h({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${H(ap(e))}
429
- `)})}function ap(e){let t=e.some(n=>typeof n.postgresVersion=="number");return[["REF","NAME","STATE","REGION",...t?["POSTGRES"]:[],"LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?$("read-only"):n.state==="ready"?n.state:$(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Me(n.lastConnectionAt)])]}async function Gr(e){let t=await S("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await jt(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&lt(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;h({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
430
- `),d(""),d(`Ready. ${$(n)} in ${r.region}, at ${r.host}.`),d(e.showUrl?"The password is stored for you: `snoutdata db url` prints this again.":"`snoutdata db url` prints the connection string (it holds the password, so it is not shown here).")})}async function Ca(e,t,n={}){let r=`cloud-project-${e}`,o=await S(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){h({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Ct(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new v("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});h({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ja(e){let t=await S("cloud-project-reset-password",{ref:e});h(t,()=>{process.stdout.write(`${t.password}
431
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Ia(){let{teams:e}=await Rn();h({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${H([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
432
- `)})}async function Da(e){let{teams:t}=await Rn(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}ee();Q();async function Ma(e){let t=await S("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});h(t,()=>{process.stdout.write(`${t.token}
433
- `),d(""),d(`${$(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),d(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),d("This is the only time it is shown: only its hash is stored."),d(w(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function La(){let{tokens:e}=await S("cloud-token-list");h({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${H([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?$(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Me(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
434
- `)})}async function Ua(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await S("cloud-token-revoke",t);h(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}ee();he();Q();async function Fa(e){let t=await we(e);h({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${Ha(t.appKey,t.serverKey)}
435
- `),lp(t.keysIssuedAt)})}async function Ba(e,t){if(!t.force)throw d(`Rotating the keys for ${e} stops every app key and server key already pasted anywhere.`),d("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new g("keys rotate needs --force, because this cannot be undone");let n=An(await S("cloud-project-rotate-jwt",{ref:e}));h(n,()=>{process.stdout.write(`${Ha(n.appKey,n.serverKey)}
436
- `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function Ha(e,t){return H([["KEY","VALUE"],["app key",e],["server key",t]])}function lp(e){d(""),d(`The ${$("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${$("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}ee();he();Q();import{readdir as up,readFile as pp,stat as mp}from"node:fs/promises";import{join as Jr,relative as fp,sep as gp}from"node:path";var qa=["functions"];async function Kr(e){try{return(await mp(e)).isDirectory()}catch{return!1}}async function hp(e,t){if(t){if(!await Kr(t))throw new g(`${t} is not a directory`);return t}for(let n of qa){let r=Jr(n,e);if(await Kr(r))return r}throw new g(`no folder found for ${e} at ${qa[0]}/${e}; use --dir to say where it is.`)}async function Ga(e){let t=[];async function n(r){for(let o of await up(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Jr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await pp(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new g(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:fp(e,s).split(gp).join("/"),text:l})}}return await n(e),t}async function yp(e,t,n){let r=await Ga(t);if(r.length===0)throw new g(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new g(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Jr(t,"..","_shared");if(!await Kr(s))return{entrypoint:o,files:r};let a=(await Ga(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function zr(e,t,n){rt(t);let r=await hp(t,n.dir),{entrypoint:o,files:s}=await yp(t,r,n.entrypoint),a=wa({entrypoint:o,files:s});return{answer:await S("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function Ja(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await zr(e,t,n),l=r.functions.find(i=>i.name===t);h(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${$(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d($(" Anybody who knows this URL can run it. No API key is required.")),d(w(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(w(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Wr(e){return S("cloud-project-functions",{ref:e})}function Vr(e,t){return rt(t),S("cloud-project-functions",{ref:e,action:"delete",name:t})}function Yr(e,t,n){if(rt(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new g("functions size needs --memory MB, --concurrency N, or --reset");return S("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function wp(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 za(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 Wa(e,t,n){let r=await Yr(e,t,n),o=r.functions.find(s=>s.name===t);h(r,()=>{let s=o?wp(o,r.limit):null;d(`${$(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(w(" The next request runs at it; workers already running finish what they hold."));let a=za(r.limit);a&&d(w(` ${a}`))})}async function Va(e){let t=await Wr(e);h(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${H([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":$("open"),r.bytes===null?"-":`${Math.max(1,Math.round(r.bytes/1024))} KB`,r.memoryMb==null?"-":`${r.memoryMb} MB`,r.concurrency==null?"-":String(r.concurrency),r.url])])}
437
- `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=za(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Ka(e){let t=e.dropped_before>0?w(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?w(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?$("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Ya(e,t,n){let r=a=>S("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(h({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Ka(a)}
438
- `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(l=>setTimeout(l,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let l of a)process.stdout.write(`${Ka(l)}
439
- `),s=l.at}}async function Xa(e,t){let n=await Vr(e,t);h(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}ee();Q();import{spawn as jp}from"node:child_process";import{createReadStream as Ip}from"node:fs";import{mkdtemp as Dp,rm as Mp,stat as In}from"node:fs/promises";import{request as Lp}from"node:http";import{request as Up}from"node:https";import{tmpdir as Fp}from"node:os";import{basename as Bp,dirname as Hp,join as eo}from"node:path";var Sp=/^[a-z0-9][a-z0-9_-]{0,31}$/;function _p(e){return Sp.test(e)}function Qa(e){return _p(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 kp=/^sha256:[0-9a-f]{64}$/,vp=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,$p=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,xp=/^(?:[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 Ep(e){return kp.test(e)}function ei(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),!Ep(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),!vp.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()),xp.test(u)?((u==="index.docker.io"||u==="registry-1.docker.io")&&(u="docker.io"),u==="docker.io"&&i.length===1&&i.unshift("library"),i.length===0||i.some(p=>!$p.test(p))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var Cb=["application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json"].join(", ");function Xr(e){let t=e.split(".");if(t.length!==4)return null;let n=[];for(let r of t){if(!/^\d{1,3}$/.test(r))return null;let o=Number(r);if(o>255)return null;n.push(o)}return n}function Pp(e){let t=e.replace(/^\[|\]$/g,""),n=t.indexOf("%");if(n!==-1&&(t=t.slice(0,n)),!/^[0-9a-fA-F:.]+$/.test(t)||!t.includes(":"))return null;let r=t.lastIndexOf(":");if(t.slice(r+1).includes(".")){let c=Xr(t.slice(r+1));if(!c)return null;t=`${t.slice(0,r+1)}${(c[0]<<8|c[1]).toString(16)}:${(c[2]<<8|c[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=c=>c===""?[]:c.split(":"),a=s(o[0]??""),l=o.length===2?s(o[1]??""):[];if([...a,...l].some(c=>!/^[0-9a-fA-F]{1,4}$/.test(c)))return null;let i=a.length+l.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(c=>parseInt(c,16)),...u,...l.map(c=>parseInt(c,16))]}function Ap(e,t,n){let r=Xr(t),o=a=>(a[0]<<24|a[1]<<16|a[2]<<8|a[3])>>>0,s=n===0?0:4294967295<<32-n>>>0;return(o(e)&s)===(o(r)&s)}var Rp=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.88.99.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4]];var jb=1024**3;var Za=16,Tp=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,Op=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function Np(e,t){let n=e??[],r={error:`${t}: ports are up to ${Za} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Za)return r;let o=n.map(s=>typeof s=="string"&&/^\d{1,5}$/.test(s)?Number(s):s);return o.some(s=>typeof s!="number"||!Number.isInteger(s)||s<1||s>65535)?r:[...new Set(o)].sort((s,a)=>s-a)}function Cp(e){let t=e.trim().toLowerCase(),n=Op.exec(t);if(n){let r=Xr(n[1]??""),o=n[2]===void 0?32:Number(n[2]);if(!r||o>32)return{error:`${e} is not an IPv4 address or range.`};let s=(r[0]<<24|r[1]<<16|r[2]<<8|r[3])>>>0,a=o===0?0:4294967295<<32-o>>>0,l=(s&a)>>>0,i=[24,16,8,0].map(u=>l>>>u&255);return Rp.some(([u,c])=>o>=c&&Ap(i,u,c))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return Pp(t)?{error:`${e} is an IPv6 address. Containers send over IPv4 only; list its IPv4 address or its host name.`}:t.startsWith("*.")?{error:`${e}: a wildcard cannot be looked up. List each host name, or the address range.`}:Tp.test(t)?t:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function ti(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=Cp(r?t.slice(0,n):t);if(typeof o!="string")return o;let s=Np(r?t.slice(n+1).split(",").map(a=>a.trim()):[],o);return"error"in s?s:{destination:o,ports:s}}function Zr(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var qp=new Set(["pending","pulling","starting","restarting"]),Gp=5*6e4;function Ln(e){let t=Qa(e);if(t)throw new Error(t)}function to(e,t,n){Ln(t);let r=ei(n.image);if("error"in r)throw new Error(r.error);return S("cloud-project-containers",{ref:e,action:"deploy",name:t,image:n.image,...n.port===void 0?{}:{port:n.port},...n.registryUser?{registryUser:n.registryUser,registryPassword:n.registryPassword??""}:{},...n.clearCredential?{clearCredential:!0}:{}})}function ht(e){return S("cloud-project-containers",{ref:e})}function no(e,t){return Ln(t),S("cloud-project-containers",{ref:e,action:"delete",name:t})}function ro(e,t){return Ln(t),S("cloud-project-containers",{ref:e,action:"restart",name:t})}function Dn(e,t){return S("cloud-project-containers",{ref:e,action:"logs",...t.name?{name:t.name}:{},...t.errorsOnly?{errorsOnly:!0}:{},...t.since?{since:t.since}:{},limit:t.limit??100})}async function si(e,t){let n=Date.now()+Gp,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await ht(e)).containers.find(o=>o.name===t)??null,!r||!qp.has(r.status))return r;return r}function oo(e){if(!e.allowed)return"stopped: not covered by the plan";let t=e.restarts>0?`, ${e.restarts} restart${e.restarts===1?"":"s"}`:"";return`${e.status}${t}`}async function ai(e,t,n){await ii(e,t,await to(e,t,n),n.wait!==!1)}async function ii(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 si(e,t)??o),h({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${$(t)} deployed.`);return}d(`${$(t)} ${oo(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(w(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d($(" Anything written inside the container is lost when it restarts or is redeployed.")),d(w(" Keep what matters in your database.")),n.allowance&&d(w(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var Kp=2*1024**3,Jp=3*6e4;async function zp(e){let t;try{t=await In(e)}catch{throw new Error(`${e} does not exist: --dockerfile takes a folder with a Dockerfile in it, or the Dockerfile.`)}if(t.isDirectory()){try{await In(eo(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Hp(e),file:e}}function Wp(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function so(e){return e.replace(/\b\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData's registry").replace(/\barn:aws[a-z-]*:[^\s"',;)]*/g,"a SnoutData credential").replace(/\b[a-z0-9.-]*\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData Cloud").replace(/\b\d{12}\b/g,"(an account)")}function Vp(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?so(n):e===413?"the image is too large to upload: a container's image may be at most 1 GB compressed; a slimmer base (alpine, distroless, -slim) usually fixes it.":`the upload failed (${e}); build again in a moment.`}function Yp(e){return new Promise((t,n)=>{let r=jp("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Xp(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?Lp:Up)(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=Ip(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var ci={docker:Yp,scratch:()=>Dp(eo(Fp(),"snoutdata-build-")),remove:e=>Mp(e,{recursive:!0,force:!0}),size:async e=>(await In(e)).size,upload:async(e,t)=>Xp(e,t,(await In(t)).size),ask:e=>S("cloud-project-containers",e),say:e=>d(w(e)),now:()=>Date.now()};function ni(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function ri(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 Zp(e,t,n,r=ci){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 ri(r,e,t),s=await r.scratch();try{let a=eo(s,"image.tar");if(r.say(`${t}: building ${n.file?Bp(n.file):n.context} for linux/arm64`),await r.docker(Wp({...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>Kp)throw new Error(`the image is ${ni(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()>Jp||(o=await ri(r,e,t)),r.say(`${t}: uploading the image (${ni(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(Vp(i.status,i.body));let u="";try{u=String(JSON.parse(i.body).digest??"")}catch{}if(!/^sha256:[0-9a-f]{64}$/.test(u))throw new Error("the upload finished without saying what it stored, so there is nothing to deploy; build again.");return u}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(so(o instanceof Error?o.message:String(o)))}}async function li(e,t,n,r=ci){Ln(t);let o=await zp(n.path),s=await Zp(e,t,o,r),a;try{a=await r.ask({ref:e,action:"deploy",name:t,pushed:s,...n.port===void 0?{}:{port:n.port}})}catch(l){throw new Error(so(l instanceof Error?l.message:String(l)))}await ii(e,t,a,n.wait!==!1)}async function di(e){let t=await ht(e);h(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(w(" Containers run on the Plus and Pro plans.")):d(w(" snoutdata containers deploy <name> <image>"));return}d(H([["name","status","image","url"],...t.containers.map(r=>[r.name,oo(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(w(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(w(` Sent this month: ${Qr(n.usedBytes)} of ${Qr(n.allowanceBytes)}. At ${Qr(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Mn(e,t){if(t===null)return S("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=ti(r);if("error"in o)throw new Error(o.error);n.push(o)}return S("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Qp(e){if(e.allowlist===void 0)return["This control plane does not know about allowlists yet."];if(e.allowlist===null)return[`Containers on ${e.ref} may reach the public internet (within the limits on how much and how fast).`,w(" snoutdata containers egress set <host[:port]> ... to allow only some destinations")];let t=[e.allowlist.allow.length===0?`Containers on ${e.ref} may reach nothing outside the project.`:`Containers on ${e.ref} may reach only:`];for(let n of e.allowlist.allow)t.push(` ${Zr(n)}${n.ports.length===0?w(" (any port)"):""}`);return t.push(w(` and always: ${e.allowlist.alwaysAllowed.map(Zr).join(", ")}, and each other`)),t.push(w(" snoutdata containers egress clear to allow the public internet again")),t}async function Un(e,t){let n=t.clear?await Mn(e,null):t.set?await Mn(e,t.set):await ht(e);h({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Qp(n))d(r);(t.set||t.clear)&&d(w(" Running containers follow it within a minute; nothing restarts."))})}function Qr(e){return`${Math.round(e/1024**3*10)/10} GB`}async function ui(e,t){await ro(e,t);let n=await si(e,t);h({ref:e,containers:n?[n]:[]},()=>{d(n?`${$(t)} ${oo(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function pi(e,t){let n=await no(e,t);h(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function oi(e){return`${w(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?$(e.message):e.message}`}async function mi(e,t,n){let r=((await Dn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(h({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${oi(s)}
440
- `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await Dn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${oi(a)}
441
- `),o=a.at}}ee();he();Q();function gi(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function Fn(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function hi(e,t){if(e.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await Fn()}]}return e.map(n=>{let r=gi(n);if(!r)throw new g(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function yi(e,t,n){if(t.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await Fn()})}else for(let s of t){let a=gi(s);if(!a)throw new g(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=It(s.name);if(a)throw new g(a)}let o=null;for(let s of r)o=await S("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});h(o,()=>{for(let s of r)d(`${$(s.name)} set`);d(w(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(w(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function wi(e,t){let n=It(t);if(n)throw new g(n);let r=await S("cloud-project-secrets",{ref:e,action:"unset",name:t});h(r,()=>{d(`${t} is gone.`)})}async function bi(e){let t=await S("cloud-project-secrets",{ref:e});h(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${H([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Me(n.updatedAt)])])}
442
- `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Kt}from"node:child_process";import{appendFileSync as Hi,existsSync as zn,readFileSync as qi,statSync as Um,unlinkSync as Fm,writeFileSync as wt}from"node:fs";import{dirname as Bm,join as Wn}from"node:path";import{tmpdir as Hm}from"node:os";import{open as Gi}from"node:fs/promises";function Si(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 _i(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 ki(e){let t=e.split(`
443
- `).filter(r=>/^pg_restore: error:/.test(r.trim())),n=t.filter(r=>!/must be owner of extension/.test(r));return{total:t.length,harmless:t.length-n.length,fatal:n}}var ao={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},tm=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Bn=new Set(["snout_identity","snout_files","push","snout_live","snout_realtime","_realtime","graphql","snout_graph","snout_ext","net","cron","pgbouncer","vault","pgsodium","auth","storage","realtime","graphql_public","extensions"]),nm=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,rm={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},om=new Set(["push.settings"]),sm=["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 am(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=sm.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 im(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 vi(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
444
- `)){let u=am(i);if(!u)continue;let{type:c,schema:p,name:f,owner:E}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let X=/^(schema_migrations|migrations)$/.test(f)&&Bn.has(p);if(/realtime/.test(p)||p==="snout_live"||X||p==="public"&&f==="spatial_ref_sys"){s++;continue}if(p==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(p==="push"&&f==="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 L=rm[p];if(L){L==="storage"&&(f==="objects"||f==="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(L,[...n.get(L)??[],u]);continue}if(Bn.has(p)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&p==="-"&&/^EXTENSION( |$)/.test(f)||(c==="SCHEMA"?Bn.has(f):Bn.has(p))||nm.test(f)||tm.test(E)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=im(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(f=>f.type==="TABLE DATA"&&om.has(`${f.schema}.${f.name}`)).map(f=>`${f.schema}.${f.name}`),p=u.filter(f=>f.type==="TABLE DATA").map(f=>`${f.schema}.${f.name}`);return{product:i,clear:c,tables:p,lines:cm(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function cm(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}ee();he();pe();Q();ee();pe();var co=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],xi=["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 Mt(e){return e==="done"||e==="failed"||e==="cancelled"}function Lt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new v("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Ce(mm(e.project))}let n=e.admin??$i(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??$i(t.LEPIS_ADMIN_TOKEN);if(!r)throw new v("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new io(lm(n),r)}function $i(e){return e===void 0||e===""?void 0:e}function lm(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new v("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new v("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new v("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!dm(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 dm(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var um=6e4,io=class{target;#t;#n;constructor(t,n){this.#t=t,this.#n=n,this.target=t.toString().replace(/\/$/,"")}status(){return this.#e("GET","/v1/status")}ops(){return this.#e("GET","/v1/ops")}plan(t){return this.#e("POST","/v1/plan",t)}submit(t){return this.#e("POST","/v1/jobs",t)}async jobs(){return(await this.#e("GET","/v1/jobs")).jobs}job(t){return this.#e("GET",`/v1/jobs/${t}`)}cancel(t){return this.#e("POST",`/v1/jobs/${t}/cancel`)}resume(t){return this.#e("POST",`/v1/jobs/${t}/resume`)}settings(t){return this.#e("POST","/v1/settings",t)}advice(t){return this.#e("GET",t===void 0?"/v1/advice":`/v1/advice?sample_ms=${Math.max(0,Math.floor(t))}`)}async#e(t,n,r){let o=`${this.target}${n}`,s;try{s=await fetch(o,{method:t,headers:{authorization:`Bearer ${this.#n}`,accept:"application/json",...t==="POST"?{"content-type":"application/json"}:{}},...t==="POST"?{body:JSON.stringify(r??{})}:{},signal:AbortSignal.timeout(um)})}catch(i){let u=ln(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new v(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${c}`)}let a=await s.text(),l;try{l=a===""?{}:JSON.parse(a)}catch{throw new v("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw Ei(s.status,l);return l}},pm={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function Ei(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?pm[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":et(e));return s==="not-signed-in"?new v(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new v(s,r,o)}function mm(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 fm={"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"},gm="cloud-project-shards",Ce=class{target;ref;#t;constructor(t,n=S){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Je(o),answer:o}}async jobs(){return(await this.#e({action:"jobs"})).jobs}job(t){return this.#e({action:"jobs",id:t})}cancel(t){return this.#e({action:"cancel",id:t})}resume(t){return this.#e({action:"resume",id:t})}settings(t){return this.#e({action:"settings",settings:t})}advice(t){return this.#e({action:"advice",...t===void 0?{}:{sampleMs:Math.max(0,Math.floor(t))}})}enable(t){return this.#e({action:"enable",...t?{plan:!0}:{}})}spread(t,n){return this.#e({action:"spread",request:t,...n?{plan:!0}:{}})}retry(t){return this.#e({action:"retry",...t})}disable(t){return this.#e({action:"disable",...t?{plan:!0}:{}})}replica(t,n,r){return this.#e({action:n?"remove-replica":"add-replica",node:t,...r?{plan:!0}:{}})}async alerts(){return(await this.status()).cloud?.alerts??[]}acknowledge(t){return this.#e({action:"acknowledge",alert:t})}#n(t){let n=fm[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(gm,{ref:this.ref,...t})}catch(n){throw hm(n)}}};function Je(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"",r=t.length===1;return e.joining===!1?`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. Attach ${r?"it":"each"} once it is ready: snoutdata cluster nodes attach <ref>.${n}`:`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. ${r?"It joins":"They join"} the cluster and take${r?"s":""} ${r?"its":"their"} share of the data on ${r?"its":"their"} own; follow it with snoutdata cluster status.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`:Array.isArray(e.queued)?`${String(e.next??"The table is being spread over the cluster.")} Steps: ${e.queued.map(String).join(", ")}. Follow it with snoutdata cluster status.`:typeof e.joining=="boolean"&&typeof e.node=="string"?e.joining?`${e.node} is joining the cluster again.`:`${e.node} is left as it is.`:e.queue==="retrying"?"The cluster is trying that step again.":typeof e.replica=="string"&&typeof e.node=="string"?`A replica of node ${e.node} is being made (${e.replica}). ${String(e.note??"It protects the node once it is following it.")}`:typeof e.changed=="boolean"&&typeof e.node=="string"&&e.sharded===void 0?e.changed?`The replica of node ${e.node} is being removed, and its project slot freed.`:`node ${e.node} has no replica. Nothing changed.`:e.acknowledged===!0?"Marked as seen. The alert stays open until what it is about is over.":e.unchanged===!0?`The project already has ${String(e.nodes)} nodes. Nothing changed.`:e.sharded===!0?e.changed===!1?"This project is already a cluster.":String(e.note??"This project is a cluster now."):e.sharded===!1?e.changed===!1?"This project is not a cluster.":String(e.note??"This project is not a cluster any more."):typeof e.next=="string"?e.next:"Done."}function hm(e){if(!(e instanceof Se))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return Ei(e.status,{error:{code:typeof t.apiCode=="string"?t.apiCode:void 0,kind:typeof t.kind=="string"?t.kind:void 0,message:e.message,sqlstate:typeof t.sqlstate=="string"?t.sqlstate:void 0}});let n=t.code==="not-sharded"?"conflict":et(e.status);return new v(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Ft(e){if(Array.isArray(e))return`[${e.map(Ft).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)}:${Ft(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var po=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
445
- ${Ft(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
446
- ${Ft(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
447
- ${Ft(t)}`)}},ym=new po,mo=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],wm="2024-11-05";function F(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var A={type:"string"},Pi={type:"object",description:'The operation as the router takes it, e.g. {"op": "range.split", "keyspace": "k", "range": "-3074457345618258603", "at": "18454348402311335", "to": 3}. Range bounds and seeds are 64-bit, so pass them as strings. cluster_advice returns these ready-made, as `operation`. For a SnoutData Cloud project a node is a pod the project makes: {"op": "node.add"} makes one (no host), {"op": "node.attach", "name": "<its ref>"} hands a ready one to the cluster, and {"op": "scale", "nodes": N} is a node count.',properties:{op:{type:"string",enum:[...mo]}},required:["op"],additionalProperties:!0},Hn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},ze={type:"string",description:"A SnoutData Cloud project's ref (from list_projects): its cluster, as the signed-in user. Omit for the standalone router this server was started with (LEPIS_ADMIN_URL)."},Ht=[{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:F({})},{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:F({})},{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:F({name:A,region:A,teamId:A},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:F({ref:A},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:F({ref:A},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:F({ref:A},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs nothing installed: the CLI connects to the database itself. A file holding a psql command (\\i, \\set), or a COMMIT, END or ROLLBACK that would end the transaction the file runs in, is refused before anything runs; a file starting with -- snoutdata:no-transaction runs as written, outside a transaction.",inputSchema:F({ref:A,dir:A,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:F({ref:A,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:F({ref:A},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:F({ref:A},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING usually within fifteen seconds (longer while the host is busy starting other databases), including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about fifteen seconds before connecting, and retry a refusal for up to two minutes). Use it when a credential has leaked, not routinely.",inputSchema:F({ref:A},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:F({})},{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:F({})},{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:F({name:A,expires:A,project:A},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:F({id:A},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/v1/fn/<name>. Reads functions/<name> unless dir says otherwise. Deno, so no build step and no node_modules. Set openToAnyone ONLY for a webhook receiver whose sender cannot send an API key: it makes the URL callable by anybody who knows it, and the function must then check the sender's signature itself.",inputSchema:F({ref:A,name:A,dir:A,entrypoint:A,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:F({ref:A},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:F({ref:A,name:A,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:F({ref:A,name:A},["ref","name"])},{name:"list_containers",description:"The project's Snout Containers: each one's status (pending, pulling, starting, running, restarting, failed, stopped), why when it is not running, its image and pinned digest, restarts, and its public URL. allowed=false means the plan no longer covers it and it is stopped. Also the plan's allowance. Costs nothing.",inputSchema:F({ref:A},["ref"])},{name:"deploy_container",description:"Run a Docker image all the time beside the project, at https://<ref>.api.snoutdata.com/containers/v1/<name>/ (no API key needed there: the container authenticates its own callers). The image tag is pinned to the digest it points at now and must include linux/arm64. It listens on PORT (port, default 8080) and gets DATABASE_URL and the project's secrets in its environment. STATELESS: anything written inside it is lost on every restart or redeploy, so state belongs in the database. Plus and Pro plans only. A private image needs a person's first deploy with the CLI (no tool takes a registry token); a redeploy reuses the stored login. Returns at once: poll list_containers until the status is running or failed.",inputSchema:F({ref:A,name:A,image:A,port:{type:"integer"}},["ref","name","image"])},{name:"restart_container",description:"Start one container again from its image. Everything written inside it is lost, which is also how to clear a container stuck in a bad state. Poll list_containers to see it come back.",inputSchema:F({ref:A,name:A},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:F({ref:A,name:A},["ref","name"])},{name:"container_logs",description:"What a project's containers printed to stdout and stderr in the last seven days, newest first: each line's time, container, stream and text. Lines arrive about a minute after they are printed. Use it when list_containers says failed or restarting. name narrows to one container, errorsOnly to stderr, limit is at most 500 (default 100). Costs nothing.",inputSchema:F({ref:A,name:A,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"container_egress",description:"Where a project's containers may send. allowlist null means the public internet (within the limits on how much and how fast); otherwise allowlist.allow is the only destinations they reach, and alwaysAllowed what they reach whatever the list says (the project's own database and API). The same as `snoutdata containers egress`. Costs nothing.",inputSchema:F({ref:A},["ref"])},{name:"set_container_egress",description:'Hold a project\'s containers to an allowlist, or let them reach the public internet again. Give EITHER allow, a list of destinations ("api.example.com:443", "api.example.com" for any port, "203.0.113.0/24:443,8443"; an empty list means nothing outside the project), OR clear: true. The list replaces the one there. Running containers follow it within a minute and nothing restarts, so a container that needs a destination left off stops reaching it. The same as `snoutdata containers egress set|clear`.',inputSchema:F({ref:A,allow:{type:"array",items:A},clear:{type:"boolean"}},["ref"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:F({ref:A},["ref"])},{name:"function_logs",description:"What a project's functions printed with console.log and console.error in the last seven days, newest first: each line's time, function, level ('error' for console.error and console.warn) and text. Lines arrive about a minute after they are printed. Use it after deploying or calling a function to see why it failed. name narrows to one function, errorsOnly to errors, limit is at most 500 (default 100). Costs nothing.",inputSchema:F({ref:A,name:A,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"get_project",description:"Everything about one project in one call: its state, region and size, which products are on (auth, storage, data-api), the names of its deployed functions and function secrets, and its custom domains. Never a password, a key or a download link (export_status has the link to the last export).",inputSchema:F({ref:A},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:F({ref:A},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. All three are on every plan, including free.",inputSchema:F({ref:A,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:F({ref:A},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:F({ref:A,hostname:A},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:F({ref:A,hostname:A},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:F({ref:A,hostname:A},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:F({ref:A},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:F({ref:A,at:A,name:A},["ref","at"])},{name:"cluster_status",description:"A Lepis cluster (one Postgres database spread over several nodes): a Cloud project's with project, else the router this server was started against with LEPIS_ADMIN_URL. Its nodes, keyspaces and their ranges (a range is keyspace:lo; bounds are strings), tables, routers, unfinished jobs and settings; for a Cloud project also whether it is a cluster, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:F({project:ze})},{name:"cluster_advice",description:"Ask the cluster's advisor what to split, move or add, from each node's size, connections and write rate and each range's measured share. Each recommendation has a reason, its plan, and an `operation` that cluster_run takes as it is. RUNS NOTHING. sampleMs: how long to measure write rates over (the cluster's setting by default; 0 skips them; at most 30000).",inputSchema:F({project:ze,sampleMs:{type:"integer"}})},{name:"cluster_plan",description:"The dry run of one cluster operation: its steps, what moves where, rows, bytes, the estimated copy time and the expected write pause. Changes nothing. Planning an operation is what lets cluster_run run it.",inputSchema:F({project:ze,operation:Pi},["operation"])},{name:"cluster_run",description:"Run one cluster operation as a durable job. IT MOVES DATA, and each cutover pauses writes to what moves for up to the cluster's max_write_pause_ms. Without confirm: true it only returns the plan and runs nothing: show that plan to the user, and when they agree call again with the same operation and confirm: true. confirm runs only an operation planned here (cluster_plan, cluster_advice, or cluster_run without confirm) in the last 15 minutes. Returns the job id; follow it with cluster_watch_job.",inputSchema:F({project:ze,operation:Pi,confirm:{type:"boolean"}},["operation"])},{name:"cluster_jobs",description:"The cluster's latest jobs, or with id one job with each step's state and phase. Costs nothing.",inputSchema:F({project:ze,id:Hn})},{name:"cluster_watch_job",description:"Wait for a job to finish, up to timeoutSeconds (default 30, at most 120), and return its state and steps: done, failed (with the error; cluster_resume_job runs it again from the failed step) or cancelled. Running out of time stops the waiting, never the job; call again to keep waiting.",inputSchema:F({project:ze,id:Hn,timeoutSeconds:{type:"integer"}},["id"])},{name:"cluster_cancel_job",description:"Stop a running job. A move cancelled before its cutover rolls back; one cancelled after it finishes its cleanup.",inputSchema:F({project:ze,id:Hn},["id"])},{name:"cluster_resume_job",description:"Run a failed job again from the step that failed. It carries on the operation that was already approved and planned; follow it with cluster_watch_job.",inputSchema:F({project:ze,id:Hn},["id"])},{name:"delete_project",description:"Delete a project. THIS DESTROYS A DATABASE. Off unless the person who started this server passed --allow-delete, in which case it still only marks the project deleted and its data is erasable for a grace period.",inputSchema:F({ref:A},["ref"])}],bm=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","container_egress","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Sm=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Ai=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function fo(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(fo)};let n=t.export;if(n&&typeof n=="object"){let{url:r,rolesSql:o,...s}=n;return{...t,export:{...s,hasLink:typeof r=="string"&&r.length>0}}}return e}function go(e,t){return t.available?null:t.pitrEnabled?`${e} has no backup to restore from yet.`:`Point-in-time restore is part of the Pro and Business plans, and this project is on ${t.tier??"another plan"}.`}function _m(e){return{...e,annotations:bm.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Sm.has(e.name)}}}function km(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Bt(e,t){return{jsonrpc:"2.0",id:e,result:t}}function qn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function _(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Bt(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function vm(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Ut(e){return{op:e.op,cutovers:e.cutovers,moves:(e.moves??[]).map(t=>({from:t.source,to:t.targets,tables:t.tables,estimated_rows:t.estimated_rows,estimated_bytes:t.estimated_bytes,estimated_copy_seconds:t.estimated_copy_seconds,expected_pause_ms:t.expected_pause_ms})),steps:(e.steps??[]).map(t=>t.kind),estimated_rows:e.estimated_rows,estimated_bytes:e.estimated_bytes,estimated_copy_seconds:e.estimated_copy_seconds,expected_pause_ms:e.expected_pause_ms,max_write_pause_ms:e.max_write_pause_ms,warnings:e.warnings??[],...e.assumptions?{assumptions:e.assumptions}:{}}}function lo(e){return{id:e.id,op:e.op,state:e.state,finished:Mt(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 Ri(e,t){let n=e.operation;if(!n||typeof n!="object"||Array.isArray(n))return`${t} needs operation: an object like {"op": "range.split", "keyspace": "k", "range": "<lo>"}. cluster_advice returns them ready-made.`;let r=n;if(typeof r.op!="string"||!mo.includes(r.op))return`${t}: operation.op must be one of ${mo.join(", ")}.`;for(let o of["range","at","a","b","seed"]){let s=r[o];if(typeof s=="number"&&!Number.isSafeInteger(s))return`${t}: operation.${o} is a 64-bit number and lost its last digits as a JSON number. Pass it as a string, exactly as cluster_status or cluster_advice wrote it.`}return r}function uo(e){let t=typeof e.id=="string"&&/^\d+$/.test(e.id)?Number(e.id):e.id;return typeof t=="number"&&Number.isSafeInteger(t)&&t>0?t:null}function $m(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var xm="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 Em(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,xm,!0);s=r.shards(a)}catch(u){return _(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??ym,i=a??"";try{switch(t){case"cluster_status":return _(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let p of c.advice)p.plan&&l.remember(p.request,i);return _(e,{summary:c.summary,advice:c.advice.map(p=>({op:p.op,reason:p.reason,metric:p.metric,operation:p.request,...p.plan?{plan:Ut(p.plan)}:{},...p.needs.length>0?{needs:p.needs,note:`Ask the user for ${p.needs.join(", ")}, then plan it with cluster_plan.`}:{},...p.plan_error?{plan_error:p.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=Ri(n,t);if(typeof u=="string")return _(e,u,!0);let c=await s.plan(u);return l.remember(u,i),_(e,{plan:Ut(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=Ri(n,t);if(typeof u=="string")return _(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let p=await s.plan(u);l.remember(u,i);let f=n.confirm===!0;return _(e,{ran:!1,plan:Ut(p),next:f?"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."},f)}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:Ut(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return _(e,{jobs:await s.jobs()});let u=uo(n);if(u===null)return _(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return _(e,{...lo(c),plan:c.plan&&Array.isArray(c.plan.steps)?Ut(c.plan):null})}case"cluster_watch_job":{let u=uo(n);if(u===null)return _(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,p=Date.now()+c*1e3;for(;;){let f=await s.job(u);if(Mt(f.state))return _(e,lo(f),f.state!=="done");if(Date.now()>=p)return _(e,{...lo(f),note:`Still ${f.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(E=>setTimeout(E,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=uo(n);if(u===null)return _(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return _(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return qn(e,-32602,`unknown tool: ${t}`)}}catch(u){return _(e,$m(u),!0)}}function W(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ti(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 Bt(r,{protocolVersion:wm,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Bt(r,{});case"tools/list":return Bt(r,{tools:km([...Ht.map(_m),...n.borrowed??[]])});case"tools/call":return Pm(r,s,t,n);default:return qn(r,-32601,`unknown method: ${o}`)}}async function Pm(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Ht.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return qn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"];if(o.startsWith("cluster_")&&Ht.some(c=>c.name===o))return Em(e,o,s,n,r);let i=W(s,"ref");if(l.includes(o)&&!i)return _(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!Ai.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: ${Ai.join(", ")}.`,!0);try{switch(o){case"whoami":return _(e,await n.whoami());case"list_projects":return _(e,fo(await n.listProjects()));case"create_project":{let c=W(s,"name");return c?_(e,await n.createProject({name:c,region:W(s,"region")??void 0,teamId:W(s,"teamId")??void 0})):_(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return _(e,{url:c.uri,state:c.state,wakesInstantly:c.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return _(e,await n.setProjectState("pause",i));case"resume_project":return _(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return _(e,await n.usage(i,c))}case"export_status":return _(e,await n.exportStatus(i));case"start_export":return _(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),p=await n.connection(i);return _(e,{url:p.uri,appliesIn:c.appliesIn??"within about fifteen seconds",next:"Wait about fifteen seconds before connecting with this URL, and retry a refusal for up to two minutes: until the change applies (usually 5 to 15 seconds, longer while the host is busy) the new password is refused and the old one still works, which does not mean the new one is wrong. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return _(e,await n.listTeams());case"get_project":return _(e,fo(await n.getProject(i)));case"list_products":return _(e,await n.getProducts(i));case"set_product":{let c=W(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?_(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?_(e,"set_product needs enabled: true or false.",!0):_(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return _(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=W(s,"hostname");return c?_(e,await n.domainAction(i,o.split("_")[0],c)):_(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),p=c&&c.restore?go(i,c.restore):null;return _(e,p?{...c,reason:p}:c)}case"restore_to_point":{let c=W(s,"at");return c?_(e,await n.restoreTo(i,c,W(s,"name")??void 0)):_(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,"deploy_function needs a project ref and a function name.",!0):_(e,await n.deployFunction(c,p,{dir:W(s,"dir")??void 0,entrypoint:W(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=W(s,"ref");return c?_(e,await n.listFunctions(c)):_(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=W(s,"ref"),p=W(s,"name");if(!c||!p)return _(e,"size_function needs a project ref and a function name.",!0);let f=typeof s.memoryMb=="number"?s.memoryMb:void 0,E=typeof s.concurrency=="number"?s.concurrency:void 0,k=s.reset===!0;return!k&&f===void 0&&E===void 0?_(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):_(e,await n.sizeFunction(c,p,{memoryMb:f,concurrency:E,reset:k}))}case"delete_function":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,"delete_function needs a project ref and a function name.",!0):_(e,await n.deleteFunction(c,p))}case"list_containers":{let c=W(s,"ref");return c?_(e,await n.listContainers(c)):_(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=W(s,"ref"),p=W(s,"name"),f=W(s,"image");if(!c||!p||!f)return _(e,"deploy_container needs a project ref, a container name and an image.",!0);let E=typeof s.port=="number"?s.port:void 0;return _(e,await n.deployContainer(c,p,{image:f,...E===void 0?{}:{port:E}}))}case"restart_container":case"delete_container":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,`${o} needs a project ref and a container name.`,!0):_(e,o==="restart_container"?await n.restartContainer(c,p):await n.deleteContainer(c,p))}case"container_logs":{let c=W(s,"ref");if(!c)return _(e,"container_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.containerLogs(c,{name:W(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"container_egress":{let c=W(s,"ref");return c?_(e,await n.containerEgress(c)):_(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let c=W(s,"ref");if(!c)return _(e,"set_container_egress needs a project ref.",!0);let p=s.clear===!0,f=s.allow;return f!==void 0&&(!Array.isArray(f)||!f.every(E=>typeof E=="string"))?_(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):p===(f!==void 0)?_(e,"set_container_egress takes either allow (a list of destinations; [] for nothing outside the project) or clear: true (the public internet again), and exactly one of them.",!0):_(e,await n.setContainerEgress(c,p?null:f))}case"list_function_secrets":{let c=W(s,"ref");return c?_(e,await n.listFunctionSecrets(c)):_(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=W(s,"ref");if(!c)return _(e,"function_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.functionLogs(c,{name:W(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"list_tokens":return _(e,await n.listTokens());case"create_token":{let c=W(s,"name");if(!c)return _(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let p=W(s,"expires"),f=p?vm(p):void 0;return f===null?_(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${p}".`,!0):_(e,await n.createToken(c,f,W(s,"project")??void 0))}case"revoke_token":{let c=W(s,"id");return c?_(e,await n.revokeToken(c)):_(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return _(e,await n.pushMigrations(i,{dir:W(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?_(e,await n.setProjectState("delete",i)):_(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(c=>c.name===o)?n.callBorrowed?Bt(e,await n.callBorrowed(o,s)):_(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):qn(e,-32602,`unknown tool: ${o}`)}}catch(c){return _(e,c instanceof Error?c.message:String(c),!0)}}function Oi(e){let t=e.split(`
448
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Gn=["auth","storage","data-api","push"];function ot(e){return{error:e instanceof Error?e.message:String(e)}}async function yt(e){let[t,n,r,o]=await Promise.all([S("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),ot),S("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),ot),S("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),ot),S("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),ot)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Am}}var Am={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Ci(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Gn.includes(t))throw new g(`say which product: ${Gn.join(", ")}`);return t}async function ho(e,t,n){if(t==="auth"){let r=await S("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await S("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await S(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function ji(e){let t=await yt(e);h(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?$("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${H([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"not on this plan":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":""),n("realtime",t.realtime,"always on: signals, roster; table changes on Plus and Pro")])}
449
- `);let s=l=>!!(l&&typeof l=="object"&&"enabled"in l&&!l.enabled),a=["auth","storage","data-api","push"].find(l=>s(l==="data-api"?o:t[l]));a&&d(w(` snoutdata products enable ${a} --ref ${e}`))})}async function Ii(e,t,n){let r=await ho(e,t,n);h(r,()=>d(r.note))}async function Kn(e){let t=await S("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function yo(e,t,n){if(!n)throw new g(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await S("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Ni(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(`${H(n)}
450
- `)}async function Di(e,t,n){if(t===void 0||t==="list"){let o=await Kn(e);h(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${H([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":$("no"),s.certificate?.state??""])])}
451
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new g(`unknown command: domains ${t}`);let r=await yo(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&($n()||Ni(r.domain,e),U("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),h(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Ni(r.domain,e)),r.next&&d(r.next)})}async function wo(e){return S("cloud-project-restore",{ref:e},"GET")}async function bo(e,t,n){if(Number.isNaN(Date.parse(t)))throw new g(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return S("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Mi(e){let t=await wo(e);h(t,()=>{let n=t.restore,r=go(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(w(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function Li(e,t,n){let r=await bo(e,t,n);h(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${$(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function So(e){let{projects:t}=await _e(),n=t.find(l=>l.ref===e);if(!n)throw new g(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([yt(e),S("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),ot),S("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),ot),Kn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),ot)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ui(e){let t=await So(e);h(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${H([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (signals and the roster; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
452
- `)})}ee();pe();tt();ee();Q();import{existsSync as Rm,mkdirSync as Tm,readFileSync as Om,renameSync as Nm,writeFileSync as Cm}from"node:fs";import{homedir as _o}from"node:os";import{dirname as jm,join as Im}from"node:path";import{rootCertificates as Dm}from"node:tls";var Mm="SNOUTDATA_DB_SSLMODE";function Lm(e=_o()){let t=Im(e,".snoutdata","ca-roots.pem"),n=`${Dm.join(`
450
+ `:""))}async function Wa(e,t){for(let r of t){let o=Bt(r.name);if(o)throw new m(o)}let n=ro(e);for(let r of t)n.set(r.name,r.value);za(e,n),await Bn(e),h({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${x(r.name)} set in ${Kn(e)}`)})}function qn(e){return{ref:e.ref,local:e.folder,secrets:[...ro(e).keys()].sort().map(t=>({name:t}))}}function Ja(e){let t=qn(e),n=t.secrets.map(r=>r.name);h(t,()=>{d(n.length?n.join(`
451
+ `):`No function secrets in ${Kn(e)}.`)})}async function Va(e,t){let n=ro(e);if(!n.delete(t))throw new P("not-found",`${t} is not set for this local project`);za(e,n),await Bn(e),h({ref:e.ref,unset:t},()=>d(`${t} unset.`))}me();st();qe();Gr();vn();te();Dn();re();re();me();qe();te();async function Ya(){let{projects:e,allowance:t}=await $e();h({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${L(sm(e))}
452
+ `)})}function sm(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?x("read-only"):n.state==="ready"?n.state:x(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Fe(n.lastConnectionAt)])]}async function oo(e){let t=await _("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 Ft(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&ft(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;h({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
453
+ `),d(""),d(`Ready. ${x(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 Xa(e,t,n={}){let r=`cloud-project-${e}`,o=await _(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){h({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Ut(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new P("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});h({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Za(e){let t=await _("cloud-project-reset-password",{ref:e});h(t,()=>{process.stdout.write(`${t.password}
454
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Qa(){let{teams:e}=await Ln();h({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"])])}
455
+ `)})}async function ei(e){let{teams:t}=await Ln(),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}re();te();async function ti(e){let t=await _("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});h(t,()=>{process.stdout.write(`${t.token}
456
+ `),d(""),d(`${x(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 ni(){let{tokens:e}=await _("cloud-token-list");h({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?x(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Fe(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
457
+ `)})}async function ri(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await _("cloud-token-revoke",t);h(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}re();be();te();async function oi(e){let t=await Se(e);h({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${ai(t.appKey,t.serverKey)}
458
+ `),cm(t.keysIssuedAt)})}async function si(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 m("keys rotate needs --force, because this cannot be undone");let n=Mn(await _("cloud-project-rotate-jwt",{ref:e}));h(n,()=>{process.stdout.write(`${ai(n.appKey,n.serverKey)}
459
+ `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function ai(e,t){return L([["KEY","VALUE"],["app key",e],["server key",t]])}function cm(e){d(""),d(`The ${x("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${x("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}re();be();te();import{readdir as dm,readFile as um,stat as pm}from"node:fs/promises";import{join as ao,relative as mm,sep as fm}from"node:path";var ii=["functions"];async function so(e){try{return(await pm(e)).isDirectory()}catch{return!1}}async function gm(e,t){if(t){if(!await so(t))throw new m(`${t} is not a directory`);return t}for(let n of ii){let r=ao(n,e);if(await so(r))return r}throw new m(`no folder found for ${e} at ${ii[0]}/${e}; use --dir to say where it is.`)}async function ci(e){let t=[];async function n(r){for(let o of await dm(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=ao(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await um(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new m(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:mm(e,s).split(fm).join("/"),text:l})}}return await n(e),t}async function hm(e,t,n){let r=await ci(t);if(r.length===0)throw new m(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new m(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=ao(t,"..","_shared");if(!await so(s))return{entrypoint:o,files:r};let a=(await ci(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 io(e,t,n){it(t);let r=await gm(t,n.dir),{entrypoint:o,files:s}=await hm(t,r,n.entrypoint),a=Da({entrypoint:o,files:s});return{answer:await _("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function di(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await io(e,t,n),l=r.functions.find(i=>i.name===t);h(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${x(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d(x(" 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 co(e){return _("cloud-project-functions",{ref:e})}function lo(e,t){return it(t),_("cloud-project-functions",{ref:e,action:"delete",name:t})}function uo(e,t,n){if(it(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new m("functions size needs --memory MB, --concurrency N, or --reset");return _("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function ym(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 ui(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 pi(e,t,n){let r=await uo(e,t,n),o=r.functions.find(s=>s.name===t);h(r,()=>{let s=o?ym(o,r.limit):null;d(`${x(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=ui(r.limit);a&&d(w(` ${a}`))})}async function mi(e){let t=await co(e);h(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${L([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":x("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])])}
460
+ `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=ui(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function li(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"?x("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function fi(e,t,n){let r=a=>_("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(h({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${li(a)}
461
+ `)}),!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(`${li(l)}
462
+ `),s=l.at}}async function gi(e,t){let n=await lo(e,t);h(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}re();te();import{spawn as Im}from"node:child_process";import{createReadStream as Cm}from"node:fs";import{mkdtemp as jm,rm as Dm,stat as Gn}from"node:fs/promises";import{request as Mm}from"node:http";import{request as Lm}from"node:https";import{tmpdir as Um}from"node:os";import{basename as Fm,dirname as Bm,join as go}from"node:path";var bm=/^[a-z0-9][a-z0-9_-]{0,31}$/;function _m(e){return bm.test(e)}function yi(e){return _m(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 km=/^sha256:[0-9a-f]{64}$/,Sm=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,vm=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,$m=/^(?:[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 xm(e){return km.test(e)}function wi(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),!xm(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),!Sm.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()),$m.test(u)?((u==="index.docker.io"||u==="registry-1.docker.io")&&(u="docker.io"),u==="docker.io"&&i.length===1&&i.unshift("library"),i.length===0||i.some(p=>!vm.test(p))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var Y_=["application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json"].join(", ");function po(e){let t=e.split(".");if(t.length!==4)return null;let n=[];for(let r of t){if(!/^\d{1,3}$/.test(r))return null;let o=Number(r);if(o>255)return null;n.push(o)}return n}function Pm(e){let t=e.replace(/^\[|\]$/g,""),n=t.indexOf("%");if(n!==-1&&(t=t.slice(0,n)),!/^[0-9a-fA-F:.]+$/.test(t)||!t.includes(":"))return null;let r=t.lastIndexOf(":");if(t.slice(r+1).includes(".")){let c=po(t.slice(r+1));if(!c)return null;t=`${t.slice(0,r+1)}${(c[0]<<8|c[1]).toString(16)}:${(c[2]<<8|c[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=c=>c===""?[]:c.split(":"),a=s(o[0]??""),l=o.length===2?s(o[1]??""):[];if([...a,...l].some(c=>!/^[0-9a-fA-F]{1,4}$/.test(c)))return null;let i=a.length+l.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(c=>parseInt(c,16)),...u,...l.map(c=>parseInt(c,16))]}function Em(e,t,n){let r=po(t),o=a=>(a[0]<<24|a[1]<<16|a[2]<<8|a[3])>>>0,s=n===0?0:4294967295<<32-n>>>0;return(o(e)&s)===(o(r)&s)}var Am=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.88.99.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4]];var X_=1024**3;var hi=16,Rm=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,Tm=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function Om(e,t){let n=e??[],r={error:`${t}: ports are up to ${hi} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>hi)return r;let o=n.map(s=>typeof s=="string"&&/^\d{1,5}$/.test(s)?Number(s):s);return o.some(s=>typeof s!="number"||!Number.isInteger(s)||s<1||s>65535)?r:[...new Set(o)].sort((s,a)=>s-a)}function Nm(e){let t=e.trim().toLowerCase(),n=Tm.exec(t);if(n){let r=po(n[1]??""),o=n[2]===void 0?32:Number(n[2]);if(!r||o>32)return{error:`${e} is not an IPv4 address or range.`};let s=(r[0]<<24|r[1]<<16|r[2]<<8|r[3])>>>0,a=o===0?0:4294967295<<32-o>>>0,l=(s&a)>>>0,i=[24,16,8,0].map(u=>l>>>u&255);return Am.some(([u,c])=>o>=c&&Em(i,u,c))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return Pm(t)?{error:`${e} is an IPv6 address. Containers send over IPv4 only; list its IPv4 address or its host name.`}:t.startsWith("*.")?{error:`${e}: a wildcard cannot be looked up. List each host name, or the address range.`}:Rm.test(t)?t:/^[a-z0-9-]{1,63}$/.test(t)?{error:`${e} is not a whole host name. Give it with its domain, like api.example.com.`}:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function bi(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=r?t.slice(0,n):t,s=Om(r?t.slice(n+1).split(",").map(l=>l.trim()):[],o.trim());if("error"in s)return s;let a=Nm(o);return typeof a!="string"?a:{destination:a,ports:s}}function mo(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var Hm=new Set(["pending","pulling","starting","restarting"]),Km=5*6e4;function Jn(e){let t=yi(e);if(t)throw new Error(t)}function ho(e,t,n){Jn(t);let r=wi(n.image);if("error"in r)throw new Error(r.error);return _("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 kt(e){return _("cloud-project-containers",{ref:e})}function yo(e,t){return Jn(t),_("cloud-project-containers",{ref:e,action:"delete",name:t})}function wo(e,t){return Jn(t),_("cloud-project-containers",{ref:e,action:"restart",name:t})}function zn(e,t){return _("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 vi(e,t){let n=Date.now()+Km,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await kt(e)).containers.find(o=>o.name===t)??null,!r||!Hm.has(r.status))return r;return r}function bo(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 $i(e,t,n){await xi(e,t,await ho(e,t,n),n.wait!==!1)}async function xi(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 vi(e,t)??o),h({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${x(t)} deployed.`);return}d(`${x(t)} ${bo(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(x(" 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 qm=2*1024**3,Gm=3*6e4;async function zm(e){let t;try{t=await Gn(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 Gn(go(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Bm(e),file:e}}function Wm(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function _o(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 Jm(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?_o(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 Vm(e){return new Promise((t,n)=>{let r=Im("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Ym(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?Mm:Lm)(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=Cm(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var Pi={docker:Vm,scratch:()=>jm(go(Um(),"snoutdata-build-")),remove:e=>Dm(e,{recursive:!0,force:!0}),size:async e=>(await Gn(e)).size,upload:async(e,t)=>Ym(e,t,(await Gn(t)).size),ask:e=>_("cloud-project-containers",e),say:e=>d(w(e)),now:()=>Date.now()};function _i(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function ki(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 Xm(e,t,n,r=Pi){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 ki(r,e,t),s=await r.scratch();try{let a=go(s,"image.tar");if(r.say(`${t}: building ${n.file?Fm(n.file):n.context} for linux/arm64`),await r.docker(Wm({...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>qm)throw new Error(`the image is ${_i(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()>Gm||(o=await ki(r,e,t)),r.say(`${t}: uploading the image (${_i(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(Jm(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(_o(o instanceof Error?o.message:String(o)))}}async function Ei(e,t,n,r=Pi){Jn(t);let o=await zm(n.path),s=await Xm(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(_o(l instanceof Error?l.message:String(l)))}await xi(e,t,a,n.wait!==!1)}async function Ai(e){let t=await kt(e);h(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(w(" Containers run on the Plus and Pro plans.")):d(w(" snoutdata containers deploy <name> <image>"));return}d(L([["name","status","image","url"],...t.containers.map(r=>[r.name,bo(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: ${fo(n.usedBytes)} of ${fo(n.allowanceBytes)}. At ${fo(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Wn(e,t){if(t===null)return _("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=bi(r);if("error"in o)throw new Error(o.error);n.push(o)}return _("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Zm(e){if(e.allowlist===void 0)return["This control plane does not know about allowlists yet."];if(e.allowlist===null)return[`Containers on ${e.ref} may reach the public internet (within the limits on how much and how fast).`,w(" snoutdata containers egress set <host[:port]> ... to allow only some destinations")];let t=[e.allowlist.allow.length===0?`Containers on ${e.ref} may reach nothing outside the project.`:`Containers on ${e.ref} may reach only:`];for(let n of e.allowlist.allow)t.push(` ${mo(n)}${n.ports.length===0?w(" (any port)"):""}`);return t.push(w(` and always: ${e.allowlist.alwaysAllowed.map(mo).join(", ")}, and each other`)),t.push(w(" snoutdata containers egress clear to allow the public internet again")),t}async function Vn(e,t){let n=t.clear?await Wn(e,null):t.set?await Wn(e,t.set):await kt(e);h({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Zm(n))d(r);(t.set||t.clear)&&d(w(" Running containers follow it within a minute; nothing restarts."))})}function fo(e){return`${Math.round(e/1024**3*10)/10} GB`}async function Ri(e,t){await wo(e,t);let n=await vi(e,t);h({ref:e,containers:n?[n]:[]},()=>{d(n?`${x(t)} ${bo(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Ti(e,t){let n=await yo(e,t);h(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function Si(e){return`${w(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?x(e.message):e.message}`}async function Oi(e,t,n){let r=((await zn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(h({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${Si(s)}
463
+ `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await zn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${Si(a)}
464
+ `),o=a.at}}re();be();te();function Ii(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function St(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Ci(e,t){if(e.length===0)throw new m("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new m("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await St()}]}return e.map(n=>{let r=Ii(n);if(!r)throw new m(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function ji(e,t,n){if(t.length===0)throw new m("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new m("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await St()})}else for(let s of t){let a=Ii(s);if(!a)throw new m(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Bt(s.name);if(a)throw new m(a)}let o=null;for(let s of r)o=await _("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});h(o,()=>{for(let s of r)d(`${x(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 Di(e,t){let n=Bt(t);if(n)throw new m(n);let r=await _("cloud-project-secrets",{ref:e,action:"unset",name:t});h(r,()=>{d(`${t} is gone.`)})}async function Mi(e){let t=await _("cloud-project-secrets",{ref:e});h(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${L([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Fe(n.updatedAt)])])}
465
+ `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Xt}from"node:child_process";import{appendFileSync as ac,existsSync as nr,readFileSync as ic,statSync as Uf,unlinkSync as Ff,writeFileSync as $t}from"node:fs";import{dirname as Bf,join as rr}from"node:path";import{tmpdir as Hf}from"node:os";import{open as cc}from"node:fs/promises";function Li(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 Ui(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 Fi(e){let t=e.split(`
466
+ `).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 ko={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},ef=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Yn=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"]),tf=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,nf={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},rf=new Set(["push.settings"]),of=["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 sf(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=of.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 af(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 Bi(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
467
+ `)){let u=sf(i);if(!u)continue;let{type:c,schema:p,name:f,owner:S}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let V=/^(schema_migrations|migrations)$/.test(f)&&Yn.has(p);if(/realtime/.test(p)||p==="snout_live"||V||p==="public"&&f==="spatial_ref_sys"){s++;continue}if(p==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(p==="push"&&f==="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 C=nf[p];if(C){C==="storage"&&(f==="objects"||f==="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(C,[...n.get(C)??[],u]);continue}if(Yn.has(p)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&p==="-"&&/^EXTENSION( |$)/.test(f)||(c==="SCHEMA"?Yn.has(f):Yn.has(p))||tf.test(f)||ef.test(S)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=af(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(f=>f.type==="TABLE DATA"&&rf.has(`${f.schema}.${f.name}`)).map(f=>`${f.schema}.${f.name}`),p=u.filter(f=>f.type==="TABLE DATA").map(f=>`${f.schema}.${f.name}`);return{product:i,clear:c,tables:p,lines:cf(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function cf(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}re();be();me();te();re();me();var vo=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],Ki=["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 Kt(e){return e==="done"||e==="failed"||e==="cancelled"}function qt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new P("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new je(mf(e.project))}let n=e.admin??Hi(t.LEPIS_ADMIN_URL);if(!n)throw new P("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??Hi(t.LEPIS_ADMIN_TOKEN);if(!r)throw new P("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new So(lf(n),r)}function Hi(e){return e===void 0||e===""?void 0:e}function lf(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new P("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new P("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new P("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!df(n.hostname))throw new P("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 df(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var uf=6e4,So=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(uf)})}catch(i){let u=wn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new P(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 P("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw qi(s.status,l);return l}},pf={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function qi(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?pf[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":Le(e));return s==="not-signed-in"?new P(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new P(s,r,o)}function mf(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new P("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var ff={"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"},gf="cloud-project-shards",je=class{target;ref;#t;constructor(t,n=_){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:Ye(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=ff[t.op];if(!n)throw new P("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 P("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 P("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(gf,{ref:this.ref,...t})}catch(n){throw hf(n)}}};function Ye(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 hf(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return qi(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":Le(e.status);return new P(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function zt(e){if(Array.isArray(e))return`[${e.map(zt).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)}:${zt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Po=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
468
+ ${zt(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
469
+ ${zt(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
470
+ ${zt(t)}`)}},yf=new Po,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"],wf="2024-11-05";function K(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var I={type:"string"},Gi={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},Xn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},Xe={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)."},Jt=[{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:K({})},{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:K({})},{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:K({name:I,region:I,teamId:I},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:K({ref:I},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:K({ref:I},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:K({ref:I},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs nothing installed: the CLI connects to the database itself. A file holding a psql command (\\i, \\set), or a COMMIT, END or ROLLBACK that would end the transaction the file runs in, is refused before anything runs; a file starting with -- snoutdata:no-transaction runs as written, outside a transaction.",inputSchema:K({ref:I,dir:I,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:K({ref:I,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:K({ref:I},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:K({ref:I},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING usually within fifteen seconds (longer while the host is busy starting other databases), including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about fifteen seconds before connecting, and retry a refusal for up to two minutes). Use it when a credential has leaked, not routinely.",inputSchema:K({ref:I},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:K({})},{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:K({})},{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:K({name:I,expires:I,project:I},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:K({id:I},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/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:K({ref:I,name:I,dir:I,entrypoint:I,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:K({ref:I},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:K({ref:I,name:I,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:K({ref:I,name:I},["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:K({ref:I},["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:K({ref:I,name:I,image:I,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:K({ref:I,name:I},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:K({ref:I,name:I},["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:K({ref:I,name:I,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"container_egress",description:"Where a project's containers may send. allowlist null means the public internet (within the limits on how much and how fast); otherwise allowlist.allow is the only destinations they reach, and alwaysAllowed what they reach whatever the list says (the project's own database and API). The same as `snoutdata containers egress`. Costs nothing.",inputSchema:K({ref:I},["ref"])},{name:"set_container_egress",description:'Hold a project\'s containers to an allowlist, or let them reach the public internet again. Give EITHER allow, a list of destinations ("api.example.com:443", "api.example.com" for any port, "140.82.112.0/20:443,8443"; an empty list means nothing outside the project), OR clear: true. The list replaces the one there. Running containers follow it within a minute and nothing restarts, so a container that needs a destination left off stops reaching it. The same as `snoutdata containers egress set|clear`.',inputSchema:K({ref:I,allow:{type:"array",items:I},clear:{type:"boolean"}},["ref"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:K({ref:I},["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:K({ref:I,name:I,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:K({ref:I},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:K({ref:I},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. All three are on every plan, including free.",inputSchema:K({ref:I,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:K({ref:I},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:K({ref:I,hostname:I},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:K({ref:I,hostname:I},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:K({ref:I,hostname:I},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:K({ref:I},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:K({ref:I,at:I,name:I},["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:K({project:Xe})},{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:K({project:Xe,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:K({project:Xe,operation:Gi},["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:K({project:Xe,operation:Gi,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:K({project:Xe,id:Xn})},{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:K({project:Xe,id:Xn,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:K({project:Xe,id:Xn},["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:K({project:Xe,id:Xn},["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:K({ref:I},["ref"])}],bf=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","container_egress","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),_f=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),zi=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Ao(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Ao)};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 Ro(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 kf(e){return{...e,annotations:bf.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:_f.has(e.name)}}}function Sf(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Wt(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Zn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function $(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Wt(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function vf(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Gt(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 $o(e){return{id:e.id,op:e.op,state:e.state,finished:Kt(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 Wi(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 xo(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 $f(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var xf="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 Pf(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,xf,!0);s=r.shards(a)}catch(u){return $(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??yf,i=a??"";try{switch(t){case"cluster_status":return $(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let p of c.advice)p.plan&&l.remember(p.request,i);return $(e,{summary:c.summary,advice:c.advice.map(p=>({op:p.op,reason:p.reason,metric:p.metric,operation:p.request,...p.plan?{plan:Gt(p.plan)}:{},...p.needs.length>0?{needs:p.needs,note:`Ask the user for ${p.needs.join(", ")}, then plan it with cluster_plan.`}:{},...p.plan_error?{plan_error:p.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=Wi(n,t);if(typeof u=="string")return $(e,u,!0);let c=await s.plan(u);return l.remember(u,i),$(e,{plan:Gt(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=Wi(n,t);if(typeof u=="string")return $(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let p=await s.plan(u);l.remember(u,i);let f=n.confirm===!0;return $(e,{ran:!1,plan:Gt(p),next:f?"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."},f)}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:Gt(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=xo(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,{...$o(c),plan:c.plan&&Array.isArray(c.plan.steps)?Gt(c.plan):null})}case"cluster_watch_job":{let u=xo(n);if(u===null)return $(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,p=Date.now()+c*1e3;for(;;){let f=await s.job(u);if(Kt(f.state))return $(e,$o(f),f.state!=="done");if(Date.now()>=p)return $(e,{...$o(f),note:`Still ${f.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(S=>setTimeout(S,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=xo(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 Zn(e,-32602,`unknown tool: ${t}`)}}catch(u){return $(e,$f(u),!0)}}function J(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ji(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 Wt(r,{protocolVersion:wf,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Wt(r,{});case"tools/list":return Wt(r,{tools:Sf([...Jt.map(kf),...n.borrowed??[]])});case"tools/call":return Ef(r,s,t,n);default:return Zn(r,-32601,`unknown method: ${o}`)}}async function Ef(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Jt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return Zn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"];if(o.startsWith("cluster_")&&Jt.some(c=>c.name===o))return Pf(e,o,s,n,r);let i=J(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&&!zi.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: ${zi.join(", ")}.`,!0);try{switch(o){case"whoami":return $(e,await n.whoami());case"list_projects":return $(e,Ao(await n.listProjects()));case"create_project":{let c=J(s,"name");return c?$(e,await n.createProject({name:c,region:J(s,"region")??void 0,teamId:J(s,"teamId")??void 0})):$(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return $(e,{url:c.uri,state:c.state,wakesInstantly:c.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return $(e,await n.setProjectState("pause",i));case"resume_project":return $(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return $(e,await n.usage(i,c))}case"export_status":return $(e,await n.exportStatus(i));case"start_export":return $(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),p=await n.connection(i);return $(e,{url:p.uri,appliesIn:c.appliesIn??"within about fifteen seconds",next:"Wait about fifteen seconds before connecting with this URL, and retry a refusal for up to two minutes: until the change applies (usually 5 to 15 seconds, longer while the host is busy) the new password is refused and the old one still works, which does not mean the new one is wrong. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return $(e,await n.listTeams());case"get_project":return $(e,Ao(await n.getProject(i)));case"list_products":return $(e,await n.getProducts(i));case"set_product":{let c=J(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=J(s,"hostname");return c?$(e,await n.domainAction(i,o.split("_")[0],c)):$(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),p=c&&c.restore?Ro(i,c.restore):null;return $(e,p?{...c,reason:p}:c)}case"restore_to_point":{let c=J(s,"at");return c?$(e,await n.restoreTo(i,c,J(s,"name")??void 0)):$(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=J(s,"ref"),p=J(s,"name");return!c||!p?$(e,"deploy_function needs a project ref and a function name.",!0):$(e,await n.deployFunction(c,p,{dir:J(s,"dir")??void 0,entrypoint:J(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=J(s,"ref");return c?$(e,await n.listFunctions(c)):$(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=J(s,"ref"),p=J(s,"name");if(!c||!p)return $(e,"size_function needs a project ref and a function name.",!0);let f=typeof s.memoryMb=="number"?s.memoryMb:void 0,S=typeof s.concurrency=="number"?s.concurrency:void 0,v=s.reset===!0;return!v&&f===void 0&&S===void 0?$(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):$(e,await n.sizeFunction(c,p,{memoryMb:f,concurrency:S,reset:v}))}case"delete_function":{let c=J(s,"ref"),p=J(s,"name");return!c||!p?$(e,"delete_function needs a project ref and a function name.",!0):$(e,await n.deleteFunction(c,p))}case"list_containers":{let c=J(s,"ref");return c?$(e,await n.listContainers(c)):$(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=J(s,"ref"),p=J(s,"name"),f=J(s,"image");if(!c||!p||!f)return $(e,"deploy_container needs a project ref, a container name and an image.",!0);let S=typeof s.port=="number"?s.port:void 0;return $(e,await n.deployContainer(c,p,{image:f,...S===void 0?{}:{port:S}}))}case"restart_container":case"delete_container":{let c=J(s,"ref"),p=J(s,"name");return!c||!p?$(e,`${o} needs a project ref and a container name.`,!0):$(e,o==="restart_container"?await n.restartContainer(c,p):await n.deleteContainer(c,p))}case"container_logs":{let c=J(s,"ref");if(!c)return $(e,"container_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return $(e,await n.containerLogs(c,{name:J(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"container_egress":{let c=J(s,"ref");return c?$(e,await n.containerEgress(c)):$(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let c=J(s,"ref");if(!c)return $(e,"set_container_egress needs a project ref.",!0);let p=s.clear===!0,f=s.allow;return f!==void 0&&(!Array.isArray(f)||!f.every(S=>typeof S=="string"))?$(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):p===(f!==void 0)?$(e,"set_container_egress takes either allow (a list of destinations; [] for nothing outside the project) or clear: true (the public internet again), and exactly one of them.",!0):$(e,await n.setContainerEgress(c,p?null:f))}case"list_function_secrets":{let c=J(s,"ref");return c?$(e,await n.listFunctionSecrets(c)):$(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=J(s,"ref");if(!c)return $(e,"function_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return $(e,await n.functionLogs(c,{name:J(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"list_tokens":return $(e,await n.listTokens());case"create_token":{let c=J(s,"name");if(!c)return $(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let p=J(s,"expires"),f=p?vf(p):void 0;return f===null?$(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${p}".`,!0):$(e,await n.createToken(c,f,J(s,"project")??void 0))}case"revoke_token":{let c=J(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:J(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?Wt(e,await n.callBorrowed(o,s)):$(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Zn(e,-32602,`unknown tool: ${o}`)}}catch(c){return $(e,c instanceof Error?c.message:String(c),!0)}}function Vi(e){let t=e.split(`
471
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Qn=["auth","storage","data-api","push"];function ct(e){return{error:e instanceof Error?e.message:String(e)}}async function vt(e){let[t,n,r,o]=await Promise.all([_("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),ct),_("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}),ct),_("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),ct),_("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),ct)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Af}}var Af={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Xi(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Qn.includes(t))throw new m(`say which product: ${Qn.join(", ")}`);return t}async function To(e,t,n){if(t==="auth"){let r=await _("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await _("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await _(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function Zi(e){let t=await vt(e);h(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?x("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")])}
472
+ `);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 Qi(e,t,n){let r=await To(e,t,n);h(r,()=>d(r.note))}async function er(e){let t=await _("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Oo(e,t,n){if(!n)throw new m(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await _("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Yi(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)}
473
+ `)}async function ec(e,t,n){if(t===void 0||t==="list"){let o=await er(e);h(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":x("no"),s.certificate?.state??""])])}
474
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new m(`unknown command: domains ${t}`);let r=await Oo(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(In()||Yi(r.domain,e),H("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),h(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Yi(r.domain,e)),r.next&&d(r.next)})}async function No(e){return _("cloud-project-restore",{ref:e},"GET")}async function Io(e,t,n){if(Number.isNaN(Date.parse(t)))throw new m(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return _("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function tc(e){let t=await No(e);h(t,()=>{let n=t.restore,r=Ro(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 nc(e,t,n){let r=await Io(e,t,n);h(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${x(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function Co(e){let{projects:t}=await $e(),n=t.find(l=>l.ref===e);if(!n)throw new m(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([vt(e),_("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),ct),_("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),ct),er(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),ct)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function rc(e){let t=await Co(e);h(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${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)]])}
475
+ `)})}re();me();st();re();te();import{existsSync as Rf,mkdirSync as Tf,readFileSync as Of,renameSync as Nf,writeFileSync as If}from"node:fs";import{homedir as jo}from"node:os";import{dirname as Cf,join as jf}from"node:path";import{rootCertificates as Df}from"node:tls";var Mf="SNOUTDATA_DB_SSLMODE";function Lf(e=jo()){let t=jf(e,".snoutdata","ca-roots.pem"),n=`${Df.join(`
453
476
  `)}
454
- `,r=null;try{r=Rm(t)?Om(t,"utf8"):null}catch{r=null}if(r!==n){Tm(jm(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Cm(o,n,{mode:420}),Nm(o,t)}return t}function qt(e,t=process.env,n=_o()){if(e==="disable")return{mode:"disable"};let r=t[Mm]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Lm(n)}}function Gt(e,t=process.env,n=_o()){let{mode:r,rootCert:o}=qt(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Ki(e){try{let{projects:t}=await _e(),n=t.find(r=>r.ref===e);n?.readOnly?te(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&te(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function vo(e){await Ki(e);let t=await we(e);h(t,()=>{process.stdout.write(`${t.uri}
455
- `)})}async function Ji(e,t){if(t.length===0&&!me()){let o=pt();U("usage",`db psql with no statements opens an interactive session, and ${o?ut(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Ki(e);let n=await we(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Kt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Gt(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 zi(e,t,n="DATABASE_URL"){let r=Wn(e,".env"),o=`${n}=${t}`,s=qm(e);if(!zn(r))return wt(r,`${o}
456
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Um(r).mode&63&&te(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=qi(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(wt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Hi(r,a.endsWith(`
477
+ `,r=null;try{r=Rf(t)?Of(t,"utf8"):null}catch{r=null}if(r!==n){Tf(Cf(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;If(o,n,{mode:420}),Nf(o,t)}return t}function Vt(e,t=process.env,n=jo()){if(e==="disable")return{mode:"disable"};let r=t[Mf]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Lf(n)}}function Yt(e,t=process.env,n=jo()){let{mode:r,rootCert:o}=Vt(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function lc(e){try{let{projects:t}=await $e(),n=t.find(r=>r.ref===e);n?.readOnly?Q(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&Q(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Mo(e){await lc(e);let t=await Se(e);h(t,()=>{process.stdout.write(`${t.uri}
478
+ `)})}async function dc(e,t){if(t.length===0&&!ye()){let o=yt();H("usage",`db psql with no statements opens an interactive session, and ${o?ht(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await lc(e);let n=await Se(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Xt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Yt(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new P("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new P("failed",String(a)))}),r.on("close",a=>o(a??0))})}function uc(e,t,n="DATABASE_URL"){let r=rr(e,".env"),o=`${n}=${t}`,s=Kf(e);if(!nr(r))return $t(r,`${o}
479
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Uf(r).mode&63&&Q(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=ic(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?($t(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(ac(r,a.endsWith(`
457
480
  `)?`${o}
458
481
  `:`
459
482
  ${o}
460
- `),{path:r,replaced:!1,ignored:s})}function qm(e){let t=e;for(;!zn(Wn(t,".git"));){let s=Bm(t);if(s===t)return!1;t=s}let n=Wn(e,".gitignore"),r=zn(n)?qi(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Hi(n,r===""||r.endsWith(`
483
+ `),{path:r,replaced:!1,ignored:s})}function Kf(e){let t=e;for(;!nr(rr(t,".git"));){let s=Bf(t);if(s===t)return!1;t=s}let n=rr(e,".gitignore"),r=nr(n)?ic(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(ac(n,r===""||r.endsWith(`
461
484
  `)?`.env
462
485
  `:`
463
486
  .env
464
- `),!0)}async function Wi(e,t={}){if(t.statusOnly){let{export:a}=await ft(e);return h({ref:e,export:a},()=>ko(a)),0}let n=await Nt(e);if(!n.export.pending&&n.export.url&&!t.out)return h({ref:e,export:n.export},()=>ko(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Km(e,t.timeoutMs);if(r.error&&U("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return h({ref:e,export:r},()=>ko(r)),0;r.url||U("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Jm(r.url,t.out),s=r.rolesSql?Gm(t.out):null;return s&&r.rolesSql&&wt(s,r.rolesSql.endsWith(`
487
+ `),!0)}async function pc(e,t={}){if(t.statusOnly){let{export:a}=await bt(e);return h({ref:e,export:a},()=>Do(a)),0}let n=await Lt(e);if(!n.export.pending&&n.export.url&&!t.out)return h({ref:e,export:n.export},()=>Do(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Gf(e,t.timeoutMs);if(r.error&&H("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return h({ref:e,export:r},()=>Do(r)),0;r.url||H("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await zf(r.url,t.out),s=r.rolesSql?qf(t.out):null;return s&&r.rolesSql&&$t(s,r.rolesSql.endsWith(`
465
488
  `)?r.rolesSql:`${r.rolesSql}
466
- `,"utf8"),h({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${Ue(o)} to ${t.out}.`),d(w(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(w("Into any other Postgres, in this order:")),d(w(` psql --dbname <your database> -f ${s}`)),d(w(` pg_restore --dbname <your database> ${t.out}`)),d(w("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(w("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(w("Owners and grants are skipped: this export carries no roles file.")))}),0}function Gm(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Km(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await ft(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function Jm(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 Gi(t,"w"),o=0;try{for await(let s of n.body)await r.write(s),o+=s.byteLength}finally{await r.close()}return o}function ko(e){if(e.error){te(`The last export did not finish: ${e.error}`);return}if(e.pending){d("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){d("This project has never been exported.");return}d(`A copy taken ${Me(e.completedAt)}, ${Ue(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
467
- `),e.urlExpiresAt&&d(w(`That link stops working ${Me(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function Ue(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function Vi(e,t){let n=t.file;n||U("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),zn(n)||U("usage",`no file at ${n}`,{file:n});let r=await Gi(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Si(o.slice(0,s.bytesRead)),l=await we(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await _e(),u=i.find(X=>X.ref===e),c=await zm(l);c===null&&U("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let p=_i(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});p.ok||U("conflict",p.reason,{ref:e,file:n,refusal:p.code});let f=null;if(p.tool==="pg_restore"){let X=await Bi("pg_restore",["--list",n]),L=await Bi("pg_restore",["--section=post-data","--file=-",n]);if((X.code!==0||L.code!==0)&&U("failed",`pg_restore could not read ${n}: ${(X.stderr||L.stderr).trim().slice(0,300)}`,{ref:e,file:n}),f=vi(X.stdout,L.stdout),f.products.length>0){let de=await yt(e),I=P=>!!(P&&typeof P=="object"&&"enabled"in P&&P.enabled),Z=f.products.filter(P=>!I(de[P]));Z.length>0&&U("conflict",`this dump holds ${Z.length>1?`${Z.slice(0,-1).join(", ")} and ${Z[Z.length-1]}`:Z[0]} data, and ${e} has ${Z.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${Z.length===1?"it":"them"} on, wait a minute, and run this again:
468
- ${Z.map(P=>` snoutdata products enable ${P} --ref ${e}`).join(`
469
- `)}`,{ref:e,file:n,refusal:"products-off",products:Z})}}if(f&&f.platformData.length>0){let X=f.platformData.flatMap(({tables:I})=>I),L=await Wm(l,X);L===null&&U("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let de=f.platformData.filter(({tables:I})=>I.some(Z=>L.includes(Z))).map(({product:I})=>I);de.length>0&&U("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 ${p.tool} (${p.format} format).`);let E=f?await Vm(l,n,f):await Jn(l,p.tool,n),k=ki(E.stderr);for(let X of f?.notes??[])d(w(X));return E.code!==0&&k.fatal.length===0&&k.total>0?(h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format,ignoredErrors:k.harmless},()=>{d(`Restored into ${e}.`),d(`${k.harmless} error${k.harmless===1?"":"s"} above ${k.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(E.code!==0&&U("failed",`${p.tool} exited ${E.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:k.fatal}),h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format},()=>d(`Restored into ${e}.`)),0)}async function zm(e){return new Promise(t=>{let n=Kt("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,...Gt(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{te(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 Wm(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Kt("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,...Gt(e.ssl)}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
470
- `).map(l=>l.trim()).filter(Boolean):null))})}function Bi(e,t){return new Promise(n=>{let r=Kt(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=>{te(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 Vm(e,t,n){let r=Wn(Hm(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){wt(`${r}.sql`,`set role ${ao[l]};
489
+ `,"utf8"),h({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${He(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 qf(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Gf(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await bt(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 zf(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 cc(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 Do(e){if(e.error){Q(`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 ${Fe(e.completedAt)}, ${He(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
490
+ `),e.urlExpiresAt&&d(w(`That link stops working ${Fe(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function He(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 mc(e,t){let n=t.file;n||H("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),nr(n)||H("usage",`no file at ${n}`,{file:n});let r=await cc(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Li(o.slice(0,s.bytesRead)),l=await Se(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await $e(),u=i.find(V=>V.ref===e),c=await Wf(l);c===null&&H("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let p=Ui(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});p.ok||H("conflict",p.reason,{ref:e,file:n,refusal:p.code});let f=null;if(p.tool==="pg_restore"){let V=await sc("pg_restore",["--list",n]),C=await sc("pg_restore",["--section=post-data","--file=-",n]);if((V.code!==0||C.code!==0)&&H("failed",`pg_restore could not read ${n}: ${(V.stderr||C.stderr).trim().slice(0,300)}`,{ref:e,file:n}),f=Bi(V.stdout,C.stdout),f.products.length>0){let Y=await vt(e),R=O=>!!(O&&typeof O=="object"&&"enabled"in O&&O.enabled),ee=f.products.filter(O=>!R(Y[O]));ee.length>0&&H("conflict",`this dump holds ${ee.length>1?`${ee.slice(0,-1).join(", ")} and ${ee[ee.length-1]}`:ee[0]} data, and ${e} has ${ee.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ee.length===1?"it":"them"} on, wait a minute, and run this again:
491
+ ${ee.map(O=>` snoutdata products enable ${O} --ref ${e}`).join(`
492
+ `)}`,{ref:e,file:n,refusal:"products-off",products:ee})}}if(f&&f.platformData.length>0){let V=f.platformData.flatMap(({tables:R})=>R),C=await Jf(l,V);C===null&&H("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let Y=f.platformData.filter(({tables:R})=>R.some(ee=>C.includes(ee))).map(({product:R})=>R);Y.length>0&&H("conflict",`${Y.join(", ")} on ${e} ${Y.length===1?"has":"have"} not finished setting up ${Y.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:Y})}d(`Restoring ${n} into ${e} with ${p.tool} (${p.format} format).`);let S=f?await Vf(l,n,f):await tr(l,p.tool,n),v=Fi(S.stderr);for(let V of f?.notes??[])d(w(V));return S.code!==0&&v.fatal.length===0&&v.total>0?(h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format,ignoredErrors:v.harmless},()=>{d(`Restored into ${e}.`),d(`${v.harmless} error${v.harmless===1?"":"s"} above ${v.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):(S.code!==0&&H("failed",`${p.tool} exited ${S.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:v.fatal}),h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format},()=>d(`Restored into ${e}.`)),0)}async function Wf(e){return new Promise(t=>{let n=Xt("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,...Yt(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{Q(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 Jf(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Xt("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,...Yt(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(`
493
+ `).map(l=>l.trim()).filter(Boolean):null))})}function sc(e,t){return new Promise(n=>{let r=Xt(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=>{Q(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 Vf(e,t,n){let r=rr(Hf(),`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){$t(`${r}.sql`,`set role ${ko[l]};
471
494
  ${i.map(f=>`delete from ${f};`).join(`
472
495
  `)}
473
- `,"utf8");let p=await Jn(e,"psql",`${r}.sql`,["--quiet"]);o=o||p.code,s+=p.stderr}wt(`${r}.platform`,`${u.join(`
496
+ `,"utf8");let p=await tr(e,"psql",`${r}.sql`,["--quiet"]);o=o||p.code,s+=p.stderr}$t(`${r}.platform`,`${u.join(`
474
497
  `)}
475
- `,"utf8");let c=await Jn(e,"pg_restore",t,["--data-only",`--role=${ao[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}wt(`${r}.rest`,`${n.rest.join(`
498
+ `,"utf8");let c=await tr(e,"pg_restore",t,["--data-only",`--role=${ko[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}$t(`${r}.rest`,`${n.rest.join(`
476
499
  `)}
477
- `,"utf8");let a=await Jn(e,"pg_restore",t,[`--use-list=${r}.rest`]);return{code:o||a.code,stderr:s+a.stderr}}finally{for(let a of[".platform",".rest",".sql"])try{Fm(`${r}${a}`)}catch{}}}function Jn(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=Kt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Gt(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{te(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}))})}ee();he();Q();function Xm(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new g(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new g("db access list takes no argument");if(t!==void 0)throw new g("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new g("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new g(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new g("db access revoke needs an email address or a role name");if(t!==void 0)throw new g("--level is for db access grant");return{action:"revoke",who:r};default:throw new g(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Yi(e,t,n){let r=Xm(t,n.level);if(r.action==="list"){let s=await S("cloud-project-db-access",{ref:e,action:"list"});h(s,()=>Zm(s));return}if(r.action==="grant"){let s=await S("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});h(s,()=>Qm(s));return}let o=await S("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});h(o,()=>tf(o))}function Zm(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(`${H([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?$(n.email):n.email,n.level,n.role])])}
478
- `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(w(` psql "${t.connection.keywords}"`)))}function Qm(e){e.connection&&process.stdout.write(`${e.connection.keywords}
479
- `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${$(e.level)} access to ${e.ref}'s database, as ${e.role}${e.means?`: ${e.means}`:""}.`),e.outcome!=="unchanged"&&d(e.appliesIn),d("They sign in with psql 18 (libpq 18 with OAuth support) and approve the code it prints in a browser:"),e.connection&&d(w(` psql "${e.connection.keywords}"`))}function ef(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function tf(e){let t=ef(e);for(let n of t.slice(0,-1))d(n);te(t[t.length-1])}ee();he();Q();var nf=6e4;async function $o(e,t={}){return S("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function rf(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new g(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function of(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Xi(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 sf={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 Zi(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[of(e.at),sf[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function af(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function cf(e){let t=e.inspect;if(d($(`Realtime for ${e.ref}`)),d(`${t.connections} connection${t.connections===1?"":"s"}, ${t.connected_users} on a channel. The project's limit is ${t.limits.max_events_per_second} messages a second, counted once per message sent, however many receive it.`),t.channels.length===0){d("No channels are open.");return}for(let n of t.channels){let r=n.messages;if(d(""),d($(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(w(` last ${r.window_seconds}s: ${r.broadcasts_received} signal${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} roster update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,Xi(Date.now()-a.joined_at),Xi(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>nf?" (silent: likely gone)":"")]);d(H([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
500
+ `,"utf8");let a=await tr(e,"pg_restore",t,[`--use-list=${r}.rest`]);return{code:o||a.code,stderr:s+a.stderr}}finally{for(let a of[".platform",".rest",".sql"])try{Ff(`${r}${a}`)}catch{}}}function tr(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=Xt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Yt(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{Q(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}))})}re();be();te();function Xf(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new m(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new m("db access list takes no argument");if(t!==void 0)throw new m("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new m("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 m(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new m("db access revoke needs an email address or a role name");if(t!==void 0)throw new m("--level is for db access grant");return{action:"revoke",who:r};default:throw new m(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function fc(e,t,n){let r=Xf(t,n.level);if(r.action==="list"){let s=await _("cloud-project-db-access",{ref:e,action:"list"});h(s,()=>Zf(s));return}if(r.action==="grant"){let s=await _("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});h(s,()=>Qf(s));return}let o=await _("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});h(o,()=>tg(o))}function Zf(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?x(n.email):n.email,n.level,n.role])])}
501
+ `);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 Qf(e){e.connection&&process.stdout.write(`${e.connection.keywords}
502
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${x(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 eg(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function tg(e){let t=eg(e);for(let n of t.slice(0,-1))d(n);Q(t[t.length-1])}re();be();te();var ng=6e4;async function Lo(e,t={}){return _("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function rg(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 m(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function og(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function gc(e){if(e===null)return"-";let t=Math.round(e/1e3);if(t<60)return`${t}s ago`;let n=Math.round(t/60);return n<60?`${n}m ago`:`${Math.round(n/60)}h ago`}var sg={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 hc(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[og(e.at),sg[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function ag(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function ig(e){let t=e.inspect;if(d(x(`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(x(`${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,gc(Date.now()-a.joined_at),gc(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>ng?" (silent: likely gone)":"")]);d(L([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
480
503
  `).map(a=>` ${a}`).join(`
481
- `))}let o=Object.keys(n.presence);if(o.length>0){d(" roster:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${af(a)}`)}}}async function Qi(e,t={}){if(t.watch&&$n())throw new g("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await $o(e,{channel:t.channel});if(h(n.inspect,()=>cf(n)),!t.watch)return 0;d(""),d(w("Watching. New events appear below as they happen; Ctrl+C to stop."));let r=n.at+1,o=t.intervalMs??2e3;for(;;){await new Promise(a=>setTimeout(a,o));let s=await $o(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Zi(a)}
504
+ `))}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} ${ag(a)}`)}}}async function yc(e,t={}){if(t.watch&&In())throw new m("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await Lo(e,{channel:t.channel});if(h(n.inspect,()=>ig(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 Lo(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${hc(a)}
482
505
  `);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"}
483
- `))}}}async function ec(e,t={}){let n=t.since?rf(t.since):0,r=await $o(e,{channel:t.channel,since:n});return h({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${Zi(o)}
484
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}ee();he();Q();import{readFile as df}from"node:fs/promises";var uf="api.snoutdata.com";function rc(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}.${uf}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function pf(e){return rc(e,await S("cloud-project-auth",{ref:e,action:"status"}))}async function Vn(e,t){return rc(e,await S("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function xo(e){let t=e.google,n="off";t.ready?n=$("on"):t.enabled&&(n=`switched on, not offered yet: needs ${[!t.clientId&&"a client id",!t.clientSecretSet&&"a client secret"].filter(Boolean).join(" and ")}`),process.stdout.write(`${H([["auth",e.enabled?$("on"):"off"],["site url",e.siteUrl??w("not set")],["redirects",e.redirects.length?e.redirects.join(", "):w("none")],["google",n],["google client id",t.clientId??w("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?$("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
485
- `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function mf(){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 oc(e,t,n,r){if(t===void 0||t==="show"){let o=await pf(e);h(o,()=>xo(o));return}if(t==="google"){if(n[0]==="off"){let a=await Vn(e,{googleEnabled:!1});h(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new g('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await mf();if(!a)throw new g("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Vn(e,o);h(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):xo(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new g('auth anonymous takes "on" or "off"');let s=await Vn(e,{anonymousEnabled:o==="on"});h(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new g("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Vn(e,o);h(s,()=>xo(s));return}if(t==="templates"){let o=await S("cloud-project-auth",{ref:e,action:"templates"});h(o,()=>nc(o.templates));return}if(t==="template"){let o=n[0];if(!o||!tc.includes(o))throw new g(`auth template needs a kind: ${tc.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new g(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await df(r.file,"utf8"),i=await S("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await S("cloud-project-auth",{ref:e,action:"template",template:s});h(a,()=>{nc(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new g(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var tc=["confirmation","recovery","magic_link","invite","email_change"];function nc(e){process.stdout.write(`${H([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?$("yours"):"ours",t.subject])])}
486
- `)}ee();Q();pe();import{createHash as Cf}from"node:crypto";import{existsSync as jf,readdirSync as If,readFileSync as Df,statSync as Mf}from"node:fs";import{isAbsolute as Lf,join as To,relative as Uf,resolve as Ff}from"node:path";function ff(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Yn(e,t,n={}){let r=ff(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 bt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Eo,createHmac as Xn,pbkdf2Sync as gf,randomBytes as hf,timingSafeEqual as yf}from"node:crypto";import{readFileSync as wf}from"node:fs";import{homedir as bf}from"node:os";import{connect as Sf,isIP as _f}from"node:net";import{connect as kf}from"node:tls";var Ve=class extends Error{severity;code;detail;hint;position;where;constructor(t){super(t.get("M")??"the server reported an error"),this.name="PgServerError",this.severity=t.get("V")??t.get("S")??"ERROR",this.code=t.get("C")??"";let n=t.get("D"),r=t.get("H"),o=Number(t.get("P")),s=t.get("W");n&&(this.detail=n),r&&(this.hint=r),Number.isInteger(o)&&o>0&&(this.position=o),s&&(this.where=s)}},re=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},vf=196608,$f=80877103;function sc(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function We(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function xf(e,t=0){let n=[],r=t;for(;r<e.length;){let o=e.indexOf(0,r);if(o<0||o===r)break;n.push(e.toString("utf8",r,o)),r=o+1}return n}function ac(e){let t=new Map,n=0;for(;n<e.length&&e[n]!==0;){let r=String.fromCharCode(e[n]),o=e.indexOf(0,n+1);if(o<0)break;t.set(r,e.toString("utf8",n+1,o)),n=o+1}return t}var Po=class{constructor(t,n=hf(18).toString("base64")){this.password=t;this.nonce=n,this.firstBare=`n=,r=${this.nonce}`}nonce;firstBare;serverSignature=null;first(){return`n,,${this.firstBare}`}final(t){let n=new Map(t.split(",").map(k=>[k.slice(0,1),k.slice(2)])),r=n.get("r"),o=n.get("s"),s=Number(n.get("i"));if(!r||!r.startsWith(this.nonce)||r.length<=this.nonce.length)throw new re("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new re("the server answered the password exchange with no salt or iteration count");let a=gf(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),l=Xn("sha256",a).update("Client Key").digest(),i=Eo("sha256").update(l).digest(),u=`c=biws,r=${r}`,c=`${this.firstBare},${t},${u}`,p=Xn("sha256",i).update(c).digest(),f=Buffer.alloc(l.length);for(let k=0;k<f.length;k++)f[k]=l[k]^p[k];let E=Xn("sha256",a).update("Server Key").digest();return this.serverSignature=Xn("sha256",E).update(c).digest(),`${u},p=${f.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new re(`the server refused the password exchange: ${n[1]}`);let r=/(?:^|,)v=([^,]*)/.exec(t),o=r?Buffer.from(r[1],"base64"):Buffer.alloc(0);if(!this.serverSignature||o.length!==this.serverSignature.length||!yf(o,this.serverSignature))throw new re("the server could not prove it knows the password, so this is not the database it claims to be")}},Ao=class{buffer=Buffer.alloc(0);queue=[];waiting=null;failure=null;constructor(t){t.on("data",n=>this.take(n)),t.on("error",n=>this.fail(new re(n.message))),t.on("close",()=>this.fail(new re("the server closed the connection")))}take(t){for(this.buffer=this.buffer.length===0?t:Buffer.concat([this.buffer,t]);this.buffer.length>=5;){let n=this.buffer.readInt32BE(1);if(n<4){this.fail(new re("the server sent a message this client cannot read"));return}if(this.buffer.length<n+1)break;let r={type:String.fromCharCode(this.buffer[0]),body:this.buffer.subarray(5,n+1)};if(this.buffer=this.buffer.subarray(n+1),this.waiting){let{resolve:o}=this.waiting;this.waiting=null,o(r)}else this.queue.push(r)}}fail(t){if(!this.failure&&(this.failure=t,this.waiting)){let{reject:n}=this.waiting;this.waiting=null,n(t)}}next(){let t=this.queue.shift();return t?Promise.resolve(t):this.failure?Promise.reject(this.failure):new Promise((n,r)=>{this.waiting={resolve:n,reject:r}})}};function Ef(e){return new Promise((t,n)=>{let r=l=>{if(a(),l.length!==1){n(new re("the server sent more than its answer to the TLS request, so the connection was closed"));return}t(String.fromCharCode(l[0]))},o=l=>{a(),n(new re(l.message))},s=()=>{a(),n(new re("the server closed the connection before TLS"))},a=()=>{e.off("data",r),e.off("error",o),e.off("close",s)};e.on("data",r),e.on("error",o),e.on("close",s)})}function Pf(e,t){return new Promise((n,r)=>{let o=Sf({host:e,port:t}),s=a=>r(new re(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function Af(e,t,n,r){let o=await Pf(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE($f,4),o.write(s),await Ef(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new re(`${e.host} does not accept TLS, and this connection requires it (sslmode ${t})`)}let l=t==="verify-ca"||t==="verify-full";return new Promise((i,u)=>{let c=kf({socket:o,host:e.host,..._f(e.host)===0?{servername:e.host}:{},rejectUnauthorized:l,...l&&n?{ca:wf(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(c),c.once("secureConnect",()=>{c.off("error",p),i(c)});let p=f=>u(new re(`TLS to ${e.host} failed: ${f.message}`));c.once("error",p)})}var Zn=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=qt(t.ssl,r,n.home??bf()),s=o.rootCert??(r.PGSSLROOTCERT||void 0),a=o.mode==="require"&&s?"verify-ca":o.mode,l=n.connectTimeoutMs??12e4,i=[],u=()=>{for(let f of i)f.destroy()},c,p=new Promise((f,E)=>{c=setTimeout(()=>{u(),E(new re(`${t.host}:${t.port} did not answer within ${Math.round(l/1e3)} seconds`))},l)});try{return await Promise.race([(async()=>{let f=await Af(t,a,s,k=>i.push(k)),E=new e(f,new Ao(f));return await E.startup(t),E})(),p])}catch(f){throw u(),f}finally{clearTimeout(c)}}send(t,n){this.socket.write(sc(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[We(s),We(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(vf,4),this.socket.write(Buffer.concat([r,n]));let o=null;for(;;){let s=await this.reader.next();switch(s.type){case"R":{let a=s.body.readInt32BE(0);if(a===0)break;if(a===3)this.send("p",We(t.password));else if(a===5){let l=Eo("md5").update(t.password+t.user).digest("hex"),i=Eo("md5").update(Buffer.concat([Buffer.from(l),s.body.subarray(4,8)])).digest("hex");this.send("p",We(`md5${i}`))}else if(a===10){let l=xf(s.body,4);if(!l.includes("SCRAM-SHA-256"))throw new re(`the server asked for ${l.join(", ")||"a password exchange"}, which this client does not speak`);o=new Po(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([We("SCRAM-SHA-256"),u,i]))}else if(a===11&&o)this.send("p",Buffer.from(o.final(s.body.toString("utf8",4)),"utf8"));else if(a===12&&o)o.verify(s.body.toString("utf8",4));else throw new re(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new Ve(ac(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",We(t));let r=[],o=null,s=null;for(;;){let a=await this.reader.next();switch(a.type){case"T":r=[];break;case"D":{let l=a.body.readInt16BE(0),i=[],u=2;for(let c=0;c<l;c++){let p=a.body.readInt32BE(u);u+=4,p<0?i.push(null):(i.push(a.body.toString("utf8",u,u+p)),u+=p)}r.push(i);break}case"C":o=a.body.toString("utf8",0,Math.max(0,a.body.indexOf(0)));break;case"E":s??=new Ve(ac(a.body));break;case"G":n===void 0?this.send("f",We("db push has no rows for this COPY: the lines after it, ending in \\., are what it reads")):(n.length>0&&this.send("d",Buffer.from(n,"utf8")),this.send("c",Buffer.alloc(0)));break;case"W":this.send("f",We("COPY BOTH is not something a migration can do"));break;case"Z":if(s)throw s;return{rows:r,command:o};default:break}}}close(){this.socket.destroyed||this.socket.end(sc("X",Buffer.alloc(0)))}};function ic(e,t,n=1){let r=[`${e.severity}: ${e.message}`];if(e.position!==void 0){let o=[...t].slice(0,e.position-1),s=o.filter(u=>u===`
506
+ `))}}}async function wc(e,t={}){let n=t.since?rg(t.since):0,r=await Lo(e,{channel:t.channel,since:n});return h({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${hc(o)}
507
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}re();be();me();te();var or=[{id:"bedrock",name:"Amazon Bedrock",mark:"AB",blurb:"Claude, Llama, Mistral, Qwen and more, in your own AWS account.",kind:"bedrock",suggestedName:"bedrock",region:!0,auth:"bedrock",connect:{console:{label:"AWS IAM console",href:"https://console.aws.amazon.com/iam/home#/users"},steps:["In IAM, make a user for the AI gateway (or reuse one) with a policy allowing bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream and bedrock:Converse on the models you will serve.","Under the user's Security credentials, choose Create access key, for an application running outside AWS, and copy the access key ID and the secret. The secret is shown once.","Or use a Bedrock API key instead: Bedrock console, API keys, Generate.","Pick the region the models are in. Inference profiles starting with us. or global. route across regions from there."]},model:{link:{label:"Bedrock model catalog",href:"https://console.aws.amazon.com/bedrock/home#/model-catalog"},steps:["Open the Bedrock model catalog in the region you picked and choose the model.","If it asks for access, request it under Model access first; Anthropic models ask for a short use-case form once.","Copy its inference profile ID (it starts with us., eu., apac. or global.). A model with no inference profile is called by its model ID."],example:"us.anthropic.claude-sonnet-4-5-20250929-v1:0"},price:{link:{label:"Bedrock pricing",href:"https://aws.amazon.com/bedrock/pricing/"},steps:["Find the model on Bedrock's pricing page under on-demand. AWS quotes per 1,000 tokens for some models: multiply by 1,000 for the per-million rate asked here.",'Leave the prices blank if the shipped catalogue already knows the model; the model list shows "from the catalogue" when it does.']},caching:!0},{id:"anthropic",name:"Anthropic",mark:"An",blurb:"Claude, straight from Anthropic's API.",kind:"anthropic",suggestedName:"anthropic",baseUrl:"https://api.anthropic.com/v1",auth:"api_key",connect:{console:{label:"Anthropic Console, API keys",href:"https://console.anthropic.com/settings/keys"},steps:["In the Anthropic Console, open Settings, API keys, and choose Create key.","Name it after this gateway and copy it. It starts with sk-ant- and is shown once.","Make sure the organisation has credit or a billing plan, or every call is refused."]},model:{link:{label:"Claude models",href:"https://docs.anthropic.com/en/docs/about-claude/models/overview"},steps:["Open Anthropic's models overview and copy the model's API name, not its display name."],example:"claude-sonnet-4-5"},price:{link:{label:"Anthropic API pricing",href:"https://www.anthropic.com/pricing#api"},steps:["Copy the input and output rates per million tokens. Cache reads and cache writes have their own rates on the same page.","Leave the prices blank if the shipped catalogue already knows the model."]},caching:!0},{id:"openai",name:"OpenAI",mark:"OA",blurb:"GPT models, straight from OpenAI's API.",kind:"openai",suggestedName:"openai",baseUrl:"https://api.openai.com/v1",auth:"api_key",connect:{console:{label:"OpenAI API keys",href:"https://platform.openai.com/api-keys"},steps:["On the OpenAI platform, open API keys and choose Create new secret key.","Give it a project and the permission to call models, and copy it. It starts with sk- and is shown once.","Check the project has billing set up; a key without it is refused on the first call."]},model:{link:{label:"OpenAI models",href:"https://platform.openai.com/docs/models"},steps:["Open OpenAI's models page, choose the model and copy its id from the top of its page."],example:"gpt-5-mini"},price:{link:{label:"OpenAI API pricing",href:"https://openai.com/api/pricing/"},steps:['Copy the input and output rates per million tokens; "cached input" is the cache read rate. OpenAI caches by itself, so there is no cache write rate.',"Leave the prices blank if the shipped catalogue already knows the model."]},caching:!1},{id:"openrouter",name:"OpenRouter",mark:"OR",blurb:"Hundreds of models from many providers behind one key.",kind:"openai",suggestedName:"openrouter",baseUrl:"https://openrouter.ai/api/v1",auth:"api_key",connect:{console:{label:"OpenRouter keys",href:"https://openrouter.ai/settings/keys"},steps:["On OpenRouter, open Settings, Keys, and choose Create Key. A credit limit on the key is a good second fence.","Copy it. It starts with sk-or- and is shown once.","Add credits to the account, or every paid model is refused."]},model:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Find the model on OpenRouter and copy its id from under its name: the provider, a slash and the model."],example:"anthropic/claude-sonnet-4.5"},price:{link:{label:"OpenRouter models",href:"https://openrouter.ai/models"},steps:["Each model's page shows its input and output price per million tokens. Copy those; OpenRouter adds no fee on top of them for a key you pay for with credits."]},caching:!0},{id:"ollama",name:"Ollama",mark:"Ol",blurb:"A model on a machine of your own, such as the home server.",kind:"openai",suggestedName:"home-ollama",editableUrl:!0,urlExample:"https://home-ai.snoutdata.com/v1",auth:"optional",connect:{console:{label:"Ollama documentation",href:"https://github.com/ollama/ollama/blob/main/docs/openai.md"},steps:["The address must reach Ollama from this gateway, so not localhost: a public HTTPS name, such as one through a Cloudflare tunnel, ending in /v1.","Never leave it open: put a check in front of it (a Cloudflare rule that blocks any request without a secret header) and give that header's name and secret here.","Check it from your own machine first: a request to the address plus /models with the header should list the models, and one without it should be refused."]},model:{steps:["On the Ollama machine, run ollama pull with the model first, if it is not there yet.","Run ollama list and copy the NAME column exactly, tag included."],example:"snout-fim:1.0"},price:{steps:["Your machine charges nothing per token, so the price is what you choose to count against a person's budget: a small rate, such as $0.05 in and $0.10 out, keeps a runaway caller inside it.","Do not leave the price blank: the catalogue does not know your models, and a model nothing prices is refused to every signed-in user."]},caching:!1},{id:"compatible",name:"Any OpenAI-compatible server",mark:"{}",blurb:"vLLM, LM Studio, Together, Groq, DeepSeek and the rest.",kind:"openai",suggestedName:"my-provider",editableUrl:!0,urlExample:"https://api.example.com/v1",auth:"optional",connect:{steps:["Find the server's OpenAI-compatible address in its documentation; it usually ends in /v1.","Make an API key in its console and copy it. A server that checks a header of its own instead of a Bearer token takes the header's name too."]},model:{steps:["Copy the model id from the provider's model list, or from what the address plus /models returns."],example:"deepseek-chat"},price:{steps:["Copy the input and output rates per million tokens from the provider's pricing page.","For a server of your own, choose a small rate so budgets still count it; a model nothing prices is refused to every signed-in user."]},caching:!1}];function bc(e,t){if(t.kind!==e.kind)return!1;if(e.kind!=="openai")return!0;let n=t.base_url.replace(/\/+$/,"");return e.baseUrl?n===e.baseUrl:!or.some(r=>r.baseUrl&&r.kind==="openai"&&r.baseUrl===n)}function _c(e){return(e.trim().split("/").pop()??"").replace(/^(us|eu|apac|global)\.(anthropic|meta|mistral|amazon|cohere|qwen|deepseek|openai)\./,"").replace(/-v\d+:\d+$/,"").replace(/:/g,"-").toLowerCase()}function kc(e,t,n){let o=e.auth==="bedrock"&&n.aws===!0?n.accessKeyId?.trim()&&n.secretAccessKey?.trim()?{access_key_id:n.accessKeyId.trim(),secret_access_key:n.secretAccessKey.trim()}:null:n.apiKey?.trim()?{api_key:n.apiKey.trim()}:null;if(e.auth!=="optional"&&!o)return{problem:"Paste the key from the first steps; Psyche checks it with the provider before keeping it."};if(e.kind!=="bedrock"&&!n.baseUrl?.trim())return{problem:"Give the address."};if(e.kind==="bedrock"&&!n.region?.trim())return{problem:"Give the region the model runs in."};let s={kind:e.kind};return e.kind==="bedrock"?s.region=n.region.trim():(s.base_url=n.baseUrl.trim(),n.authHeader?.trim()&&(s.auth_header=n.authHeader.trim())),o&&(s.credential=t),{provider:s,credential:o}}function lg(e){return e===void 0||e.trim()===""?void 0:Number(e)}function Sc(e){if(!e.upstream.trim())return{problem:"Give the model's id, as the provider names it."};let[t,n,r,o,s]=[e.input,e.output,e.cacheRead,e.cacheWrite,e.maxOutput].map(lg);if([t,n,r,o].some(l=>l!==void 0&&(!Number.isFinite(l)||l<0)))return{problem:"Prices are numbers, zero or more."};if(t===void 0!=(n===void 0))return{problem:"Give both the input and the output rate, or neither to take the catalogue's."};let a={provider:e.provider,upstream:e.upstream.trim()};return e.caching!==void 0&&(a.prompt_caching=e.caching),e.enabled!==void 0&&(a.enabled=e.enabled),e.access&&(a.access=e.access),t!==void 0&&n!==void 0&&(a.price={input:t,output:n},r!==void 0&&(a.price.cache_read=r),o!==void 0&&(a.price.cache_write=o)),s!==void 0&&Number.isFinite(s)&&s>0&&(a.max_output_tokens=Math.round(s)),{model:a}}var ar=class{send;constructor(t){this.send=t}providers(){return this.list("admin/providers")}putProvider(t,n){return this.call("PUT",`admin/providers/${Ke(t)}`,n)}deleteProvider(t){return this.none("DELETE",`admin/providers/${Ke(t)}`)}models(){return this.list("admin/models")}putModel(t,n){return this.call("PUT",`admin/models/${sr(t)}`,n)}deleteModel(t){return this.call("DELETE",`admin/models/${sr(t)}`)}routes(){return this.list("admin/routes")}putRoute(t,n){return this.call("PUT",`admin/routes/${sr(t)}`,n)}deleteRoute(t){return this.none("DELETE",`admin/routes/${sr(t)}`)}credentials(){return this.list("admin/credentials")}putCredential(t,n){return this.call("PUT",`admin/credentials/${Ke(t)}`,n)}deleteCredential(t){return this.none("DELETE",`admin/credentials/${Ke(t)}`)}keys(){return this.list("admin/keys")}createKey(t){return this.call("POST","admin/keys",t)}revokeKey(t){return this.none("DELETE",`admin/keys/${Ke(t)}`)}budgets(){return this.list("admin/budgets")}createBudget(t){return this.call("POST","admin/budgets",t)}editBudget(t,n){return this.call("PATCH",`admin/budgets/${Ke(t)}`,n)}deleteBudget(t){return this.none("DELETE",`admin/budgets/${Ke(t)}`)}limits(){return this.list("admin/limits")}createLimit(t){return this.call("POST","admin/limits",t)}deleteLimit(t){return this.none("DELETE",`admin/limits/${Ke(t)}`)}groups(){return this.list("admin/groups")}usage(t={}){return this.call("GET",`admin/usage${vc(t)}`)}requests(t={}){return this.call("GET",`admin/requests${vc(t)}`)}async list(t){return(await this.send("GET",t))?.data??[]}async call(t,n,r){return await this.send(t,n,r)}async none(t,n){await this.send(t,n)}};function Ke(e){return encodeURIComponent(e)}function sr(e){return e.split("/").map(Ke).join("/")}function vc(e){let t=Object.entries(e).filter(([,n])=>n!=null&&n!=="").map(([n,r])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(r))}`);return t.length?`?${t.join("&")}`:""}function Zt(e){if(e==null||e==="")return null;let t=Number(e);return Number.isFinite(t)?t:null}async function cr(e,t){return _("cloud-project-ai",t===void 0?{ref:e}:{ref:e,enable:t})}function dg(e){if(e instanceof ge){let t=e.body?.error;if(t&&typeof t=="object"&&typeof t.message=="string")return new ge(e.status,t.message,typeof t.code=="string"?t.code:null,e.body)}return e}function ug(e){return async(t,n,r)=>{try{return await _("cloud-project-ai",{ref:e,admin:{method:t,path:n,...r===void 0?{}:{body:r}}})}catch(o){throw dg(o)}}}function fe(e){return new ar(ug(e))}function pg(e){return e instanceof ge&&(e.status===502||e.status===404&&e.serverCode!=="ai-off")}function Uo(e){return Number.isInteger(e)?`$${e}`:Math.abs(e*100-Math.round(e*100))<1e-9?`$${e.toFixed(2)}`:`$${e}`}function xc(e){return e?`${Uo(e.input)} in, ${Uo(e.output)} out`:"no price"}function Pc(e){return e.every_user?"every user":e.user_id?`user ${e.user_id}`:e.key_id?`key ${e.key_id}`:e.group_id?`group ${e.group_id}`:"the project"}function Fo(e){let t=e.period==="rolling"?`per ${e.days??"?"} days`:e.period==="since"?`since ${e.starts_on??"?"}`:`a ${e.period}`,n=Zt(e.amount);return`${n===null?`$${e.amount}`:Uo(n)} ${t}, ${e.hard?"hard":"soft"}`}function mg(e){let t=Zt(e);return t===null?"-":`$${t.toFixed(t!==0&&t<.01?4:2)}`}function ir(e){return e===null?"never":new Date(e*1e3).toISOString().slice(0,16).replace("T"," ")}function Ec(e,t=Date.now()){return new Date(t-(e-1)*864e5).toISOString().slice(0,10)}function fg(e,t){return`${e} ${t}${e===1?"":"s"}`}async function Ac(e){let t=await cr(e);if(!t.ai.enabled){h({ref:e,enabled:!1,endpoint:t.ai.endpoint},()=>{d(`AI is off for ${e}.`),d(w(` snoutdata ai enable --ref ${e} ${t.ai.restartsDatabase}`))});return}let n=fe(e),[r,o,s]=await Promise.allSettled([n.models(),n.budgets(),n.usage({by:"day",from:Ec(30)})]),a=[r,o,s].find(f=>f.status==="rejected");if(a&&pg(a.reason)){h({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!1},()=>{d(`AI is on for ${e}, and its gateway is still starting. Try again in a minute.`)});return}if(a)throw a.reason;let l=r.value,i=o.value,u=s.value.data,c=u.reduce((f,S)=>f+(Zt(S.cost)??0),0),p=u.reduce((f,S)=>f+S.requests,0);h({ref:e,enabled:!0,endpoint:t.ai.endpoint,serving:!0,models:l,budgets:i,last30Days:{requests:p,cost:c}},()=>{d(`${x(`AI for ${e}`)}: on, at ${t.ai.endpoint}`),d(""),l.length===0?(d("No models yet. Add one:"),d(w(" snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(Tc(l)),d(""),d(i.length===0?"No budgets.":i.map(f=>`Budget: ${Pc(f)}, ${Fo(f)}`).join(`
508
+ `)),d(`Last 30 days: ${fg(p,"request")}, $${c.toFixed(2)}.`)})}async function Rc(e,t){let n=await cr(e,t);h(n,()=>{t?(d(`AI is on for ${e}. ${n.ai.restartsDatabase} The gateway answers at ${n.ai.endpoint} within a minute or two.`),d(w(" next: snoutdata ai models add --source openai --model gpt-4o-mini --stdin < key.txt"))):d(`AI is off for ${e}. Its providers, models, keys and log stay in the project's database, for when it is switched on again.`)})}function Tc(e){return L([["MODEL","PROVIDER","UPSTREAM","PRICE PER M TOKENS","SERVED"],...e.map(t=>[t.name,t.provider,t.upstream,xc(t.price)+(t.price_from==="catalogue"?" (catalogue)":""),t.served?"yes":`no: ${t.problem??(t.enabled?"not served":"disabled")}`])])}async function Oc(e){let t=await fe(e).models();h(t,()=>{d(t.length===0?`No models on ${e}'s gateway yet.`:Tc(t))})}async function Nc(e){let t=await fe(e).providers();h(t,()=>{if(t.length===0){d(`No providers on ${e}'s gateway yet. \`snoutdata ai models add\` makes one with its first model.`);return}d(L([["PROVIDER","KIND","WHERE","KEY","MODELS"],...t.map(n=>[n.name,n.kind,n.kind==="bedrock"?n.region??"":n.base_url,gg(n),n.models.join(", ")||"-"])]));for(let n of t.filter(r=>r.problem))Q(`${n.name}: ${n.problem}`)})}function gg(e){return e.key==="environment"?"from the environment":e.key==="none"||e.credential===null?"none":e.credential_stored?"stored":"missing"}async function Ic(e,t){if(!t)throw new m("ai providers remove needs the provider's name (see `snoutdata ai providers`)");await fe(e).deleteProvider(t),h({ref:e,removed:t},()=>d(`Removed the provider ${t}.`))}function hg(e){let t=or.map(r=>r.id).join("|");if(!e)throw new m(`ai models add needs --source ${t}: where the model runs`);let n=or.find(r=>r.id===e);if(!n)throw new m(`--source is one of ${t}, not "${e}"`);return n}function yg(e){d(x(`Adding a model from ${e.name}`));let t=[["The key",e.connect],["The model's id",e.model],["Its price",e.price]];for(let[n,r]of t){d(""),d(x(n));for(let s of r.steps)d(` - ${s}`);let o=r.console??r.link;o&&d(w(` ${o.label}: ${o.href}`))}d("")}function wg(e,t,n){if(n)return{name:n,existing:t.find(o=>o.name===n)??null};let r=t.filter(o=>bc(e,o));if(r.length===1)return{name:r[0].name,existing:r[0]};if(r.length>1)throw new m(`${e.name} is connected more than once (${r.map(o=>o.name).join(", ")}): say which with --provider`);return{name:e.suggestedName,existing:t.find(o=>o.name===e.suggestedName)??null}}async function Cc(e,t,n){let r=hg(n.source),o=n.upstream?.trim();if(!o)throw yg(r),new m(`Give the model's id as ${r.name} names it, with --model (for example ${r.model.example})`);let s=t?.trim()||_c(o),a=fe(e),[l,i]=await Promise.all([a.providers(),a.models()]);if(i.some(R=>R.name===s))throw new m(`${e} already has a model called ${s}. Name this one differently, or remove that one first.`);let{name:u,existing:c}=wg(r,l,n.provider);if(c&&c.kind!==r.kind)throw new m(`The provider ${u} speaks ${c.kind}, not ${r.name}: choose another --provider name`);if(c?.origin==="file"&&n.stdin)throw new m(`The provider ${u} comes from the gateway's providers file, so its key is set there, not here`);let p=n.stdin?await St():void 0,f=r.auth==="bedrock"&&n.accessKeyId!==void 0,S=null;if(!c){if(r.auth!=="optional"&&!p)throw new m(f?"Pipe the AWS secret access key in with --stdin; --access-key-id is its id":`${r.name} needs a key: pipe it in with --stdin (snoutdata ai models add ... --stdin < key.txt), so it is in neither the shell history nor ps`);let R=kc(r,u,{baseUrl:n.baseUrl??r.baseUrl,region:n.region,apiKey:f?void 0:p,aws:f,accessKeyId:n.accessKeyId,secretAccessKey:f?p:void 0,authHeader:n.authHeader});if("problem"in R)throw new m(r.kind==="bedrock"&&!n.region?"Give the region the model runs in, with --region":n.baseUrl===void 0&&r.kind!=="bedrock"?"Give the provider's address, with --base-url":R.problem);S=R}let v=Sc({provider:u,upstream:o,input:n.priceIn,output:n.priceOut,caching:r.caching,enabled:!0,access:"everyone"});if("problem"in v)throw new m(v.problem.replace("input and the output rate","--price-in and --price-out"));let V=!1,C=!1,Y;try{if(S&&(await a.putProvider(u,S.provider),V=!0),Y=await a.putModel(s,v.model),C=!0,S?.credential)await a.putCredential(u,S.credential);else if(c&&p){if(!c.credential)throw new m(`The provider ${u} takes no key`);await a.putCredential(c.credential,f?{access_key_id:n.accessKeyId,secret_access_key:p}:{api_key:p})}}catch(R){throw C&&await a.deleteModel(s).catch(()=>{}),V&&await a.deleteProvider(u).catch(()=>{}),R}Y=(await a.models()).find(R=>R.name===s)??Y,h(Y,()=>{d(`Added ${x(s)} (${o} on ${u}${V?", a new provider":""}), at ${xc(Y.price)}${Y.price_from==="catalogue"?" from the shipped catalogue":""}.`),Y.price||Q("Nothing prices this model, so signed-in users are refused it. Give it a price with --price-in and --price-out."),!Y.served&&Y.problem&&Q(`It is not served yet: ${Y.problem}`),d(w(` snoutdata ai test --model ${s}`))})}async function jc(e,t){if(!t)throw new m("ai models remove needs the model's name (see `snoutdata ai models`)");let n=await fe(e).deleteModel(t);h(n,()=>d(n.disabled?`${t} comes from the gateway's providers file, so it was disabled rather than removed.`:`Removed ${t}.`))}function bg(e){return(e??"").split(",").map(t=>t.trim()).filter(t=>t!=="")}function _g(e){return L([["ROUTE","TRIES, IN ORDER","SORT","SKIPS"],...e.map(t=>[t.name,t.tried.join(", ")||"-",t.sort,t.skipped.join(", ")||"-"])])}async function Dc(e){let t=await fe(e).routes();h(t,()=>{if(t.length===0){d(`No routes on ${e}'s gateway. A route is one name for several models, each tried when the one before fails:`),d(w(" snoutdata ai routes set NAME --models first,second [--sort price]"));return}d(_g(t))})}async function Mc(e,t,n){if(!t)throw new m("ai routes set needs the route's name: snoutdata ai routes set NAME --models first,second");let r=bg(n.models);if(r.length===0)throw new m("ai routes set needs --models, the models to try in order, comma-separated (see `snoutdata ai models`)");let o=n.sort??"order";if(o!=="order"&&o!=="price")throw new m(`--sort is order (as written) or price (the cheapest first), not ${o}`);let s=fe(e);await s.putRoute(t,{targets:r,sort:o});let a=(await s.routes()).find(l=>l.name===t);h(a??{name:t,targets:r,sort:o},()=>{let l=a?.tried??r;d(`${x(t)} tries ${l.join(", then ")}${o==="price"?", the cheapest first":""}.`),a&&a.skipped.length>0&&Q(`skipped, not served: ${a.skipped.join(", ")}`),d(w(` Call it as the model "${t}", or: snoutdata ai test --model ${t}`))})}async function Lc(e,t){if(!t)throw new m("ai routes remove needs the route's name (see `snoutdata ai routes`)");await fe(e).deleteRoute(t),h({name:t,removed:!0},()=>d(`Removed the route ${t}. Its models are untouched.`))}function kg(e,t=Date.now()/1e3){return e.revoked_at!==null?"revoked":e.expires_at!==null&&e.expires_at<=t?"expired":e.expires_at!==null?`until ${ir(e.expires_at).slice(0,10)}`:"active"}async function Uc(e){let t=await fe(e).keys();h(t,()=>{if(t.length===0){d(`No keys on ${e}'s gateway. \`snoutdata ai keys create --name my-server\` makes one for a server.`);return}d(L([["NAME","ID","KEY","MODELS","CREATED","LAST USED","STATE"],...t.map(n=>[n.name,n.id,n.hint,n.models?.join(", ")??"all",ir(n.created_at),n.last_used_at!==null?ir(n.last_used_at):n.last_used_on??"never",kg(n)])]))})}async function Fc(e,t){if(!t.name)throw new m("ai keys create needs --name: what the key is for, shown in ai keys");if(t.expiresDays!==void 0&&t.expiresDays<=0)throw new m("--expires is a number of days, 1 or more");let n=t.models?.split(",").map(s=>s.trim()).filter(Boolean),r=await fe(e).createKey({name:t.name,...n&&n.length>0?{models:n}:{},...t.expiresDays!==void 0?{expires_in:t.expiresDays*86400}:{}}),o=await cr(e);h({...r,endpoint:o.ai.endpoint},()=>{process.stdout.write(`${r.key}
509
+ `),d(""),d(`${x(r.name)}: ${r.models?`for ${r.models.join(", ")}`:"for every model"}, ${r.expires_at?`until ${ir(r.expires_at)}`:"until revoked"}.`),d("This is the only time it is shown: only its hash is kept. It is a server's key; a browser calls with the app key and a signed-in user instead."),d(w(` OPENAI_BASE_URL=${o.ai.endpoint} OPENAI_API_KEY=<that key>`))})}async function Bc(e,t){if(!t)throw new m("ai keys revoke needs the key's id (see `snoutdata ai keys`)");await fe(e).revokeKey(t),h({ref:e,revoked:t},()=>d(`Revoked ${t}. A call with it is refused from now on.`))}async function Hc(e){let t=await fe(e).budgets();h(t,()=>{if(t.length===0){d(`No budgets on ${e}'s gateway. A browser call needs two hard ones:`),d(w(" snoutdata ai budgets set project --amount 5")),d(w(" snoutdata ai budgets set users --amount 0.50"));return}d(L([["ID","FOR","BUDGET"],...t.map(n=>[n.id,Pc(n),Fo(n)])]))})}function Sg(e){if(e==="project"||e==="users")return e;throw new m("ai budgets set takes project (the whole project) or users (each signed-in user)")}function vg(e,t,n){return e.find(r=>r.period===n&&!r.key_id&&!r.user_id&&!r.group_id&&(t==="users"?r.every_user:!r.every_user))}async function Kc(e,t,n){let r=Sg(t),o=Number(n.amount);if(n.amount===void 0||!Number.isFinite(o)||o<0)throw new m("ai budgets set needs --amount, in dollars, zero or more");let s=n.period??"day";if(s!=="day"&&s!=="month")throw new m("--period is day or month");let a=fe(e),l=vg(await a.budgets(),r,s),i=l?await a.editBudget(l.id,{amount:o,hard:!n.soft}):await a.createBudget({name:r==="users"?"every user":"project",amount:o,period:s,hard:!n.soft,...r==="users"?{every_user:!0}:{}});h(i,()=>d(`${l?"Changed":"Set"}: ${r==="users"?"every user":"the project"}, ${Fo(i)}.`))}async function qc(e,t){if(!t)throw new m("ai budgets remove needs the budget's id (see `snoutdata ai budgets`)");await fe(e).deleteBudget(t),h({ref:e,removed:t},()=>d(`Removed the budget ${t}.`))}var $c=["day","model","provider","operation","key","user","group"];async function Gc(e,t){let n=t.by??"model";if(!$c.includes(n))throw new m(`--by is one of ${$c.join("|")}`);let r=t.days??30;if(r<1||r>366)throw new m("--days is 1 to 366");let o=await fe(e).usage({by:n,from:Ec(r)});h(o,()=>{if(o.data.length===0){d(`No calls to ${e}'s gateway from ${o.from} to ${o.to}.`);return}let s=o.data.reduce((a,l)=>a+(Zt(l.cost)??0),0);d(L([[n.toUpperCase(),"REQUESTS","FAILED","TOKENS IN","TOKENS OUT","COST"],...o.data.map(a=>[String(a[n]??"-"),String(a.requests),String(a.failed),String(a.prompt_tokens),String(a.completion_tokens),mg(a.cost)+(a.unpriced>0?` (+${a.unpriced} unpriced)`:"")])])),d(`${o.from} to ${o.to}: $${s.toFixed(2)}.`)})}async function zc(e,t){let n=await cr(e);if(!n.ai.enabled)throw new P("not-found",`AI is off for ${e}. Switch it on first: snoutdata ai enable.`);let r=fe(e),s=(await r.models()).filter(S=>S.served&&S.embeddings===null),a=t.model??s[0]?.name;if(!a)throw new P("not-found",`${e}'s gateway serves no chat model yet. Add one: snoutdata ai models add.`);let l=await r.createKey({name:"snoutdata ai test",models:[a],expires_in:600}),i=Date.now(),u=0,c={};try{let S=await fetch(`${n.ai.endpoint}/chat/completions`,{method:"POST",headers:{authorization:`Bearer ${l.key}`,"content-type":"application/json"},body:JSON.stringify({model:a,max_tokens:64,messages:[{role:"user",content:t.prompt??"Say hello in five words."}]})});u=S.status;let v=await S.text();try{c=v?JSON.parse(v):{}}catch{c={error:{message:Un(S.status,S.statusText,v)}}}}finally{await r.revokeKey(l.id).catch(()=>{})}let p=Date.now()-i;if(u<200||u>=300){let S=c.error?.message??`the gateway answered ${u}`;throw new P(Le(u,c.error?.code??null),`${a} through ${e}'s gateway: ${S}`,{model:a,status:u,gatewayCode:c.error?.code??null})}let f=c.choices?.[0]?.message?.content??"";return h({ref:e,model:a,reply:f,usage:c.usage??null,ms:p},()=>{process.stdout.write(`${f}
510
+ `),d(w(`${a}, ${p} ms, ${c.usage?.prompt_tokens??"?"} tokens in, ${c.usage?.completion_tokens??"?"} out, through ${n.ai.endpoint}`))}),0}re();be();te();import{readFile as xg}from"node:fs/promises";var Pg="api.snoutdata.com";function Vc(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}.${Pg}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function Eg(e){return Vc(e,await _("cloud-project-auth",{ref:e,action:"status"}))}async function lr(e,t){return Vc(e,await _("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Bo(e){let t=e.google,n="off";t.ready?n=x("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?x("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?x("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
511
+ `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function Ag(){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 Yc(e,t,n,r){if(t===void 0||t==="show"){let o=await Eg(e);h(o,()=>Bo(o));return}if(t==="google"){if(n[0]==="off"){let a=await lr(e,{googleEnabled:!1});h(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new m('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 Ag();if(!a)throw new m("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await lr(e,o);h(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Bo(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 m('auth anonymous takes "on" or "off"');let s=await lr(e,{anonymousEnabled:o==="on"});h(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new m("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await lr(e,o);h(s,()=>Bo(s));return}if(t==="templates"){let o=await _("cloud-project-auth",{ref:e,action:"templates"});h(o,()=>Jc(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Wc.includes(o))throw new m(`auth template needs a kind: ${Wc.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new m(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await xg(r.file,"utf8"),i=await _("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 _("cloud-project-auth",{ref:e,action:"template",template:s});h(a,()=>{Jc(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new m(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Wc=["confirmation","recovery","magic_link","invite","email_change"];function Jc(e){process.stdout.write(`${L([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?x("yours"):"ours",t.subject])])}
512
+ `)}re();te();me();import{createHash as Jg}from"node:crypto";import{existsSync as Vg,readdirSync as Yg,readFileSync as Xg,statSync as Zg}from"node:fs";import{isAbsolute as Qg,join as zo,relative as eh,resolve as th}from"node:path";function Rg(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function dr(e,t,n={}){let r=Rg(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 xt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Ho,createHmac as ur,pbkdf2Sync as Tg,randomBytes as Og,timingSafeEqual as Ng}from"node:crypto";import{readFileSync as Ig}from"node:fs";import{homedir as Cg}from"node:os";import{connect as jg,isIP as Dg}from"node:net";import{connect as Mg}from"node:tls";var Qe=class extends Error{severity;code;detail;hint;position;where;constructor(t){super(t.get("M")??"the server reported an error"),this.name="PgServerError",this.severity=t.get("V")??t.get("S")??"ERROR",this.code=t.get("C")??"";let n=t.get("D"),r=t.get("H"),o=Number(t.get("P")),s=t.get("W");n&&(this.detail=n),r&&(this.hint=r),Number.isInteger(o)&&o>0&&(this.position=o),s&&(this.where=s)}},se=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},Lg=196608,Ug=80877103;function Xc(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function Ze(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function Fg(e,t=0){let n=[],r=t;for(;r<e.length;){let o=e.indexOf(0,r);if(o<0||o===r)break;n.push(e.toString("utf8",r,o)),r=o+1}return n}function Zc(e){let t=new Map,n=0;for(;n<e.length&&e[n]!==0;){let r=String.fromCharCode(e[n]),o=e.indexOf(0,n+1);if(o<0)break;t.set(r,e.toString("utf8",n+1,o)),n=o+1}return t}var Ko=class{constructor(t,n=Og(18).toString("base64")){this.password=t;this.nonce=n,this.firstBare=`n=,r=${this.nonce}`}nonce;firstBare;serverSignature=null;first(){return`n,,${this.firstBare}`}final(t){let n=new Map(t.split(",").map(v=>[v.slice(0,1),v.slice(2)])),r=n.get("r"),o=n.get("s"),s=Number(n.get("i"));if(!r||!r.startsWith(this.nonce)||r.length<=this.nonce.length)throw new se("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new se("the server answered the password exchange with no salt or iteration count");let a=Tg(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),l=ur("sha256",a).update("Client Key").digest(),i=Ho("sha256").update(l).digest(),u=`c=biws,r=${r}`,c=`${this.firstBare},${t},${u}`,p=ur("sha256",i).update(c).digest(),f=Buffer.alloc(l.length);for(let v=0;v<f.length;v++)f[v]=l[v]^p[v];let S=ur("sha256",a).update("Server Key").digest();return this.serverSignature=ur("sha256",S).update(c).digest(),`${u},p=${f.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new se(`the server refused the password exchange: ${n[1]}`);let r=/(?:^|,)v=([^,]*)/.exec(t),o=r?Buffer.from(r[1],"base64"):Buffer.alloc(0);if(!this.serverSignature||o.length!==this.serverSignature.length||!Ng(o,this.serverSignature))throw new se("the server could not prove it knows the password, so this is not the database it claims to be")}},qo=class{buffer=Buffer.alloc(0);queue=[];waiting=null;failure=null;constructor(t){t.on("data",n=>this.take(n)),t.on("error",n=>this.fail(new se(n.message))),t.on("close",()=>this.fail(new se("the server closed the connection")))}take(t){for(this.buffer=this.buffer.length===0?t:Buffer.concat([this.buffer,t]);this.buffer.length>=5;){let n=this.buffer.readInt32BE(1);if(n<4){this.fail(new se("the server sent a message this client cannot read"));return}if(this.buffer.length<n+1)break;let r={type:String.fromCharCode(this.buffer[0]),body:this.buffer.subarray(5,n+1)};if(this.buffer=this.buffer.subarray(n+1),this.waiting){let{resolve:o}=this.waiting;this.waiting=null,o(r)}else this.queue.push(r)}}fail(t){if(!this.failure&&(this.failure=t,this.waiting)){let{reject:n}=this.waiting;this.waiting=null,n(t)}}next(){let t=this.queue.shift();return t?Promise.resolve(t):this.failure?Promise.reject(this.failure):new Promise((n,r)=>{this.waiting={resolve:n,reject:r}})}};function Bg(e){return new Promise((t,n)=>{let r=l=>{if(a(),l.length!==1){n(new se("the server sent more than its answer to the TLS request, so the connection was closed"));return}t(String.fromCharCode(l[0]))},o=l=>{a(),n(new se(l.message))},s=()=>{a(),n(new se("the server closed the connection before TLS"))},a=()=>{e.off("data",r),e.off("error",o),e.off("close",s)};e.on("data",r),e.on("error",o),e.on("close",s)})}function Hg(e,t){return new Promise((n,r)=>{let o=jg({host:e,port:t}),s=a=>r(new se(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function Kg(e,t,n,r){let o=await Hg(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(Ug,4),o.write(s),await Bg(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new se(`${e.host} does not accept TLS, and this connection requires it (sslmode ${t})`)}let l=t==="verify-ca"||t==="verify-full";return new Promise((i,u)=>{let c=Mg({socket:o,host:e.host,...Dg(e.host)===0?{servername:e.host}:{},rejectUnauthorized:l,...l&&n?{ca:Ig(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(c),c.once("secureConnect",()=>{c.off("error",p),i(c)});let p=f=>u(new se(`TLS to ${e.host} failed: ${f.message}`));c.once("error",p)})}var pr=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=Vt(t.ssl,r,n.home??Cg()),s=o.rootCert??(r.PGSSLROOTCERT||void 0),a=o.mode==="require"&&s?"verify-ca":o.mode,l=n.connectTimeoutMs??12e4,i=[],u=()=>{for(let f of i)f.destroy()},c,p=new Promise((f,S)=>{c=setTimeout(()=>{u(),S(new se(`${t.host}:${t.port} did not answer within ${Math.round(l/1e3)} seconds`))},l)});try{return await Promise.race([(async()=>{let f=await Kg(t,a,s,v=>i.push(v)),S=new e(f,new qo(f));return await S.startup(t),S})(),p])}catch(f){throw u(),f}finally{clearTimeout(c)}}send(t,n){this.socket.write(Xc(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[Ze(s),Ze(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(Lg,4),this.socket.write(Buffer.concat([r,n]));let o=null;for(;;){let s=await this.reader.next();switch(s.type){case"R":{let a=s.body.readInt32BE(0);if(a===0)break;if(a===3)this.send("p",Ze(t.password));else if(a===5){let l=Ho("md5").update(t.password+t.user).digest("hex"),i=Ho("md5").update(Buffer.concat([Buffer.from(l),s.body.subarray(4,8)])).digest("hex");this.send("p",Ze(`md5${i}`))}else if(a===10){let l=Fg(s.body,4);if(!l.includes("SCRAM-SHA-256"))throw new se(`the server asked for ${l.join(", ")||"a password exchange"}, which this client does not speak`);o=new Ko(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([Ze("SCRAM-SHA-256"),u,i]))}else if(a===11&&o)this.send("p",Buffer.from(o.final(s.body.toString("utf8",4)),"utf8"));else if(a===12&&o)o.verify(s.body.toString("utf8",4));else throw new se(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new Qe(Zc(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",Ze(t));let r=[],o=null,s=null;for(;;){let a=await this.reader.next();switch(a.type){case"T":r=[];break;case"D":{let l=a.body.readInt16BE(0),i=[],u=2;for(let c=0;c<l;c++){let p=a.body.readInt32BE(u);u+=4,p<0?i.push(null):(i.push(a.body.toString("utf8",u,u+p)),u+=p)}r.push(i);break}case"C":o=a.body.toString("utf8",0,Math.max(0,a.body.indexOf(0)));break;case"E":s??=new Qe(Zc(a.body));break;case"G":n===void 0?this.send("f",Ze("db push has no rows for this COPY: the lines after it, ending in \\., are what it reads")):(n.length>0&&this.send("d",Buffer.from(n,"utf8")),this.send("c",Buffer.alloc(0)));break;case"W":this.send("f",Ze("COPY BOTH is not something a migration can do"));break;case"Z":if(s)throw s;return{rows:r,command:o};default:break}}}close(){this.socket.destroyed||this.socket.end(Xc("X",Buffer.alloc(0)))}};function Qc(e,t,n=1){let r=[`${e.severity}: ${e.message}`];if(e.position!==void 0){let o=[...t].slice(0,e.position-1),s=o.filter(u=>u===`
487
513
  `).length,a=t.split(`
488
514
  `)[s]??"",l=o.length-(o.lastIndexOf(`
489
515
  `)+1),i=`LINE ${n+s}: `;r.push(`${i}${a.replace(/\r$/,"")}`,`${" ".repeat(i.length+l)}^`)}return e.detail&&r.push(`DETAIL: ${e.detail}`),e.hint&&r.push(`HINT: ${e.hint}`),e.where&&r.push(`CONTEXT: ${e.where}`),r.join(`
490
- `)}var Rf=/[A-Za-z0-9_$\u0080-￿]/,Tf=/[A-Za-z_\u0080-￿]/,Of=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Nf(e){let t=Ro(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function cc(e){let t=Ro(e).trim();if(/^rollback\s+(?:(?:work|transaction)\s+)?to\b/i.test(t))return null;let n=/^(commit|end|rollback|abort|prepare\s+transaction)\b/i.exec(t);return n?n[1].toUpperCase().replace(/\s+/," "):null}function Ro(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function lc(e){let t=[],n=[],r=e.length,o=0,s=1,a=-1,l=1,i=0,u=0,c=[],p=0,f=()=>{a<0&&(a=o,l=s)},E=()=>{a=-1,i=0,u=0,c=[],p=0},k=P=>{let le=Math.min(r,o+P);for(let J=o;J<le;J++)e.charCodeAt(J)===10&&s++;o=le},X=P=>k(P<0?r-o:P-o),L=null,de=()=>{let P=a>=0?e.slice(a,o).trim():"";if(Ro(P).replace(/;/g,"").trim()!=="")if(!Nf(P))t.push({sql:P,line:l});else{let le=e.indexOf(`
491
- `,o),J=[];if(le>=0){let m=le+1,y=s+1;for(;m<r;){let b=e.indexOf(`
492
- `,m),N=e.slice(m,b<0?r:b).replace(/\r$/,"");if(m=b<0?r:b+1,y++,N==="\\.")break;J.push(`${N}
493
- `)}L={from:le+1,to:m,line:y}}t.push({sql:P,line:l,copyData:J.join("")})}E()};for(;o<r;){if(L&&o>=L.from){o=L.to,s=L.line,L=null;continue}let P=e[o],le=e[o+1];if(P===`
494
- `||P===" "||P===" "||P==="\r"||P==="\f"||P==="\v"){k(1);continue}if(P==="-"&&le==="-"){X(e.indexOf(`
495
- `,o));continue}if(P==="/"&&le==="*"){f();let J=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")J++,k(2);else if(e[o]==="*"&&e[o+1]==="/"){if(J--,k(2),J===0)break}else k(1);continue}if(P==="'"){f(),I(!1);continue}if(P==='"'){for(f(),k(1);;){let J=e.indexOf('"',o);if(J<0){X(-1);break}if(X(J+1),e[o]==='"'){k(1);continue}break}continue}if(P==="$"){f();let J=Of.exec(e.slice(o,o+64))?.[0];if(J){k(J.length);let m=e.indexOf(J,o);X(m<0?-1:m+J.length)}else k(1);continue}if(P==="\\"){let J=e.indexOf(`
496
- `,o);n.push({line:s,text:e.slice(o,J<0?r:J).replace(/\r$/,"").trimEnd()}),X(J);continue}if(P==="("){f(),i++,k(1);continue}if(P===")"){f(),i>0&&i--,k(1);continue}if(P===";"){f(),k(1),i===0&&u===0&&de();continue}if(Tf.test(P)){f();let J=o+1;for(;J<r&&Rf.test(e[J]);)J++;let m=e.slice(o,J);if(k(J-o),e[o]==="'"){I(m==="e"||m==="E");continue}Z(m.toLowerCase());continue}if(P>="0"&&P<="9"){f();let J=o+1;for(;J<r&&/[0-9A-Za-z_.]/.test(e[J]);)J++;k(J-o);continue}f(),k(1)}return de(),{statements:t,metaCommands:n};function I(P){for(k(1);o<r;){let le=e[o];if(P&&le==="\\"){k(2);continue}if(le==="'"){if(k(1),e[o]==="'"){k(1);continue}return}k(1)}}function Z(P){p<4&&["create","function","procedure","or","replace"].includes(P)&&(c[p]=P[0]),p++,!(!(c[0]==="c"&&(c[1]==="f"||c[1]==="p"||c[1]==="o"&&c[2]==="r"&&(c[3]==="f"||c[3]==="p")))||i!==0)&&(P==="begin"?u++:P==="case"?u>=1&&u++:P==="end"&&u>0&&u--)}}function uc(e){let t=Uf(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Qn="_snoutdata_migrations",Bf=`create table if not exists ${Qn} (
516
+ `)}var qg=/[A-Za-z0-9_$\u0080-￿]/,Gg=/[A-Za-z_\u0080-￿]/,zg=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Wg(e){let t=Go(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function el(e){let t=Go(e).trim();if(/^rollback\s+(?:(?:work|transaction)\s+)?to\b/i.test(t))return null;let n=/^(commit|end|rollback|abort|prepare\s+transaction)\b/i.exec(t);return n?n[1].toUpperCase().replace(/\s+/," "):null}function Go(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function tl(e){let t=[],n=[],r=e.length,o=0,s=1,a=-1,l=1,i=0,u=0,c=[],p=0,f=()=>{a<0&&(a=o,l=s)},S=()=>{a=-1,i=0,u=0,c=[],p=0},v=O=>{let pe=Math.min(r,o+O);for(let z=o;z<pe;z++)e.charCodeAt(z)===10&&s++;o=pe},V=O=>v(O<0?r-o:O-o),C=null,Y=()=>{let O=a>=0?e.slice(a,o).trim():"";if(Go(O).replace(/;/g,"").trim()!=="")if(!Wg(O))t.push({sql:O,line:l});else{let pe=e.indexOf(`
517
+ `,o),z=[];if(pe>=0){let ce=pe+1,un=s+1;for(;ce<r;){let ut=e.indexOf(`
518
+ `,ce),pn=e.slice(ce,ut<0?r:ut).replace(/\r$/,"");if(ce=ut<0?r:ut+1,un++,pn==="\\.")break;z.push(`${pn}
519
+ `)}C={from:pe+1,to:ce,line:un}}t.push({sql:O,line:l,copyData:z.join("")})}S()};for(;o<r;){if(C&&o>=C.from){o=C.to,s=C.line,C=null;continue}let O=e[o],pe=e[o+1];if(O===`
520
+ `||O===" "||O===" "||O==="\r"||O==="\f"||O==="\v"){v(1);continue}if(O==="-"&&pe==="-"){V(e.indexOf(`
521
+ `,o));continue}if(O==="/"&&pe==="*"){f();let z=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")z++,v(2);else if(e[o]==="*"&&e[o+1]==="/"){if(z--,v(2),z===0)break}else v(1);continue}if(O==="'"){f(),R(!1);continue}if(O==='"'){for(f(),v(1);;){let z=e.indexOf('"',o);if(z<0){V(-1);break}if(V(z+1),e[o]==='"'){v(1);continue}break}continue}if(O==="$"){f();let z=zg.exec(e.slice(o,o+64))?.[0];if(z){v(z.length);let ce=e.indexOf(z,o);V(ce<0?-1:ce+z.length)}else v(1);continue}if(O==="\\"){let z=e.indexOf(`
522
+ `,o);n.push({line:s,text:e.slice(o,z<0?r:z).replace(/\r$/,"").trimEnd()}),V(z);continue}if(O==="("){f(),i++,v(1);continue}if(O===")"){f(),i>0&&i--,v(1);continue}if(O===";"){f(),v(1),i===0&&u===0&&Y();continue}if(Gg.test(O)){f();let z=o+1;for(;z<r&&qg.test(e[z]);)z++;let ce=e.slice(o,z);if(v(z-o),e[o]==="'"){R(ce==="e"||ce==="E");continue}ee(ce.toLowerCase());continue}if(O>="0"&&O<="9"){f();let z=o+1;for(;z<r&&/[0-9A-Za-z_.]/.test(e[z]);)z++;v(z-o);continue}f(),v(1)}return Y(),{statements:t,metaCommands:n};function R(O){for(v(1);o<r;){let pe=e[o];if(O&&pe==="\\"){v(2);continue}if(pe==="'"){if(v(1),e[o]==="'"){v(1);continue}return}v(1)}}function ee(O){p<4&&["create","function","procedure","or","replace"].includes(O)&&(c[p]=O[0]),p++,!(!(c[0]==="c"&&(c[1]==="f"||c[1]==="p"||c[1]==="o"&&c[2]==="r"&&(c[3]==="f"||c[3]==="p")))||i!==0)&&(O==="begin"?u++:O==="case"?u>=1&&u++:O==="end"&&u>0&&u--)}}function rl(e){let t=eh(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var mr="_snoutdata_migrations",nh=`create table if not exists ${mr} (
497
523
  name text primary key,
498
524
  checksum text not null,
499
525
  applied_at timestamptz not null default now()
500
- );`;function Hf(e){if(!jf(e)||!Mf(e).isDirectory())throw new Error(`no migrations folder at ${uc(e)}. Put your .sql files there, or pass --dir.`);return If(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=To(e,t),r=Df(n,"utf8");return{name:t,path:n,sql:r,checksum:Cf("sha256").update(r,"utf8").digest("hex")}})}var qf={connection:e=>we(e),open:e=>Zn.open(e)};function pc(e,t){throw e instanceof re?new v("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Ve?new v("failed",`${t}'s database refused the connection: ${e.message}`):e}async function mc(e,t,n){try{return await e.open(t)}catch(r){return pc(r,n)}}async function Gf(e,t,n){let r=await mc(e,t,n);try{await r.query(Bf);let{rows:o}=await r.query(`select name, checksum from ${Qn};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Ve)throw new v("failed",`Could not read ${Qn} on ${n}: ${o.message}`);return pc(o,n)}finally{r.close()}}async function Kf(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Ve)return`${r.line>0?`${n}:${r.line}: `:""}${ic(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof re)return`the connection was lost: ${o.message}`;throw o}return null}function dc(e){return`'${e.replace(/'/g,"''")}'`}async function fc(e,t={}){let n=await Oo(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
501
- `);n.problems.every(o=>o.code==="psql-command"||o.code==="ends-transaction")&&U("failed",`Refused, and nothing ran:
502
- ${r}`,{ref:e,problems:n.problems}),U("conflict",`Refused. This folder and ${e} do not agree:
503
- ${r}`,{ref:e,problems:n.problems})}U("failed",`${n.failed} failed, and nothing after it was tried.
504
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return h(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function Oo(e,t={},n=()=>{},r=qf){let o=Ff(t.dir===void 0?To(process.cwd(),"migrations"):Lf(t.dir)?t.dir:To(process.cwd(),t.dir)),s=[];function a(I){s.push(I),n(I)}let l=Hf(o);if(l.length===0)return a(`No .sql files in ${uc(o)}.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};let i=await r.connection(e),u={host:i.host,port:i.port,database:i.database,user:i.user,password:i.password,ssl:i.ssl};i.wakesInstantly||a("This project is paused; the connection will wake it, which takes a few seconds.");let c=await Gf(r,u,e),p=Yn(l,c,{outOfOrder:t.outOfOrder});if(!p.ok)return{ref:e,directory:o,ok:!1,applied:[],problems:[...p.problems],notes:s};let f=new Map(l.map(I=>[I.name,I])),E=p.pending.map(I=>f.get(I.name)),k=new Map(E.map(I=>[I.name,lc(I.sql)])),X=E.flatMap(I=>k.get(I.name).metaCommands.map(Z=>({code:"psql-command",name:I.name,reason:`${I.name} line ${Z.line} is a psql command (${Z.text.length>60?`${Z.text.slice(0,57)}...`:Z.text}), which only psql runs. db push sends SQL to the database itself, so put the SQL it stands for in the file instead.`}))),L=E.filter(I=>bt(I.sql)).flatMap(I=>k.get(I.name).statements.flatMap(Z=>{let P=cc(Z.sql);return P?[{code:"ends-transaction",name:I.name,reason:`${I.name} line ${Z.line} ends the transaction itself (${P}). db push already runs each file in one transaction and records it there, so a ${P} would keep what ran before it if a later statement failed. Take it out, or put -- snoutdata:no-transaction at the top of the file to run it exactly as written.`}]:[]}));if(X.length>0||L.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:[...X,...L],notes:s};if(E.length===0)return a(`${e} is up to date: ${c.length} migration${c.length===1?"":"s"} already applied.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};if(t.dryRun){for(let I of E)bt(I.sql)||a(`${I.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:E.map(I=>I.name),notes:s}}let de=[];for(let I of E){let Z=`insert into ${Qn} (name, checksum) values (${dc(I.name)}, ${dc(I.checksum)});`,P=bt(I.sql);P||a(`${I.name} runs outside a transaction, so an interruption can leave it half applied.`);let le=[...P?[{sql:"begin;",line:0}]:[],...k.get(I.name).statements,{sql:Z,line:0},...P?[{sql:"commit;",line:0}]:[]],J=await mc(r,u,e),m;try{m=await Kf(J,le,I.name)}finally{J.close()}if(m!==null)return P&&(a("It ran inside a transaction, so nothing from it is in the database."),/cannot run inside a transaction block/i.test(m)&&a("Put -- snoutdata:no-transaction at the top of the file to run it outside one.")),{ref:e,directory:o,ok:!1,applied:de,failed:I.name,error:m,notes:s};de.push(I.name),a(` applied ${I.name}`)}return{ref:e,directory:o,ok:!0,applied:de,notes:s}}ee();he();Q();import{readFile as gc}from"node:fs/promises";function No(e){if(e==="apns"||e==="fcm")return e;throw new g("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Jf(e){let t=await S("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function hc(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new g("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new g("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await gc(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function yc(e){if(!e)throw new g("push credentials set fcm needs --file service-account.json");return{service_account:await gc(e,"utf8")}}async function zf(e,t,n){let r=await S("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Wf(e,t){return await S("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function wc(e){let t=await Jf(e);h(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${H([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
505
- `),(!n||!r)&&d(w(` snoutdata push credentials set ${n?"fcm --file service-account.json":"apns --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app"} --ref ${e}`))})}async function bc(e,t,n){let r=await zf(e,t,n);h(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function Sc(e,t){let n=await Wf(e,t);h(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}ee();pe();Q();import{spawn as cg}from"node:child_process";import{writeFileSync as lg}from"node:fs";import{isAbsolute as dg,resolve as ug}from"node:path";var Yf=" ";function M(e){return Yf.repeat(e)}function fe(e){return JSON.stringify(e)}function je(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:fe(e)}function st(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Xf(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 Zf={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"},Qf={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function eg(e,t){let n=Qf[e]?.[t];return n||(Zf[t]??"unknown")}function er(e,t){let{base:n,array:r}=Xf(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=eg(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function tg(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Co(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${M(n)}${je(t.name)}?: never`;let s=er(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${M(n)}${je(t.name)}${r?"?":""}: ${a}`}function ng(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 rg(e,t){let n=[],r=[...e.foreignKeys].sort((o,s)=>o.constraintName<s.constraintName?-1:o.constraintName>s.constraintName?1:0);if(r.length===0)return n.push(`${M(t)}Relationships: []`),n;n.push(`${M(t)}Relationships: [`);for(let o of r)n.push(`${M(t+1)}{`),n.push(`${M(t+2)}foreignKeyName: ${fe(o.constraintName)}`),n.push(`${M(t+2)}columns: [${o.columns.map(fe).join(", ")}]`),n.push(`${M(t+2)}isOneToOne: ${ng(e,o)?"true":"false"}`),n.push(`${M(t+2)}referencedRelation: ${fe(o.referencedTable)}`),n.push(`${M(t+2)}referencedColumns: [${o.referencedColumns.map(fe).join(", ")}]`),n.push(`${M(t+1)}},`);return n.push(`${M(t)}]`),n}function _c(e,t,n){let r=st(t.columns),o=[];o.push(`${M(n)}${je(t.name)}: {`),o.push(`${M(n+1)}Row: {`);for(let s of r)o.push(Co(e,s,n+2,!1));if(o.push(`${M(n+1)}}`),t.kind!=="view"){o.push(`${M(n+1)}Insert: {`);for(let s of r)o.push(Co(e,s,n+2,tg(s),!0));o.push(`${M(n+1)}}`),o.push(`${M(n+1)}Update: {`);for(let s of r)o.push(Co(e,s,n+2,!0,!0));o.push(`${M(n+1)}}`)}return o.push(...rg(t,n+1)),o.push(`${M(n)}}`),o}function Jt(e,t){return[`${M(t)}${e}: {`,`${M(t+1)}[_ in never]: never`,`${M(t)}}`]}function og(e,t){if(t.kind==="procedure"||!t.returnType)return"undefined";let n=t.returnType.trim(),r=n.toLowerCase();if(r==="void"||r==="")return"undefined";if(r.startsWith("table("))return"Record<string, unknown>[]";if(r.startsWith("setof ")){let s=n.slice(6).trim();if(s.toLowerCase()==="record")return"Record<string, unknown>[]";let a=e.rowRefs.get(s.toLowerCase());return a?`${a}[]`:`${er(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??er(e,n)}function sg(e,t,n){let r=[],o=st((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${M(n)}${je(t.name)}: {`),o.length===0)r.push(`${M(n+1)}Args: never`);else{r.push(`${M(n+1)}Args: {`);for(let s of o)r.push(`${M(n+2)}${je(s.name)}: ${er(e,s.dataType)}`);r.push(`${M(n+1)}}`)}return r.push(`${M(n+1)}Returns: ${og(e,t)}`),r.push(`${M(n)}}`),r}function St(e,t){return e.schema&&e.schema.length>0?e.schema:t}function kc(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let p of o){let f=p.schema&&p.schema.length>0?p.schema:n;s.set(p.name.toLowerCase(),`Database[${fe(f)}][${fe("Enums")}][${fe(p.name)}]`)}let a=new Map;for(let p of e.tables){let f=St(p,n),E=p.kind==="view"?"Views":"Tables";a.set(p.name.toLowerCase(),`Database[${fe(f)}][${fe(E)}][${fe(p.name)}][${fe("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let p of e.tables)i.add(St(p,n));for(let p of e.routines??[])i.add(St(p,n));for(let p of o)i.add(p.schema&&p.schema.length>0?p.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let p of u){let f=st(e.tables.filter(L=>St(L,n)===p&&L.kind!=="view")),E=st(e.tables.filter(L=>St(L,n)===p&&L.kind==="view")),k=st((e.routines??[]).filter(L=>St(L,n)===p)),X=st(o.filter(L=>(L.schema&&L.schema.length>0?L.schema:n)===p));if(c.push(`${M(1)}${je(p)}: {`),f.length===0)c.push(...Jt("Tables",2));else{c.push(`${M(2)}Tables: {`);for(let L of f)c.push(..._c(l,L,3));c.push(`${M(2)}}`)}if(E.length===0)c.push(...Jt("Views",2));else{c.push(`${M(2)}Views: {`);for(let L of E)c.push(..._c(l,L,3));c.push(`${M(2)}}`)}if(k.length===0)c.push(...Jt("Functions",2));else{c.push(`${M(2)}Functions: {`);for(let L of k)c.push(...sg(l,L,3));c.push(`${M(2)}}`)}if(X.length===0)c.push(...Jt("Enums",2));else{c.push(`${M(2)}Enums: {`);for(let L of X)c.push(`${M(3)}${je(L.name)}: ${L.values.map(fe).join(" | ")}`);c.push(`${M(2)}}`)}c.push(...Jt("CompositeTypes",2)),c.push(`${M(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(ig(n))),t.constants!==!1&&(c.push(""),c.push(ag(u,o,n))),`${c.join(`
526
+ );`;function rh(e){if(!Vg(e)||!Zg(e).isDirectory())throw new Error(`no migrations folder at ${rl(e)}. Put your .sql files there, or pass --dir.`);return Yg(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=zo(e,t),r=Xg(n,"utf8");return{name:t,path:n,sql:r,checksum:Jg("sha256").update(r,"utf8").digest("hex")}})}var oh={connection:e=>Se(e),open:e=>pr.open(e)};function ol(e,t){throw e instanceof se?new P("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Qe?new P("failed",`${t}'s database refused the connection: ${e.message}`):e}async function sl(e,t,n){try{return await e.open(t)}catch(r){return ol(r,n)}}async function sh(e,t,n){let r=await sl(e,t,n);try{await r.query(nh);let{rows:o}=await r.query(`select name, checksum from ${mr};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Qe)throw new P("failed",`Could not read ${mr} on ${n}: ${o.message}`);return ol(o,n)}finally{r.close()}}async function ah(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Qe)return`${r.line>0?`${n}:${r.line}: `:""}${Qc(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof se)return`the connection was lost: ${o.message}`;throw o}return null}function nl(e){return`'${e.replace(/'/g,"''")}'`}async function al(e,t={}){let n=await Wo(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
527
+ `);n.problems.every(o=>o.code==="psql-command"||o.code==="ends-transaction")&&H("failed",`Refused, and nothing ran:
528
+ ${r}`,{ref:e,problems:n.problems}),H("conflict",`Refused. This folder and ${e} do not agree:
529
+ ${r}`,{ref:e,problems:n.problems})}H("failed",`${n.failed} failed, and nothing after it was tried.
530
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return h(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function Wo(e,t={},n=()=>{},r=oh){let o=th(t.dir===void 0?zo(process.cwd(),"migrations"):Qg(t.dir)?t.dir:zo(process.cwd(),t.dir)),s=[];function a(R){s.push(R),n(R)}let l=rh(o);if(l.length===0)return a(`No .sql files in ${rl(o)}.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};let i=await r.connection(e),u={host:i.host,port:i.port,database:i.database,user:i.user,password:i.password,ssl:i.ssl};i.wakesInstantly||a("This project is paused; the connection will wake it, which takes a few seconds.");let c=await sh(r,u,e),p=dr(l,c,{outOfOrder:t.outOfOrder});if(!p.ok)return{ref:e,directory:o,ok:!1,applied:[],problems:[...p.problems],notes:s};let f=new Map(l.map(R=>[R.name,R])),S=p.pending.map(R=>f.get(R.name)),v=new Map(S.map(R=>[R.name,tl(R.sql)])),V=S.flatMap(R=>v.get(R.name).metaCommands.map(ee=>({code:"psql-command",name:R.name,reason:`${R.name} line ${ee.line} is a psql command (${ee.text.length>60?`${ee.text.slice(0,57)}...`:ee.text}), which only psql runs. db push sends SQL to the database itself, so put the SQL it stands for in the file instead.`}))),C=S.filter(R=>xt(R.sql)).flatMap(R=>v.get(R.name).statements.flatMap(ee=>{let O=el(ee.sql);return O?[{code:"ends-transaction",name:R.name,reason:`${R.name} line ${ee.line} ends the transaction itself (${O}). db push already runs each file in one transaction and records it there, so a ${O} would keep what ran before it if a later statement failed. Take it out, or put -- snoutdata:no-transaction at the top of the file to run it exactly as written.`}]:[]}));if(V.length>0||C.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:[...V,...C],notes:s};if(S.length===0)return a(`${e} is up to date: ${c.length} migration${c.length===1?"":"s"} already applied.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};if(t.dryRun){for(let R of S)xt(R.sql)||a(`${R.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:S.map(R=>R.name),notes:s}}let Y=[];for(let R of S){let ee=`insert into ${mr} (name, checksum) values (${nl(R.name)}, ${nl(R.checksum)});`,O=xt(R.sql);O||a(`${R.name} runs outside a transaction, so an interruption can leave it half applied.`);let pe=[...O?[{sql:"begin;",line:0}]:[],...v.get(R.name).statements,{sql:ee,line:0},...O?[{sql:"commit;",line:0}]:[]],z=await sl(r,u,e),ce;try{ce=await ah(z,pe,R.name)}finally{z.close()}if(ce!==null)return O&&(a("It ran inside a transaction, so nothing from it is in the database."),/cannot run inside a transaction block/i.test(ce)&&a("Put -- snoutdata:no-transaction at the top of the file to run it outside one.")),{ref:e,directory:o,ok:!1,applied:Y,failed:R.name,error:ce,notes:s};Y.push(R.name),a(` applied ${R.name}`)}return{ref:e,directory:o,ok:!0,applied:Y,notes:s}}re();be();te();import{readFile as il}from"node:fs/promises";function Jo(e){if(e==="apns"||e==="fcm")return e;throw new m("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function ih(e){let t=await _("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function cl(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new m("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 m("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await il(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function ll(e){if(!e)throw new m("push credentials set fcm needs --file service-account.json");return{service_account:await il(e,"utf8")}}async function ch(e,t,n){let r=await _("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function lh(e,t){return await _("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function dl(e){let t=await ih(e);h(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}`:""]])}
531
+ `),(!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 ul(e,t,n){let r=await ch(e,t,n);h(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function pl(e,t){let n=await lh(e,t);h(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}re();me();te();import{spawn as vh}from"node:child_process";import{writeFileSync as $h}from"node:fs";import{isAbsolute as xh,resolve as Ph}from"node:path";var uh=" ";function U(e){return uh.repeat(e)}function we(e){return JSON.stringify(e)}function De(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:we(e)}function lt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function ph(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 mh={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"},fh={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function gh(e,t){let n=fh[e]?.[t];return n||(mh[t]??"unknown")}function fr(e,t){let{base:n,array:r}=ph(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=gh(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function hh(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Vo(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${U(n)}${De(t.name)}?: never`;let s=fr(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${U(n)}${De(t.name)}${r?"?":""}: ${a}`}function yh(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 wh(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(`${U(t)}Relationships: []`),n;n.push(`${U(t)}Relationships: [`);for(let o of r)n.push(`${U(t+1)}{`),n.push(`${U(t+2)}foreignKeyName: ${we(o.constraintName)}`),n.push(`${U(t+2)}columns: [${o.columns.map(we).join(", ")}]`),n.push(`${U(t+2)}isOneToOne: ${yh(e,o)?"true":"false"}`),n.push(`${U(t+2)}referencedRelation: ${we(o.referencedTable)}`),n.push(`${U(t+2)}referencedColumns: [${o.referencedColumns.map(we).join(", ")}]`),n.push(`${U(t+1)}},`);return n.push(`${U(t)}]`),n}function ml(e,t,n){let r=lt(t.columns),o=[];o.push(`${U(n)}${De(t.name)}: {`),o.push(`${U(n+1)}Row: {`);for(let s of r)o.push(Vo(e,s,n+2,!1));if(o.push(`${U(n+1)}}`),t.kind!=="view"){o.push(`${U(n+1)}Insert: {`);for(let s of r)o.push(Vo(e,s,n+2,hh(s),!0));o.push(`${U(n+1)}}`),o.push(`${U(n+1)}Update: {`);for(let s of r)o.push(Vo(e,s,n+2,!0,!0));o.push(`${U(n+1)}}`)}return o.push(...wh(t,n+1)),o.push(`${U(n)}}`),o}function Qt(e,t){return[`${U(t)}${e}: {`,`${U(t+1)}[_ in never]: never`,`${U(t)}}`]}function bh(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}[]`:`${fr(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??fr(e,n)}function _h(e,t,n){let r=[],o=lt((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${U(n)}${De(t.name)}: {`),o.length===0)r.push(`${U(n+1)}Args: never`);else{r.push(`${U(n+1)}Args: {`);for(let s of o)r.push(`${U(n+2)}${De(s.name)}: ${fr(e,s.dataType)}`);r.push(`${U(n+1)}}`)}return r.push(`${U(n+1)}Returns: ${bh(e,t)}`),r.push(`${U(n)}}`),r}function Pt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function fl(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let p of o){let f=p.schema&&p.schema.length>0?p.schema:n;s.set(p.name.toLowerCase(),`Database[${we(f)}][${we("Enums")}][${we(p.name)}]`)}let a=new Map;for(let p of e.tables){let f=Pt(p,n),S=p.kind==="view"?"Views":"Tables";a.set(p.name.toLowerCase(),`Database[${we(f)}][${we(S)}][${we(p.name)}][${we("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let p of e.tables)i.add(Pt(p,n));for(let p of e.routines??[])i.add(Pt(p,n));for(let p of o)i.add(p.schema&&p.schema.length>0?p.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let p of u){let f=lt(e.tables.filter(C=>Pt(C,n)===p&&C.kind!=="view")),S=lt(e.tables.filter(C=>Pt(C,n)===p&&C.kind==="view")),v=lt((e.routines??[]).filter(C=>Pt(C,n)===p)),V=lt(o.filter(C=>(C.schema&&C.schema.length>0?C.schema:n)===p));if(c.push(`${U(1)}${De(p)}: {`),f.length===0)c.push(...Qt("Tables",2));else{c.push(`${U(2)}Tables: {`);for(let C of f)c.push(...ml(l,C,3));c.push(`${U(2)}}`)}if(S.length===0)c.push(...Qt("Views",2));else{c.push(`${U(2)}Views: {`);for(let C of S)c.push(...ml(l,C,3));c.push(`${U(2)}}`)}if(v.length===0)c.push(...Qt("Functions",2));else{c.push(`${U(2)}Functions: {`);for(let C of v)c.push(..._h(l,C,3));c.push(`${U(2)}}`)}if(V.length===0)c.push(...Qt("Enums",2));else{c.push(`${U(2)}Enums: {`);for(let C of V)c.push(`${U(3)}${De(C.name)}: ${C.values.map(we).join(" | ")}`);c.push(`${U(2)}}`)}c.push(...Qt("CompositeTypes",2)),c.push(`${U(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Sh(n))),t.constants!==!1&&(c.push(""),c.push(kh(u,o,n))),`${c.join(`
506
532
  `)}
507
- `}function ag(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=st(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${M(1)}${je(o)}: {`),r.push(`${M(2)}Enums: {},`),r.push(`${M(1)}},`);continue}r.push(`${M(1)}${je(o)}: {`),r.push(`${M(2)}Enums: {`);for(let a of s)r.push(`${M(3)}${je(a.name)}: [${a.values.map(fe).join(", ")}],`);r.push(`${M(2)}},`),r.push(`${M(1)}},`)}return r.push("} as const"),r.join(`
508
- `)}function ig(e){return`type DefaultSchemaName = Extract<keyof Database, ${fe(e)}>
533
+ `}function kh(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=lt(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${U(1)}${De(o)}: {`),r.push(`${U(2)}Enums: {},`),r.push(`${U(1)}},`);continue}r.push(`${U(1)}${De(o)}: {`),r.push(`${U(2)}Enums: {`);for(let a of s)r.push(`${U(3)}${De(a.name)}: [${a.values.map(we).join(", ")}],`);r.push(`${U(2)}},`),r.push(`${U(1)}},`)}return r.push("} as const"),r.join(`
534
+ `)}function Sh(e){return`type DefaultSchemaName = Extract<keyof Database, ${we(e)}>
509
535
 
510
536
  type InSchema = { schema: keyof Database }
511
537
 
@@ -542,7 +568,7 @@ export type Enums<
542
568
  export type CompositeTypes<
543
569
  Choice extends keyof Database[DefaultSchemaName]["CompositeTypes"] | InSchema,
544
570
  Name extends Choice extends InSchema ? keyof Database[Choice["schema"]]["CompositeTypes"] : never = never,
545
- > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function pg(e){let t=qt(e.ssl);return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:t.mode,...t.rootCert?{sslRootCert:t.rootCert}:{},label:e.ref}}function tr(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function jo(e,t){return new Promise(n=>{let r=cg("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 mg=`
571
+ > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function Eh(e){let t=Vt(e.ssl);return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:t.mode,...t.rootCert?{sslRootCert:t.rootCert}:{},label:e.ref}}function gr(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function Yo(e,t){return new Promise(n=>{let r=vh("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 Ah=`
546
572
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
547
573
  select json_build_object(
548
574
  'tables', coalesce((
@@ -646,40 +672,40 @@ select json_build_object(
646
672
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
647
673
  ), '[]'::json)
648
674
  )::text
649
- `;function fg(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function gg(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(p=>typeof p=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var hg=`select exists (
675
+ `;function Rh(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function Th(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(p=>typeof p=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var Oh=`select exists (
650
676
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
651
677
  where n.nspname = 'lepis' and c.relname = 'relation'
652
- )::text`,yg=`select coalesce(json_agg(json_build_object(
678
+ )::text`,Nh=`select coalesce(json_agg(json_build_object(
653
679
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
654
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function wg(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:bg(t)}function bg(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 Sg(e,t){let n=await e(hg);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(yg);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 _g(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?tr(e.dbUrl):pg(await we(kg(e))),r=!1,o=async k=>{if(e.sql)return e.sql(k);if(r&&e.ref)return vc(e.ref,k);let X=await jo(n,k);return X.code===127&&!e.dbUrl&&e.ref?(r=!0,vc(e.ref,k)):X},s=mg.replace("$SCHEMAS$",fg(t)),a=await o(s);a.code===127&&U("tool-missing",a.err.trim()),a.code!==0&&U("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||U("failed",`${n.label} returned nothing for the schema query`);let i=wg(l);i||U("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await Sg(o,t),{schema:c,enums:p}=gg(i,n.database,Date.now()),f=kc(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:p}),E=null;return e.out&&(E=dg(e.out)?e.out:ug(process.cwd(),e.out),lg(E,f,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(k=>k.kind!=="view").length,views:c.tables.filter(k=>k.kind==="view").length,functions:(c.routines??[]).length,enums:p.length,path:E,distribution:u,types:f}}async function vc(e,t,n=S){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function kg(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 $c(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&U("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await _g(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),h(r,()=>{r.path||process.stdout.write(r.types)}),0}pe();import{createHash as Qh}from"node:crypto";import{existsSync as ms,mkdirSync as ey,readFileSync as fs,readdirSync as ty,statSync as ny,writeFileSync as ry}from"node:fs";import{dirname as oy,isAbsolute as wd,join as tn,resolve as bd}from"node:path";import{pathToFileURL as sy}from"node:url";Q();function ay(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:wd(e)||e.startsWith("./")||e.startsWith("../")?sy(bd(e)).href:e}function iy(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(ay(e)):Promise.resolve().then(()=>(fd(),md))}function cy(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 gd="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 hd(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function nn(){let e;try{e=await iy()}catch(t){U("tool-missing",cy(t))}return{ready:async t=>{let n=await hd(e,t);return typeof n=="string"?await e.podmanReady(gd)??n:e.podmanReady(gd,n.name)},ensureImage:async(t,n)=>{let r=await hd(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 Sd(e){return tn(e,".snoutdata","local.json")}function mr(e){let t=Sd(e);if(!ms(t))return null;try{let n=JSON.parse(fs(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 ly(e,t){let n=Sd(e);return ey(oy(n),{recursive:!0}),ry(n,`${JSON.stringify(t,null,2)}
655
- `,{encoding:"utf8",mode:384}),n}var dy=54322;async function _d(e,t){let n=await e.ensureImage(t,o=>d(o));n&&U("tool-missing",n);let r=await e.ready(t);r&&U("tool-missing",r),await e.prepare()}function kd(e,t,n,r=jo){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 uy(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);await _d(e,r?.ref??null);let o=r??e.mint(t.port??dy),s=r?null:ly(n,o),a=await e.start(o,p=>d(p)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=kd(e,o,tr(l)),u=await py(i,n,t),c=await my(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function py(e,t,n){let r=bd(n.dir===void 0?tn(t,"migrations"):wd(n.dir)?n.dir:tn(t,n.dir));if(!ms(r)||!ny(r).isDirectory())return[];let o=ty(r).filter(p=>p.endsWith(".sql")).map(p=>{let f=fs(tn(r,p),"utf8");return{name:p,sql:f,checksum:Qh("sha256").update(f,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(fy);s.code!==0&&U(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${en}`);let a=await e(`select name || E'\\t' || checksum from ${en};`);a.code!==0&&U("failed",a.err.trim()||`could not read ${en}`);let l=a.out.split(`
656
- `).map(p=>p.trim()).filter(p=>p.length>0).map(p=>{let f=p.indexOf(" ");return{name:p.slice(0,f),checksum:p.slice(f+1)}}),i=Yn(o,l,{outOfOrder:n.outOfOrder});i.ok||U("conflict",`Refused. This folder and the local database do not agree:
680
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function Ih(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:Ch(t)}function Ch(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 jh(e,t){let n=await e(Oh);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(Nh);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 Dh(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?gr(e.dbUrl):Eh(await Se(Mh(e))),r=!1,o=async v=>{if(e.sql)return e.sql(v);if(r&&e.ref)return gl(e.ref,v);let V=await Yo(n,v);return V.code===127&&!e.dbUrl&&e.ref?(r=!0,gl(e.ref,v)):V},s=Ah.replace("$SCHEMAS$",Rh(t)),a=await o(s);a.code===127&&H("tool-missing",a.err.trim()),a.code!==0&&H("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||H("failed",`${n.label} returned nothing for the schema query`);let i=Ih(l);i||H("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await jh(o,t),{schema:c,enums:p}=Th(i,n.database,Date.now()),f=fl(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:p}),S=null;return e.out&&(S=xh(e.out)?e.out:Ph(process.cwd(),e.out),$h(S,f,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(v=>v.kind!=="view").length,views:c.tables.filter(v=>v.kind==="view").length,functions:(c.routines??[]).length,enums:p.length,path:S,distribution:u,types:f}}async function gl(e,t,n=_){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function Mh(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 hl(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&H("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await Dh(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),h(r,()=>{r.path||process.stdout.write(r.types)}),0}me();import{createHash as hw}from"node:crypto";import{existsSync as Ns,mkdirSync as yw,readFileSync as Is,readdirSync as ww,statSync as bw,writeFileSync as _w}from"node:fs";import{dirname as kw,isAbsolute as hu,join as ln,resolve as yu}from"node:path";import{pathToFileURL as Sw}from"node:url";te();function vw(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:hu(e)||e.startsWith("./")||e.startsWith("../")?Sw(yu(e)).href:e}function $w(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(vw(e)):Promise.resolve().then(()=>(pu(),uu))}function xw(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 mu="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 fu(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function dn(){let e;try{e=await $w()}catch(t){H("tool-missing",xw(t))}return{ready:async t=>{let n=await fu(e,t);return typeof n=="string"?await e.podmanReady(mu)??n:e.podmanReady(mu,n.name)},ensureImage:async(t,n)=>{let r=await fu(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 wu(e){return ln(e,".snoutdata","local.json")}function Er(e){let t=wu(e);if(!Ns(t))return null;try{let n=JSON.parse(Is(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 Pw(e,t){let n=wu(e);return yw(kw(n),{recursive:!0}),_w(n,`${JSON.stringify(t,null,2)}
681
+ `,{encoding:"utf8",mode:384}),n}var Ew=54322;async function bu(e,t){let n=await e.ensureImage(t,o=>d(o));n&&H("tool-missing",n);let r=await e.ready(t);r&&H("tool-missing",r),await e.prepare()}function _u(e,t,n,r=Yo){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 Aw(e,t={}){let n=t.cwd??process.cwd(),r=Er(n);await bu(e,r?.ref??null);let o=r??e.mint(t.port??Ew),s=r?null:Pw(n,o),a=await e.start(o,p=>d(p)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=_u(e,o,gr(l)),u=await Rw(i,n,t),c=await Tw(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function Rw(e,t,n){let r=yu(n.dir===void 0?ln(t,"migrations"):hu(n.dir)?n.dir:ln(t,n.dir));if(!Ns(r)||!bw(r).isDirectory())return[];let o=ww(r).filter(p=>p.endsWith(".sql")).map(p=>{let f=Is(ln(r,p),"utf8");return{name:p,sql:f,checksum:hw("sha256").update(f,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Ow);s.code!==0&&H(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${cn}`);let a=await e(`select name || E'\\t' || checksum from ${cn};`);a.code!==0&&H("failed",a.err.trim()||`could not read ${cn}`);let l=a.out.split(`
682
+ `).map(p=>p.trim()).filter(p=>p.length>0).map(p=>{let f=p.indexOf(" ");return{name:p.slice(0,f),checksum:p.slice(f+1)}}),i=dr(o,l,{outOfOrder:n.outOfOrder});i.ok||H("conflict",`Refused. This folder and the local database do not agree:
657
683
  ${i.problems.map(p=>` ${p.reason}`).join(`
658
- `)}`,{problems:i.problems});let u=new Map(o.map(p=>[p.name,p])),c=[];for(let p of i.pending){let f=u.get(p.name),E=`insert into ${en} (name, checksum) values (${yd(f.name)}, ${yd(f.checksum)});`,k=bt(f.sql);k||d(`${f.name} runs outside a transaction, so an interruption can leave it half applied.`);let X=k?`begin;
684
+ `)}`,{problems:i.problems});let u=new Map(o.map(p=>[p.name,p])),c=[];for(let p of i.pending){let f=u.get(p.name),S=`insert into ${cn} (name, checksum) values (${gu(f.name)}, ${gu(f.checksum)});`,v=xt(f.sql);v||d(`${f.name} runs outside a transaction, so an interruption can leave it half applied.`);let V=v?`begin;
659
685
  ${f.sql}
660
- ${E}
686
+ ${S}
661
687
  commit;
662
688
  `:`${f.sql}
663
- ${E}
664
- `,L=await e(X);L.code!==0&&U("failed",`${f.name} failed, and nothing after it was tried.
665
- ${L.err.trim()}`,{failed:f.name,applied:c}),c.push(f.name),d(` applied ${f.name}`)}return c}async function my(e,t,n){let r=tn(t,"seed.sql");if(!n||!ms(r))return!1;let o=await e(fs(r,"utf8"));return o.code!==0&&U("failed",`seed.sql failed.
666
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var en="_snoutdata_migrations",fy=`create table if not exists ${en} (
689
+ ${S}
690
+ `,C=await e(V);C.code!==0&&H("failed",`${f.name} failed, and nothing after it was tried.
691
+ ${C.err.trim()}`,{failed:f.name,applied:c}),c.push(f.name),d(` applied ${f.name}`)}return c}async function Tw(e,t,n){let r=ln(t,"seed.sql");if(!n||!Ns(r))return!1;let o=await e(Is(r,"utf8"));return o.code!==0&&H("failed",`seed.sql failed.
692
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var cn="_snoutdata_migrations",Ow=`create table if not exists ${cn} (
667
693
  name text primary key,
668
694
  checksum text not null,
669
695
  applied_at timestamptz not null default now()
670
- );`;function yd(e){return`'${e.replace(/'/g,"''")}'`}function gy(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 vd(e,t={}){let n=await uy(e,t);return h(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${$("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${$("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${$("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
671
- `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function $d(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);r||U("not-found","there is no local database for this folder. `snoutdata start` makes one."),await _d(e,r.ref);let o=await e.stop(r.ref);return h(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function xd(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);if(!r)return h({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return h({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return h({...s,uri:s.uri},()=>{d(` ${$("Project")} ${s.ref}`),d(` ${$("State")} ${s.ready?"running":s.status}`),d(` ${$("Port")} ${s.port}`),d(` ${$("Size")} ${gy(s.databaseBytes)}`),s.services.length>0&&d(` ${$("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
672
- `)}),0}async function Ed(e,t=process.cwd()){let n=mr(t);n||U("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:kd(e,n,tr(r))}}ee();Q();fn();Pt();Fe();import{resolve as Pd}from"node:path";function hy(){let e=n=>Le(n),t;return{shards:n=>{if(n!==void 0)return Lt({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??=Lt({}),t},whoami:()=>nt(),listProjects:async()=>({...await _e(),local:yy()}),localProject:n=>e(n),createProject:async n=>{let r=await S("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await jt(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(n,r)=>S("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>ft(n),requestExport:n=>Nt(n),resetPassword:n=>S("cloud-project-reset-password",{ref:n}),listTeams:()=>S("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Fr(s,r,o):zr(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Nn(r):Wr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Hr(o,r):Vr(n,r)},sizeFunction:(n,r,o)=>Yr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?jn(r):S("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>S("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>ht(n),deployContainer:(n,r,o)=>to(n,r,o),restartContainer:(n,r)=>ro(n,r),deleteContainer:(n,r)=>no(n,r),containerLogs:(n,r)=>Dn(n,r),containerEgress:async n=>{let r=await ht(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await Mn(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>S("cloud-token-list",{}),createToken:(n,r,o)=>S("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>S("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:Nn(r).functions.map(o=>o.name),secretNames:jn(r).secrets.map(o=>o.name)}:So(n)},getProducts:n=>yt(n),setProduct:(n,r,o)=>ho(n,r,o),listDomains:n=>Kn(n),domainAction:(n,r,o)=>yo(n,r,o),restoreWindow:n=>wo(n),restoreTo:(n,r,o)=>bo(n,r,o),setProjectState:async(n,r)=>{let o=await S(`cloud-project-${n}`,{ref:r});try{let s=await Ct(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>we(n),pushMigrations:(n,r)=>Oo(n,{dir:r.dir,dryRun:r.dryRun})}}function yy(){let e=an().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=ct(process.cwd())?.local;if(t&&!e.some(n=>Pd(n.folder)===Pd(t)))try{let n=He(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Ad="app_";async function wy(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await pn();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await zs(t)).map(o=>({name:`${Ad}${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 Rd(e){Ot(!0);let{tools:t,config:n}=await wy(),r=hy();n&&(r.callBorrowed=(i,u)=>Ws(n,i.slice(Ad.length),u));let o={...e,borrowed:t};te(t.length>0?`snoutdata mcp: ${Ht.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await nt();te(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(te(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return te("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;te(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}te(`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=Oi(s+u);s=c.rest;for(let p of c.messages)l(by(p,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function by(e,t,n){let r;try{r=JSON.parse(e)}catch{te(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ti(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
696
+ );`;function gu(e){return`'${e.replace(/'/g,"''")}'`}function Nw(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 ku(e,t={}){let n=await Aw(e,t);return h(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${x("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${x("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${x("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
697
+ `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function Su(e,t={}){let n=t.cwd??process.cwd(),r=Er(n);r||H("not-found","there is no local database for this folder. `snoutdata start` makes one."),await bu(e,r.ref);let o=await e.stop(r.ref);return h(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function vu(e,t={}){let n=t.cwd??process.cwd(),r=Er(n);if(!r)return h({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return h({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return h({...s,uri:s.uri},()=>{d(` ${x("Project")} ${s.ref}`),d(` ${x("State")} ${s.ready?"running":s.status}`),d(` ${x("Port")} ${s.port}`),d(` ${x("Size")} ${Nw(s.databaseBytes)}`),s.services.length>0&&d(` ${x("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
698
+ `)}),0}async function $u(e,t=process.cwd()){let n=Er(t);n||H("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:_u(e,n,gr(r))}}re();te();vn();It();qe();import{resolve as xu}from"node:path";function Iw(){let e=n=>Be(n),t;return{shards:n=>{if(n!==void 0)return qt({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??=qt({}),t},whoami:()=>at(),listProjects:async()=>({...await $e(),local:Cw()}),localProject:n=>e(n),createProject:async n=>{let r=await _("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Ft(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)=>_("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>bt(n),requestExport:n=>Lt(n),resetPassword:n=>_("cloud-project-reset-password",{ref:n}),listTeams:()=>_("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?eo(s,r,o):io(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Hn(r):co(n)},deleteFunction:(n,r)=>{let o=e(n);return o?no(o,r):lo(n,r)},sizeFunction:(n,r,o)=>uo(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?qn(r):_("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>_("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>kt(n),deployContainer:(n,r,o)=>ho(n,r,o),restartContainer:(n,r)=>wo(n,r),deleteContainer:(n,r)=>yo(n,r),containerLogs:(n,r)=>zn(n,r),containerEgress:async n=>{let r=await kt(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await Wn(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>_("cloud-token-list",{}),createToken:(n,r,o)=>_("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>_("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:Hn(r).functions.map(o=>o.name),secretNames:qn(r).secrets.map(o=>o.name)}:Co(n)},getProducts:n=>vt(n),setProduct:(n,r,o)=>To(n,r,o),listDomains:n=>er(n),domainAction:(n,r,o)=>Oo(n,r,o),restoreWindow:n=>No(n),restoreTo:(n,r,o)=>Io(n,r,o),setProjectState:async(n,r)=>{let o=await _(`cloud-project-${n}`,{ref:r});try{let s=await Ut(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=>Se(n),pushMigrations:(n,r)=>Wo(n,{dir:r.dir,dryRun:r.dryRun})}}function Cw(){let e=hn().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=mt(process.cwd())?.local;if(t&&!e.some(n=>xu(n.folder)===xu(t)))try{let n=ze(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Pu="app_";async function jw(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await kn();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await pa(t)).map(o=>({name:`${Pu}${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 Eu(e){Mt(!0);let{tools:t,config:n}=await jw(),r=Iw();n&&(r.callBorrowed=(i,u)=>ma(n,i.slice(Pu.length),u));let o={...e,borrowed:t};Q(t.length>0?`snoutdata mcp: ${Jt.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 at();Q(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(Q(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return Q("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;Q(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}Q(`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=Vi(s+u);s=c.rest;for(let p of c.messages)l(Dw(p,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function Dw(e,t,n){let r;try{r=JSON.parse(e)}catch{Q(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ji(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
673
699
  `)}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)}})}
674
- `)}}Pt();ee();Q();function Sy(e){return e>=1?"over":e>=.8?"near":"ok"}function Td(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:Sy(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function gs(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 Od(e,t={}){let n=await S("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Td(n.days,n.limit);return h({...n,summary:r},()=>{if(d($(`${n.name} (${n.ref})`)),!r.latest)d("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${Ue(r.storage.usedBytes)} of ${Ue(r.storage.limitBytes)} on ${n.limit?.tier}`;d(r.storage.state==="over"?`${s} (${o}%). Over the limit, so writes are refused until it shrinks or the plan changes.`:r.storage.state==="near"?`${s} (${o}%). Close to the limit: at 100% the database goes read-only.`:`${s} (${o}%).`)}else d(`${Ue(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&d("This project is currently read-only."),d(`Over ${r.days} day${r.days===1?"":"s"}: ${gs(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(w(`Peak in that window was ${Ue(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(H([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":Ue(o.dbBytes),o.repoBytes===null?"-":Ue(o.repoBytes),gs(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}he();pe();tt();Q();import{createInterface as _y}from"node:readline/promises";var ky=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),hs={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"},vy="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Nd(e,t,n=!1){let[r,o,...s]=e,a=c=>x(t,c),l=(c,p)=>{if(c===void 0||c==="")throw new g(p);return c},i,u=`${r??""}${o!==void 0&&$y(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new g("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. It joins the cluster on its own once it is ready.");return{body:O(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new g("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new g("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Ze(t,"nodes","nodes");if(!c.nodes)throw new g("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[p,f]of Object.entries(hs))Object.assign(i,Ze(t,f,p));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...Ze(t,"port","port"),...Xe(t,"dbname","dbname"),...Xe(t,"sslmode","sslmode"),...Xe(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:hr(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...Ze(t,"ranges","ranges"),...a("seed")!==void 0?{seed:xy(a("seed"))}:{},...a("nodes")?{nodes:hr(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new g("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=fr(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:ys(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=fr(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new g("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=fr(s[0],t),p=fr(s[1],t);if(c.keyspace!==p.keyspace)throw new g(`range merge: ${c.keyspace} and ${p.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:p.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],p=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(p,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Xe(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?hr(a("add")).map(f=>Ey(f,t)):[],p=Ze(t,"remove","remove");if(c.length===0&&!p.remove)throw new g("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...p};break}case"verify":i={op:"verify",...Xe(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Xe(t,"node","node")};break;default:throw new g(`unknown operation: cluster ${e.join(" ")}. One of ${vy}`)}for(let[c,p]of Object.entries(hs)){let f=Ze(t,p,c);Object.assign(i,f)}return{body:i,destructive:ky.has(i.op)}}function $y(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function hr(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Xe(e,t,n){let r=x(e,t);return r===void 0?{}:{[n]:r}}function Ze(e,t,n){let r=x(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new g(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function ys(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new g(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function xy(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new g(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function fr(e,t){let n=x(t,"keyspace"),r=x(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new g(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:ys(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new g("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:ys(o,"--range")}}function Ey(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new g(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Xe(t,"dbname","dbname"),...Xe(t,"sslmode","sslmode")}}function gr(e,t){if(e===void 0||!/^\d+$/.test(e))throw new g(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Id(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=x(e,"project")??x(e,"ref"),l=a!==void 0,i=()=>t.client??Lt({project:a,admin:x(e,"admin"),token:x(e,"token")});switch(r){case"enable":{if(!l)throw new g("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof Ce))throw new g("cluster enable needs --project <ref>");if(O(e,"plan")){let f=await c.enable(!0);return h(f,()=>ve(f,process.stdout)),0}if(!O(e,"yes")){if(!t.confirm&&!me())throw new g("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.enable(!0),process.stderr),!await(t.confirm??xt)(`Make a cluster of ${c.target}?`))throw new v("failed","Not turned on. Nothing was changed.")}let p=await c.enable(!1);return h(p,()=>d(Je(p))),0}case"disable":{if(!l)throw new g("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof Ce))throw new g("cluster disable needs --project <ref>");if(O(e,"plan")){let f=await c.disable(!0);return h(f,()=>ve(f,process.stdout)),0}if(!O(e,"yes")){if(!t.confirm&&!me())throw new g("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.disable(!0),process.stderr),!await(t.confirm??xt)(`Turn the cluster of ${c.target} off?`))throw new v("failed","Not turned off. Nothing was changed.")}let p=await c.disable(!1);return h(p,()=>d(Je(p))),0}case"spread":{if(!l)throw new g("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof Ce))throw new g("cluster spread needs --project <ref>");let p={table:o??$t(x(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:$t(x(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:$t(x(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(O(e,"plan")){let E=await c.spread(p,!0);return h(E,()=>ve(E,process.stdout)),0}if(!O(e,"yes")){if(!t.confirm&&!me())throw new g("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.spread(p,!0),process.stderr),!await(t.confirm??xt)(`Spread ${p.table} over ${c.target}?`))throw new v("failed","Not spread. Nothing was changed.")}let f=await c.spread(p,!1);return h(f,()=>d(Je(f))),0}case"retry":{if(!l)throw new g("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof Ce))throw new g("cluster retry needs --project <ref>");let p=O(e,"queue")?await c.retry({queue:!0}):await c.retry({node:$t(o??x(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return h(p,()=>d(Je(p))),0}case"replica":{if(!l)throw new g("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new g("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof Ce))throw new g("cluster replica needs --project <ref>");let p=o==="remove",f=$t(s??x(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(O(e,"plan")){let k=await c.replica(f,p,!0);return h(k,()=>ve(k,process.stdout)),0}if(!O(e,"yes")){if(!t.confirm&&!me())throw new g(p?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.replica(f,p,!0),process.stderr),!await(t.confirm??xt)(p?`Remove the replica of node ${f}?`:`Add a replica to node ${f}?`))throw new v("failed","Nothing was changed.")}let E=await c.replica(f,p,!1);return h(E,()=>d(Je(E))),0}case"alerts":{if(!l)throw new g("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof Ce))throw new g("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let p=await c.alerts();return h({alerts:p},()=>Md(p,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let p=$t(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(p))throw new g(`not an alert id: ${p} (as cluster alerts lists it)`);let f=await c.acknowledge(p.toLowerCase());return h(f,()=>d(Je(f))),0}throw new g(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return h(c,()=>Cy(c)),0}case"ops":{let c=await i().ops();return h(c,()=>{process.stdout.write(`${c.ops.join(`
700
+ `)}}It();re();te();function Mw(e){return e>=1?"over":e>=.8?"near":"ok"}function Au(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:Mw(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Cs(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 Ru(e,t={}){let n=await _("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Au(n.days,n.limit);return h({...n,summary:r},()=>{if(d(x(`${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=`${He(r.storage.usedBytes)} of ${He(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(`${He(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"}: ${Cs(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 ${He(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?"-":He(o.dbBytes),o.repoBytes===null?"-":He(o.repoBytes),Cs(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}be();me();st();te();import{createInterface as Lw}from"node:readline/promises";var Uw=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),js={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"},Fw="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Tu(e,t,n=!1){let[r,o,...s]=e,a=c=>k(t,c),l=(c,p)=>{if(c===void 0||c==="")throw new m(p);return c},i,u=`${r??""}${o!==void 0&&Bw(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new m("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:N(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new m("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 m("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=rt(t,"nodes","nodes");if(!c.nodes)throw new m("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[p,f]of Object.entries(js))Object.assign(i,rt(t,f,p));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...rt(t,"port","port"),...nt(t,"dbname","dbname"),...nt(t,"sslmode","sslmode"),...nt(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:Tr(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, ...)"),...rt(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Hw(a("seed"))}:{},...a("nodes")?{nodes:Tr(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 m("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=Ar(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:Ds(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=Ar(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 m("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=Ar(s[0],t),p=Ar(s[1],t);if(c.keyspace!==p.keyspace)throw new m(`range merge: ${c.keyspace} and ${p.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:p.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],p=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(p,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...nt(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?Tr(a("add")).map(f=>Kw(f,t)):[],p=rt(t,"remove","remove");if(c.length===0&&!p.remove)throw new m("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...p};break}case"verify":i={op:"verify",...nt(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...nt(t,"node","node")};break;default:throw new m(`unknown operation: cluster ${e.join(" ")}. One of ${Fw}`)}for(let[c,p]of Object.entries(js)){let f=rt(t,p,c);Object.assign(i,f)}return{body:i,destructive:Uw.has(i.op)}}function Bw(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function Tr(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function nt(e,t,n){let r=k(e,t);return r===void 0?{}:{[n]:r}}function rt(e,t,n){let r=k(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new m(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Ds(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new m(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function Hw(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new m(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Ar(e,t){let n=k(t,"keyspace"),r=k(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new m(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Ds(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new m("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Ds(o,"--range")}}function Kw(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new m(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...nt(t,"dbname","dbname"),...nt(t,"sslmode","sslmode")}}function Rr(e,t){if(e===void 0||!/^\d+$/.test(e))throw new m(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Iu(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=k(e,"project")??k(e,"ref"),l=a!==void 0,i=()=>t.client??qt({project:a,admin:k(e,"admin"),token:k(e,"token")});switch(r){case"enable":{if(!l)throw new m("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 je))throw new m("cluster enable needs --project <ref>");if(N(e,"plan")){let f=await c.enable(!0);return h(f,()=>Pe(f,process.stdout)),0}if(!N(e,"yes")){if(!t.confirm&&!ye())throw new m("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(Pe(await c.enable(!0),process.stderr),!await(t.confirm??Ot)(`Make a cluster of ${c.target}?`))throw new P("failed","Not turned on. Nothing was changed.")}let p=await c.enable(!1);return h(p,()=>d(Ye(p))),0}case"disable":{if(!l)throw new m("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 je))throw new m("cluster disable needs --project <ref>");if(N(e,"plan")){let f=await c.disable(!0);return h(f,()=>Pe(f,process.stdout)),0}if(!N(e,"yes")){if(!t.confirm&&!ye())throw new m("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(Pe(await c.disable(!0),process.stderr),!await(t.confirm??Ot)(`Turn the cluster of ${c.target} off?`))throw new P("failed","Not turned off. Nothing was changed.")}let p=await c.disable(!1);return h(p,()=>d(Ye(p))),0}case"spread":{if(!l)throw new m("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 je))throw new m("cluster spread needs --project <ref>");let p={table:o??Tt(k(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:Tt(k(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:Tt(k(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(N(e,"plan")){let S=await c.spread(p,!0);return h(S,()=>Pe(S,process.stdout)),0}if(!N(e,"yes")){if(!t.confirm&&!ye())throw new m("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(Pe(await c.spread(p,!0),process.stderr),!await(t.confirm??Ot)(`Spread ${p.table} over ${c.target}?`))throw new P("failed","Not spread. Nothing was changed.")}let f=await c.spread(p,!1);return h(f,()=>d(Ye(f))),0}case"retry":{if(!l)throw new m("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 je))throw new m("cluster retry needs --project <ref>");let p=N(e,"queue")?await c.retry({queue:!0}):await c.retry({node:Tt(o??k(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return h(p,()=>d(Ye(p))),0}case"replica":{if(!l)throw new m("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new m("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof je))throw new m("cluster replica needs --project <ref>");let p=o==="remove",f=Tt(s??k(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(N(e,"plan")){let v=await c.replica(f,p,!0);return h(v,()=>Pe(v,process.stdout)),0}if(!N(e,"yes")){if(!t.confirm&&!ye())throw new m(p?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(Pe(await c.replica(f,p,!0),process.stderr),!await(t.confirm??Ot)(p?`Remove the replica of node ${f}?`:`Add a replica to node ${f}?`))throw new P("failed","Nothing was changed.")}let S=await c.replica(f,p,!1);return h(S,()=>d(Ye(S))),0}case"alerts":{if(!l)throw new m("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof je))throw new m("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let p=await c.alerts();return h({alerts:p},()=>ju(p,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let p=Tt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(p))throw new m(`not an alert id: ${p} (as cluster alerts lists it)`);let f=await c.acknowledge(p.toLowerCase());return h(f,()=>d(Ye(f))),0}throw new m(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return h(c,()=>Yw(c)),0}case"ops":{let c=await i().ops();return h(c,()=>{process.stdout.write(`${c.ops.join(`
675
701
  `)}
676
- `),d(w(`Default settings: ${Object.entries(c.settings).map(([p,f])=>`${p}=${f}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(p=>`${p.kind} (${p.status})`).join(", ")}`))}),0}case"settings":return Ry(e,i);case"advise":case"advice":{let c=Ze(e,"sample-ms","sampleMs").sampleMs,p=await i().advice(c);return h(p,()=>Ny(p,process.stdout)),0}case"jobs":return Ty(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return h({nodes:c.nodes??[]},()=>Dd(c)),0}break;case"plan":{let c=Nd(n.slice(1),e,l),p=await i().plan(c.body);return h(p,()=>ve(p,process.stdout)),0}}let u=Nd(n,e,l);if(O(e,"plan")){let c=await i().plan(u.body);return h(c,()=>ve(c,process.stdout)),0}return Py(u,e,i,t)}async function Py(e,t,n,r){let o=O(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!me())throw new g(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(ve(u,process.stderr),!await(r.confirm??xt)(`Run ${s} on ${a.target}?`))throw new v("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return h(l.answer,()=>d(l.done)),0;if(O(t,"no-wait"))return h({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
677
- `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await ws(a,l.job,r);return bs(i)}function $t(e,t){if(e===void 0||e==="")throw new g(t);return e}async function xt(e){let t=_y({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 Ay(e){let t=[...co,...xi],n={};for(let r of hr(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new g(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new g(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function Ry(e,t){let n={};for(let s of co)Object.assign(n,Ze(e,hs[s],s));let r=x(e,"set");if(r!==void 0&&Object.assign(n,Ay(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return h({settings:a},()=>process.stdout.write(`${H(Object.entries(a).map(([l,i])=>[l,String(i)]))}
678
- `)),0}let o=await t().settings(n);return h(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function Ty(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return h({jobs:s},()=>Uy(s)),0}case"show":{let s=await r.job(gr(t,"show"));return h(s,()=>Fy(s)),0}case"watch":{let s=await ws(r,gr(t,"watch"),o);return bs(s)}case"cancel":{let s=gr(t,"cancel");if(!O(n,"yes")){if(!o.confirm&&!me())throw new g(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??xt)(`Cancel job ${s} on ${r.target}?`))throw new v("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return h(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=gr(t,"resume"),a=await r.resume(s);return O(n,"no-wait")?(h(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):bs(await ws(r,s,o))}default:throw new g(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function ws(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(Mt(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 bs(e){if(e.state==="done")return h(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
679
- `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata cluster jobs resume ${e.id}`:"was cancelled";throw new v("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function C(e,t=""){e.write(`${t}
680
- `)}function Ss(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function ve(e,t){C(t,`${$(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&C(t,H([[" 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")}`,Ss(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),C(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Ss(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)`),C(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)C(t,` warning: ${n}`);e.assumptions&&C(t,w(` ${e.assumptions}`))}function Pe(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function Oy(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Pe(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Pe(t.at)}`:""}${t.to!==void 0?` --to ${Pe(t.to)}`:""}`;case"range.move":return`${n} range move ${Pe(`${t.keyspace}:${t.range}`)} --to ${Pe(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Pe(t.keyspace)} --value ${Pe(t.value)}${t.node!==void 0?` --node ${Pe(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Pe(t.name)} --host ${t.host?Pe(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Pe(t.keyspace)}`:""}`;default:return null}}function Ny(e,t){C(t,e.summary),e.advice.forEach((n,r)=>{C(t),C(t,`${$(`${r+1}. ${n.op}`)} (${n.metric})`),C(t,` ${n.reason}`);let o=Oy(n.request);if(C(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;C(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Ss(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])C(t,` warning: ${a}`)}else n.needs.length>0?C(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&C(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(C(t),C(t,w(e.assumptions)))}function Cd(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Dd(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}C(process.stdout,H([["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 Cy(e){let t=process.stdout;if(e.cloud&&(Ly(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){C(t,"The home node has no Lepis catalog yet (schema lepis).");return}C(t,`${$("Cluster")} at epoch ${e.epoch}`),C(t),Dd(e);for(let s of e.keyspaces??[]){C(t),C(t,`${$(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),C(t,H([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Cd(e,a.node)])]));for(let a of s.pins)C(t,` pinned: ${a.value} on ${Cd(e,a.node)}`)}let n=e.tables??[];n.length>0&&(C(t),C(t,H([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(C(t),C(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&&C(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 jy(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 Iy(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`;case"held":return"failover held: its replica was behind and its last writes could not be copied to it, so it was not promoted (see cluster alerts)"}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function jd(e){return e.severity.toUpperCase()}function Md(e,t,n,r="<ref>"){let o=e.filter(a=>a.resolvedAt===null),s=e.filter(a=>a.resolvedAt!==null);if(!n){for(let a of o)C(t,`${$(jd(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&C(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)C(t,`${$(jd(a))} ${a.message}`),C(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&C(t),C(t,"Resolved in the last day:");for(let a of s)C(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Dy(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function My(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 Ly(e,t){let n=My(e);if(!e.sharded){C(t,`${$(e.ref)} is not a cluster (${n}).`),Dy(e)>0&&C(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}Md(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(l=>l.desiredState!=="deleted"),o=1+r.filter(l=>!l.replicaOf).length,s=r.length-(o-1);if(C(t,`${$(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let l=e.pods.some(i=>i.replicaOf!==void 0);C(t,H([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,jy(e,i),...l?[Iy(i)]:[]])]))}e.router==="starting"?C(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&C(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&C(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?C(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&C(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),C(t)}function Uy(e){if(e.length===0){d("No jobs yet.");return}C(process.stdout,H([["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 Fy(e){let t=process.stdout;C(t,`${$(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),C(t,H([[" 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)&&(C(t),ve(e.plan,t))}pe();Q();Rt();import{spawn as By}from"node:child_process";import{createHash as Hy}from"node:crypto";import{chmodSync as qy,renameSync as yr,rmSync as Ld,writeFileSync as Gy}from"node:fs";import{dirname as Ky,join as Jy}from"node:path";var wr=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",zy="https://github.com/snoutdata/studio/releases/download";function Wy(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 Vy(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 Yy(){let e;try{e=await fetch(`${wr}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new v("network",`could not reach ${wr} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new v("server",`${wr} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new v("server",`${wr} did not say which version is newest`);return t.version}async function Ud(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 Xy(e,t){let n=Jy(Ky(e),`.snoutdata-upgrade-${process.pid}`);Gy(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Ld(r,{force:!0}),yr(e,r);try{yr(n,e)}catch(o){throw yr(r,e),o}}else qy(n,493),yr(n,e)}catch(r){Ld(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new v("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?De:At}`):r}}function Zy(){return new Promise((e,t)=>{let n=By(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new v("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${De}`))),n.on("exit",r=>e(r??1))})}async function Fd(e={}){let t=e.kind??bn(),n=await Yy(),r=Xs(ie,n),o={installed:ie,latest:n,upToDate:!r,install:t};if(!r||e.check)return h({...o,upgraded:!1},()=>{d(r?`snoutdata ${ie} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ie} is the newest version.`)}),0;switch(t){case"npx":return h({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ie}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new v("usage",`this snoutdata (${ie}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?De:At}`);case"npm":{d(`Upgrading snoutdata ${ie} to ${n} with npm\u2026`);let s=await Zy();if(s!==0)throw new v("failed",`npm exited ${s}. Run it yourself: ${De}`);return h({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Wy(process.platform,process.arch);if(!s)throw new v("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${De}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${zy}/cli-v${n}`,[l,i]=await Promise.all([Ud(`${a}/${s}`),Ud(`${a}/SHA256SUMS`)]),u=Vy(i.toString("utf8"),s);if(!u)throw new v("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=Hy("sha256").update(l).digest("hex");if(c!==u)throw new v("failed",`checksum mismatch for ${s}. Refusing to install.
702
+ `),d(w(`Default settings: ${Object.entries(c.settings).map(([p,f])=>`${p}=${f}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(p=>`${p.kind} (${p.status})`).join(", ")}`))}),0}case"settings":return zw(e,i);case"advise":case"advice":{let c=rt(e,"sample-ms","sampleMs").sampleMs,p=await i().advice(c);return h(p,()=>Vw(p,process.stdout)),0}case"jobs":return Ww(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return h({nodes:c.nodes??[]},()=>Cu(c)),0}break;case"plan":{let c=Tu(n.slice(1),e,l),p=await i().plan(c.body);return h(p,()=>Pe(p,process.stdout)),0}}let u=Tu(n,e,l);if(N(e,"plan")){let c=await i().plan(u.body);return h(c,()=>Pe(c,process.stdout)),0}return qw(u,e,i,t)}async function qw(e,t,n,r){let o=N(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!ye())throw new m(`${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(Pe(u,process.stderr),!await(r.confirm??Ot)(`Run ${s} on ${a.target}?`))throw new P("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return h(l.answer,()=>d(l.done)),0;if(N(t,"no-wait"))return h({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
703
+ `),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 Ms(a,l.job,r);return Ls(i)}function Tt(e,t){if(e===void 0||e==="")throw new m(t);return e}async function Ot(e){let t=Lw({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 Gw(e){let t=[...vo,...Ki],n={};for(let r of Tr(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 m(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new m(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function zw(e,t){let n={};for(let s of vo)Object.assign(n,rt(e,js[s],s));let r=k(e,"set");if(r!==void 0&&Object.assign(n,Gw(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return h({settings:a},()=>process.stdout.write(`${L(Object.entries(a).map(([l,i])=>[l,String(i)]))}
704
+ `)),0}let o=await t().settings(n);return h(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function Ww(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return h({jobs:s},()=>nb(s)),0}case"show":{let s=await r.job(Rr(t,"show"));return h(s,()=>rb(s)),0}case"watch":{let s=await Ms(r,Rr(t,"watch"),o);return Ls(s)}case"cancel":{let s=Rr(t,"cancel");if(!N(n,"yes")){if(!o.confirm&&!ye())throw new m(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??Ot)(`Cancel job ${s} on ${r.target}?`))throw new P("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return h(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Rr(t,"resume"),a=await r.resume(s);return N(n,"no-wait")?(h(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):Ls(await Ms(r,s,o))}default:throw new m(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Ms(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(Kt(a.state))return a;if(Date.now()>=o)throw new P("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 Ls(e){if(e.state==="done")return h(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
705
+ `)),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 P("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function D(e,t=""){e.write(`${t}
706
+ `)}function Us(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 Pe(e,t){D(t,`${x(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&D(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")}`,Us(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),D(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Us(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)`),D(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)D(t,` warning: ${n}`);e.assumptions&&D(t,w(` ${e.assumptions}`))}function Te(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function Jw(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Te(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Te(t.at)}`:""}${t.to!==void 0?` --to ${Te(t.to)}`:""}`;case"range.move":return`${n} range move ${Te(`${t.keyspace}:${t.range}`)} --to ${Te(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Te(t.keyspace)} --value ${Te(t.value)}${t.node!==void 0?` --node ${Te(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Te(t.name)} --host ${t.host?Te(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Te(t.keyspace)}`:""}`;default:return null}}function Vw(e,t){D(t,e.summary),e.advice.forEach((n,r)=>{D(t),D(t,`${x(`${r+1}. ${n.op}`)} (${n.metric})`),D(t,` ${n.reason}`);let o=Jw(n.request);if(D(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;D(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Us(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])D(t,` warning: ${a}`)}else n.needs.length>0?D(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&D(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(D(t),D(t,w(e.assumptions)))}function Ou(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Cu(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}D(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 Yw(e){let t=process.stdout;if(e.cloud&&(tb(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){D(t,"The home node has no Lepis catalog yet (schema lepis).");return}D(t,`${x("Cluster")} at epoch ${e.epoch}`),D(t),Cu(e);for(let s of e.keyspaces??[]){D(t),D(t,`${x(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),D(t,L([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Ou(e,a.node)])]));for(let a of s.pins)D(t,` pinned: ${a.value} on ${Ou(e,a.node)}`)}let n=e.tables??[];n.length>0&&(D(t),D(t,L([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(D(t),D(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&&D(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 Xw(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 Zw(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`;case"held":return"failover held: its replica was behind and its last writes could not be copied to it, so it was not promoted (see cluster alerts)"}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function Nu(e){return e.severity.toUpperCase()}function ju(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)D(t,`${x(Nu(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&D(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)D(t,`${x(Nu(a))} ${a.message}`),D(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&D(t),D(t,"Resolved in the last day:");for(let a of s)D(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Qw(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function eb(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 tb(e,t){let n=eb(e);if(!e.sharded){D(t,`${x(e.ref)} is not a cluster (${n}).`),Qw(e)>0&&D(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}ju(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(D(t,`${x(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);D(t,L([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,Xw(e,i),...l?[Zw(i)]:[]])]))}e.router==="starting"?D(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&D(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&D(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?D(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&D(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),D(t)}function nb(e){if(e.length===0){d("No jobs yet.");return}D(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 rb(e){let t=process.stdout;D(t,`${x(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),D(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)&&(D(t),Pe(e.plan,t))}me();te();jt();import{spawn as ob}from"node:child_process";import{createHash as sb}from"node:crypto";import{chmodSync as ab,renameSync as Or,rmSync as Du,writeFileSync as ib}from"node:fs";import{dirname as cb,join as lb}from"node:path";var Nr=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",db="https://github.com/snoutdata/studio/releases/download";function ub(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 pb(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 mb(){let e;try{e=await fetch(`${Nr}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new P("network",`could not reach ${Nr} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new P("server",`${Nr} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new P("server",`${Nr} did not say which version is newest`);return t.version}async function Mu(e){let t;try{t=await fetch(e)}catch(n){throw new P("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new P("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function fb(e,t){let n=lb(cb(e),`.snoutdata-upgrade-${process.pid}`);ib(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Du(r,{force:!0}),Or(e,r);try{Or(n,e)}catch(o){throw Or(r,e),o}}else ab(n,493),Or(n,e)}catch(r){Du(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new P("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?Ue:Ct}`):r}}function gb(){return new Promise((e,t)=>{let n=ob(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new P("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${Ue}`))),n.on("exit",r=>e(r??1))})}async function Lu(e={}){let t=e.kind??An(),n=await mb(),r=ha(ue,n),o={installed:ue,latest:n,upToDate:!r,install:t};if(!r||e.check)return h({...o,upgraded:!1},()=>{d(r?`snoutdata ${ue} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ue} is the newest version.`)}),0;switch(t){case"npx":return h({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ue}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new P("usage",`this snoutdata (${ue}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?Ue:Ct}`);case"npm":{d(`Upgrading snoutdata ${ue} to ${n} with npm\u2026`);let s=await gb();if(s!==0)throw new P("failed",`npm exited ${s}. Run it yourself: ${Ue}`);return h({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=ub(process.platform,process.arch);if(!s)throw new P("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${Ue}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${db}/cli-v${n}`,[l,i]=await Promise.all([Mu(`${a}/${s}`),Mu(`${a}/SHA256SUMS`)]),u=pb(i.toString("utf8"),s);if(!u)throw new P("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=sb("sha256").update(l).digest("hex");if(c!==u)throw new P("failed",`checksum mismatch for ${s}. Refusing to install.
681
707
  expected ${u}
682
- actual ${c}`);return Xy(process.execPath,l),h({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Pn();Fe();Rt();var Bd=`snoutdata ${ie} \u2014 hosted Postgres, from a terminal or an agent
708
+ actual ${c}`);return fb(process.execPath,l),h({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Dn();qe();jt();var Uu=`snoutdata ${ue} \u2014 hosted Postgres, from a terminal or an agent
683
709
 
684
710
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
685
711
  --env writes DATABASE_URL into ./.env (and adds
@@ -730,6 +756,22 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var en="_snoutdata_migrations",fy=`crea
730
756
  snoutdata realtime logs [--since 10m] [--channel C] [--ref R]
731
757
  connects, joins, leaves and disconnects, with why
732
758
 
759
+ snoutdata ai [--ref R] the project's own AI gateway: on or off, its models,
760
+ budgets and the last 30 days' spend
761
+ snoutdata ai enable|disable switch it (on restarts the database once)
762
+ snoutdata ai models [add|remove] what it serves; add one with its provider and key:
763
+ \u2026 | snoutdata ai models add [NAME] --source openai --model gpt-4o-mini --stdin
764
+ the key on stdin, proven with the provider first;
765
+ without --model, the steps to find the key and id
766
+ snoutdata ai providers [remove NAME] where its models run, and whether their keys are set
767
+ snoutdata ai routes [set NAME --models a,b [--sort price] | remove NAME]
768
+ one name for several models, tried in turn
769
+ snoutdata ai keys [create --name N [--models a,b] [--expires DAYS] | revoke ID]
770
+ keys for a server, the key shown once
771
+ snoutdata ai budgets [set project|users --amount 5 [--period day|month] [--soft] | remove ID]
772
+ snoutdata ai usage [--by model|day|key|user|provider] [--days 30]
773
+ snoutdata ai test [--model M] [--prompt TEXT] one call through the project's door
774
+
733
775
  snoutdata domains [--ref R] your own domains in front of the project's API
734
776
  snoutdata domains add|verify|remove <hostname> [--ref R]
735
777
 
@@ -837,11 +879,11 @@ that re-signs Postgres traffic.
837
879
  Docs: https://docs.snoutdata.com/developers/cli
838
880
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
839
881
  Source: https://github.com/snoutdata/snout-cli
840
- `,Qy=["yes","token"];async function ew(e){let[t,n,...r]=e.command;if(O(e,"version")||t==="version")return h({version:ie},()=>process.stdout.write(`${ie}
841
- `)),0;if(O(e,"help")&&t&&t!=="help"){let i=Es(t,n);if(i.length>0)return h({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,_r(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",p=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${p}
882
+ `,hb=["yes","token"];async function yb(e){let[t,n,...r]=e.command;if(N(e,"version")||t==="version")return h({version:ue},()=>process.stdout.write(`${ue}
883
+ `)),0;if(N(e,"help")&&t&&t!=="help"){let i=Gs(t,n);if(i.length>0)return h({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,jr(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",p=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${p}
842
884
  ${u.summary}
843
- `);let f=Math.max(0,...u.flags.map(E=>E.length));for(let E of u.flags)process.stdout.write(` ${E.padEnd(f)} ${_r(u.name,E)}
885
+ `);let f=Math.max(0,...u.flags.map(S=>S.length));for(let S of u.flags)process.stdout.write(` ${S.padEnd(f)} ${jr(u.name,S)}
844
886
  `)}process.stdout.write(`
845
887
  Every command also takes --json, --quiet and --help.
846
- `)}),0}let o=O(e,"help")||t==="help";if(o||!t)return h({version:ie,usage:Bd.trim(),commands:Sr},()=>process.stdout.write(Bd)),o?0:Ie.usage;let s=()=>{let i=$e(e,"timeout");if(i!==void 0){if(!(i>0))throw new g("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=sn({flag:x(e,"ref")});if(!i)throw new g("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=sn({flag:x(e,"ref")});return i?Le(i):null};for(let i of Qy)if(e.flags[i]!==void 0&&t!=="cluster")throw new g(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Id(e,{timeoutMs:s()});case"login":{if(O(e,"device")){let p=await Sn({name:mn(),platform:process.platform});h({verifyUrl:p.verifyUrl,code:p.code,expiresAt:p.expiresAt},()=>{d(""),d(` Open ${p.verifyUrl}`),d(` Type ${p.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let f=await _n(p.secret,{intervalMs:Math.max(1,p.interval)*1e3,expiresAt:p.expiresAt});Ae({accessToken:f.token});let E=x(e,"email"),k=E?(await nt()).email??void 0:void 0;return E&&Dr(E,{accessToken:f.token,email:k}),h({ok:!0,name:f.name??null,email:k??null},()=>d(`Signed in${k?` as ${k}`:""}.`)),0}let i=x(e,"domain"),u=O(e,"sso")||i!==void 0;if(u&&x(e,"provider"))throw new g("--sso and --provider are two different doors: pass one or the other");let c=await En({provider:x(e,"provider"),sso:u,domain:i,noBrowser:O(e,"no-browser")||!me(),timeoutMs:s(),email:x(e,"email")});return h({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{Ns();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return h({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await nt(),u=on()?.expectedEmail,c=!!(u&&i.email&&!Mr(u,i.email));return c&&te(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),h({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
847
- `),i.token&&d(`via access token ${i.token.prefix} (${i.token.name})${i.token.project?`, limited to project ${i.token.project}`:""}.`)}),0}case"tokens":switch(n){case void 0:case"list":return await La(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("tokens create needs --name (what is it for, and where does it live)");return await Ma({name:i,expiresInDays:$e(e,"expires"),project:x(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new g("tokens revoke needs the token's id or its sdt_ prefix");return await Ua(i),0}default:throw new g(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Na(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("projects create needs --name");let u=x(e,"team");return await Gr({teamId:u?await Da(u):void 0,name:i,region:x(e,"region"),wait:!O(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:O(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Ca(n,x(e,"ref")??r[0]??a(),{wait:!O(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=x(e,"ref")??r[0]??a(),u=Le(i);u?ha(u):await Ui(i)}return 0;default:throw new g(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await xa(i,r[0],{dir:x(e,"dir"),verifyJwt:!O(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Ea(i),0;if((n==="delete"||n==="remove")&&r[0])return await Pa(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await Ja(a(),u,{dir:x(e,"dir"),entrypoint:x(e,"entrypoint"),verifyJwt:!O(e,"no-verify-jwt")}),0}case void 0:case"list":return await Va(a()),0;case"size":{let u=r[0];if(!u)throw new g("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await Wa(a(),u,{memoryMb:$e(e,"memory"),concurrency:$e(e,"concurrency"),reset:O(e,"reset")}),0}case"logs":return await Ya(a(),r[0],{errorsOnly:O(e,"errors"),limit:$e(e,"tail"),follow:O(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new g("functions delete needs a name");return await Xa(a(),u),0}default:throw new g(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=x(e,"dockerfile");if(i&&c){if(u)throw new g("containers deploy takes an image or --dockerfile, not both");if(x(e,"registry-user")||O(e,"clear-registry"))throw new g("an image built with --dockerfile is uploaded to the project and needs no registry login");return await li(a(),i,{path:c,port:$e(e,"port"),wait:!O(e,"no-wait")}),0}if(!i||!u)throw new g("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let p=x(e,"registry-user");if(p&&!O(e,"stdin"))throw new g('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await ai(a(),i,{image:u,port:$e(e,"port"),registryUser:p,registryPassword:p?await Fn():void 0,clearCredential:O(e,"clear-registry"),wait:!O(e,"no-wait")}),0}case void 0:case"list":return await di(a()),0;case"logs":return await mi(a(),r[0],{errorsOnly:O(e,"errors"),limit:$e(e,"tail"),follow:O(e,"follow")}),0;case"restart":{if(!r[0])throw new g("containers restart needs a name");return await ui(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new g("containers delete needs a name");return await pi(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await Un(a(),{}),0;if(i==="clear")return await Un(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!O(e,"none"))throw new g("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&O(e,"none"))throw new g("containers egress set takes destinations or --none, not both");return await Un(a(),{set:u}),0}throw new g(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new g(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await Ra(i,await hi(r,{stdin:O(e,"stdin")})),0;if(n===void 0||n==="list")return Ta(i),0;if((n==="unset"||n==="remove")&&r[0])return await Oa(i,r[0]),0}switch(n){case"set":return await yi(a(),r,{stdin:O(e,"stdin")}),0;case void 0:case"list":return await bi(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new g("secrets unset needs a name");return await wi(a(),u),0}default:throw new g(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await Fa(a()),0;case"rotate":return await Ba(a(),{force:O(e,"force")}),0;default:throw new g(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await ji(a()),0;if(n==="enable"||n==="disable")return await Ii(a(),Ci(r[0]),n==="enable"),0;throw new g(`unknown command: products ${n}. products [enable|disable ${Gn.join("|")}]`)}case"push":{if(n!=="credentials")throw new g("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await wc(a()),0;if(i==="set"){let u=No(r[1]),c=u==="apns"?await hc({p8:x(e,"p8"),keyId:x(e,"key-id"),teamId:x(e,"team-id"),topic:x(e,"topic"),environment:x(e,"environment")}):await yc(x(e,"file"));return await bc(a(),u,c),0}if(i==="remove"||i==="unset")return await Sc(a(),No(r[1])),0;throw new g(`unknown command: push credentials ${i}`)}case"auth":return await oc(a(),n,r,{clientId:x(e,"client-id"),stdin:O(e,"stdin"),siteUrl:x(e,"site-url"),allow:x(e,"allow"),file:x(e,"file"),subject:x(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Qi(a(),{channel:x(e,"channel"),watch:O(e,"watch")});if(n==="logs")return ec(a(),{channel:x(e,"channel"),since:x(e,"since")});throw new g(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Di(a(),n,r[0]),0;case"teams":return await Ia(),0;case"link":{if(O(e,"local")){let c;try{c=Ms(n)}catch(f){throw new v("not-found",f instanceof Error?f.message:String(f))}let p=lt(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return h({ref:c.ref,local:c.folder,api:c.apiUrl,path:p},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${p}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=x(e,"ref")??n;if(!i)throw new g("link needs --ref");let u=lt(process.cwd(),{ref:i});return h({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await vo(a()),0;case"psql":return Ji(a(),r);case"reset-password":return await ja(a()),0;case"access":return await Yi(a(),r,{level:x(e,"level")}),0;case"push":return fc(a(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return O(e,"window")?(await Mi(a()),0):x(e,"at")?(await Li(a(),x(e,"at"),x(e,"name")),0):Vi(a(),{file:x(e,"file"),force:!!e.flags.force});case"export":return Wi(a(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new g(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new g(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=O(e,"local")?await Ed(await nn()):null,u=i?i.dbUrl:x(e,"db-url");return $c(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(x(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:x(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return fa(i)}return vd(await nn(),{port:$e(e,"port"),dir:x(e,"dir"),noMigrations:O(e,"no-migrations"),outOfOrder:O(e,"out-of-order")});case"stop":{let i=l();return i?ga(i):$d(await nn())}case"status":{let i=l();return i?ma(i):xd(await nn())}case"usage":return Od(a(),{days:$e(e,"days"),history:!!e.flags.history});case"mcp":return Rd({version:ie,allowDelete:!!e.flags["allow-delete"]});case"init":return tw(e);case"upgrade":return Fd({check:O(e,"check")});default:throw new g(`unknown command: ${t}`)}}async function tw(e){let t=Is({flag:x(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),O(e,"env")?await Hd(o):await vo(o),t.kind==="link"){let s=Le(o),a=lt(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=x(e,"name")??nw(process.cwd());d(`Creating a project called "${n}"\u2026`),await Gr({name:n,region:x(e,"region"),wait:!0,link:process.cwd(),showUrl:!O(e,"env")});let r=sn({cwd:process.cwd()});return r&&O(e,"env")&&await Hd(r),0}async function Hd(e){let{connection:t}=await Promise.resolve().then(()=>(ee(),Ne)),n=await t(e),r=zi(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 nw(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function rw(e){if(e instanceof v)return Ge(e.code,e.message,e.details),e.exitCode;if(e instanceof g)return Ge("usage",e.message),Ie.usage;if(e instanceof Ke)return Ge("not-signed-in",e.message),Ie["not-signed-in"];if(e instanceof Se){let r=et(e.status,e.serverCode);return Ge(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ie[r]}let t=Fs(e);if(t)return Ge("usage",t),Ie.usage;let n=ln(e);return Ge(n,e instanceof Error?e.message:String(e)),Ie[n]}function _s(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function ow(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function sw(){Ot(ow(process.argv.slice(2)));let e;try{e=$s(process.argv.slice(2))}catch(t){Ge("usage",String(t instanceof Error?t.message:t)),_s(Ie.usage);return}Ot(e.json),ea(e.flags.quiet===!0),Bs(dn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{_s(await ew(e))}catch(t){_s(rw(t))}}sw();
888
+ `)}),0}let o=N(e,"help")||t==="help";if(o||!t)return h({version:ue,usage:Uu.trim(),commands:Cr},()=>process.stdout.write(Uu)),o?0:Me.usage;let s=()=>{let i=_e(e,"timeout");if(i!==void 0){if(!(i>0))throw new m("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=gn({flag:k(e,"ref")});if(!i)throw new m("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=gn({flag:k(e,"ref")});return i?Be(i):null};for(let i of hb)if(e.flags[i]!==void 0&&t!=="cluster")throw new m(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Iu(e,{timeoutMs:s()});case"login":{if(N(e,"device")){let p=await Rn({name:Sn(),platform:process.platform});h({verifyUrl:p.verifyUrl,code:p.code,expiresAt:p.expiresAt},()=>{d(""),d(` Open ${p.verifyUrl}`),d(` Type ${p.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let f=await Tn(p.secret,{intervalMs:Math.max(1,p.interval)*1e3,expiresAt:p.expiresAt});Oe({accessToken:f.token});let S=k(e,"email"),v=S?(await at()).email??void 0:void 0;return S&&Yr(S,{accessToken:f.token,email:v}),h({ok:!0,name:f.name??null,email:v??null},()=>d(`Signed in${v?` as ${v}`:""}.`)),0}let i=k(e,"domain"),u=N(e,"sso")||i!==void 0;if(u&&k(e,"provider"))throw new m("--sso and --provider are two different doors: pass one or the other");let c=await jn({provider:k(e,"provider"),sso:u,domain:i,noBrowser:N(e,"no-browser")||!ye(),timeoutMs:s(),email:k(e,"email")});return h({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{Xs();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return h({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await at(),u=fn()?.expectedEmail,c=!!(u&&i.email&&!Xr(u,i.email));return c&&Q(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),h({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
889
+ `),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 ni(),0;case"create":{let i=k(e,"name")??r[0];if(!i)throw new m("tokens create needs --name (what is it for, and where does it live)");return await ti({name:i,expiresInDays:_e(e,"expires"),project:k(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new m("tokens revoke needs the token's id or its sdt_ prefix");return await ri(i),0}default:throw new m(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Ya(),0;case"create":{let i=k(e,"name")??r[0];if(!i)throw new m("projects create needs --name");let u=k(e,"team");return await oo({teamId:u?await ei(u):void 0,name:i,region:k(e,"region"),wait:!N(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:N(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Xa(n,k(e,"ref")??r[0]??a(),{wait:!N(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=k(e,"ref")??r[0]??a(),u=Be(i);u?Ca(u):await rc(i)}return 0;default:throw new m(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new m("functions deploy needs a name: snoutdata functions deploy <name>");return await Ka(i,r[0],{dir:k(e,"dir"),verifyJwt:!N(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return qa(i),0;if((n==="delete"||n==="remove")&&r[0])return await Ga(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new m("functions deploy needs a name: snoutdata functions deploy <name>");return await di(a(),u,{dir:k(e,"dir"),entrypoint:k(e,"entrypoint"),verifyJwt:!N(e,"no-verify-jwt")}),0}case void 0:case"list":return await mi(a()),0;case"size":{let u=r[0];if(!u)throw new m("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await pi(a(),u,{memoryMb:_e(e,"memory"),concurrency:_e(e,"concurrency"),reset:N(e,"reset")}),0}case"logs":return await fi(a(),r[0],{errorsOnly:N(e,"errors"),limit:_e(e,"tail"),follow:N(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new m("functions delete needs a name");return await gi(a(),u),0}default:throw new m(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=k(e,"dockerfile");if(i&&c){if(u)throw new m("containers deploy takes an image or --dockerfile, not both");if(k(e,"registry-user")||N(e,"clear-registry"))throw new m("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Ei(a(),i,{path:c,port:_e(e,"port"),wait:!N(e,"no-wait")}),0}if(!i||!u)throw new m("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let p=k(e,"registry-user");if(p&&!N(e,"stdin"))throw new m('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await $i(a(),i,{image:u,port:_e(e,"port"),registryUser:p,registryPassword:p?await St():void 0,clearCredential:N(e,"clear-registry"),wait:!N(e,"no-wait")}),0}case void 0:case"list":return await Ai(a()),0;case"logs":return await Oi(a(),r[0],{errorsOnly:N(e,"errors"),limit:_e(e,"tail"),follow:N(e,"follow")}),0;case"restart":{if(!r[0])throw new m("containers restart needs a name");return await Ri(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new m("containers delete needs a name");return await Ti(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await Vn(a(),{}),0;if(i==="clear")return await Vn(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!N(e,"none"))throw new m("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&N(e,"none"))throw new m("containers egress set takes destinations or --none, not both");return await Vn(a(),{set:u}),0}throw new m(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new m(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await Wa(i,await Ci(r,{stdin:N(e,"stdin")})),0;if(n===void 0||n==="list")return Ja(i),0;if((n==="unset"||n==="remove")&&r[0])return await Va(i,r[0]),0}switch(n){case"set":return await ji(a(),r,{stdin:N(e,"stdin")}),0;case void 0:case"list":return await Mi(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new m("secrets unset needs a name");return await Di(a(),u),0}default:throw new m(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await oi(a()),0;case"rotate":return await si(a(),{force:N(e,"force")}),0;default:throw new m(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Zi(a()),0;if(n==="enable"||n==="disable")return await Qi(a(),Xi(r[0]),n==="enable"),0;throw new m(`unknown command: products ${n}. products [enable|disable ${Qn.join("|")}]`)}case"push":{if(n!=="credentials")throw new m("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await dl(a()),0;if(i==="set"){let u=Jo(r[1]),c=u==="apns"?await cl({p8:k(e,"p8"),keyId:k(e,"key-id"),teamId:k(e,"team-id"),topic:k(e,"topic"),environment:k(e,"environment")}):await ll(k(e,"file"));return await ul(a(),u,c),0}if(i==="remove"||i==="unset")return await pl(a(),Jo(r[1])),0;throw new m(`unknown command: push credentials ${i}`)}case"auth":return await Yc(a(),n,r,{clientId:k(e,"client-id"),stdin:N(e,"stdin"),siteUrl:k(e,"site-url"),allow:k(e,"allow"),file:k(e,"file"),subject:k(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return yc(a(),{channel:k(e,"channel"),watch:N(e,"watch")});if(n==="logs")return wc(a(),{channel:k(e,"channel"),since:k(e,"since")});throw new m(`unknown command: realtime ${n}. realtime inspect|logs`)}case"ai":{let i=r[0];switch(n){case void 0:case"status":return await Ac(a()),0;case"enable":case"disable":return await Rc(a(),n==="enable"),0;case"models":if(i===void 0||i==="list")return await Oc(a()),0;if(i==="add")return await Cc(a(),r[1],{source:k(e,"source"),upstream:k(e,"model"),provider:k(e,"provider"),baseUrl:k(e,"base-url"),region:k(e,"region"),priceIn:k(e,"price-in"),priceOut:k(e,"price-out"),accessKeyId:k(e,"access-key-id"),authHeader:k(e,"auth-header"),stdin:N(e,"stdin")}),0;if(i==="remove"||i==="delete")return await jc(a(),r[1]),0;throw new m(`unknown command: ai models ${i}. ai models [add|remove]`);case"providers":if(i===void 0||i==="list")return await Nc(a()),0;if(i==="remove"||i==="delete")return await Ic(a(),r[1]),0;throw new m(`unknown command: ai providers ${i}. ai providers [remove NAME]`);case"routes":if(i===void 0||i==="list")return await Dc(a()),0;if(i==="set")return await Mc(a(),r[1],{models:k(e,"models"),sort:k(e,"sort")}),0;if(i==="remove"||i==="delete")return await Lc(a(),r[1]),0;throw new m(`unknown command: ai routes ${i}. ai routes [set NAME --models a,b | remove NAME]`);case"keys":if(i===void 0||i==="list")return await Uc(a()),0;if(i==="create")return await Fc(a(),{name:k(e,"name"),models:k(e,"models"),expiresDays:_e(e,"expires")}),0;if(i==="revoke")return await Bc(a(),r[1]),0;throw new m(`unknown command: ai keys ${i}. ai keys [create|revoke]`);case"budgets":if(i===void 0||i==="list")return await Hc(a()),0;if(i==="set")return await Kc(a(),r[1],{amount:k(e,"amount"),period:k(e,"period"),soft:N(e,"soft")}),0;if(i==="remove"||i==="delete")return await qc(a(),r[1]),0;throw new m(`unknown command: ai budgets ${i}. ai budgets [set|remove]`);case"usage":return await Gc(a(),{days:_e(e,"days"),by:k(e,"by")}),0;case"test":return zc(a(),{model:k(e,"model"),prompt:k(e,"prompt")});default:throw new m(`unknown command: ai ${n}. ai [enable|disable|models|providers|keys|budgets|usage|test]`)}}case"domains":return await ec(a(),n,r[0]),0;case"teams":return await Qa(),0;case"link":{if(N(e,"local")){let c;try{c=na(n)}catch(f){throw new P("not-found",f instanceof Error?f.message:String(f))}let p=ft(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return h({ref:c.ref,local:c.folder,api:c.apiUrl,path:p},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${p}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=k(e,"ref")??n;if(!i)throw new m("link needs --ref");let u=ft(process.cwd(),{ref:i});return h({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Mo(a()),0;case"psql":return dc(a(),r);case"reset-password":return await Za(a()),0;case"access":return await fc(a(),r,{level:k(e,"level")}),0;case"push":return al(a(),{dir:k(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return N(e,"window")?(await tc(a()),0):k(e,"at")?(await nc(a(),k(e,"at"),k(e,"name")),0):mc(a(),{file:k(e,"file"),force:!!e.flags.force});case"export":return pc(a(),{timeoutMs:s(),out:k(e,"out"),statusOnly:!!e.flags.status});default:throw new m(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new m(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=N(e,"local")?await $u(await dn()):null,u=i?i.dbUrl:k(e,"db-url");return hl(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(k(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:k(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Na(i)}return ku(await dn(),{port:_e(e,"port"),dir:k(e,"dir"),noMigrations:N(e,"no-migrations"),outOfOrder:N(e,"out-of-order")});case"stop":{let i=l();return i?Ia(i):Su(await dn())}case"status":{let i=l();return i?Oa(i):vu(await dn())}case"usage":return Ru(a(),{days:_e(e,"days"),history:!!e.flags.history});case"mcp":return Eu({version:ue,allowDelete:!!e.flags["allow-delete"]});case"init":return wb(e);case"upgrade":return Lu({check:N(e,"check")});default:throw new m(`unknown command: ${t}`)}}async function wb(e){let t=ea({flag:k(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.`),N(e,"env")?await Fu(o):await Mo(o),t.kind==="link"){let s=Be(o),a=ft(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=k(e,"name")??bb(process.cwd());d(`Creating a project called "${n}"\u2026`),await oo({name:n,region:k(e,"region"),wait:!0,link:process.cwd(),showUrl:!N(e,"env")});let r=gn({cwd:process.cwd()});return r&&N(e,"env")&&await Fu(r),0}async function Fu(e){let{connection:t}=await Promise.resolve().then(()=>(re(),Ee)),n=await t(e),r=uc(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 bb(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function _b(e){if(e instanceof P)return Je(e.code,e.message,e.details),e.exitCode;if(e instanceof m)return Je("usage",e.message),Me.usage;if(e instanceof Ve)return Je("not-signed-in",e.message),Me["not-signed-in"];if(e instanceof ge){let r=Le(e.status,e.serverCode);return Je(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Me[r]}let t=sa(e);if(t)return Je("usage",t),Me.usage;let n=wn(e);return Je(n,e instanceof Error?e.message:String(e)),Me[n]}function Fs(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function kb(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Sb(){Mt(kb(process.argv.slice(2)));let e;try{e=Ks(process.argv.slice(2))}catch(t){Je("usage",String(t instanceof Error?t.message:t)),Fs(Me.usage);return}Mt(e.json),ba(e.flags.quiet===!0),aa(bn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{Fs(await yb(e))}catch(t){Fs(_b(t))}}Sb();