snoutdata 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/snoutdata.mjs +137 -133
  2. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- var Cl=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var Jo=(e,t)=>{for(var n in t)Cl(e,n,{get:t[n],enumerable:!0})};function Ko(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]=Ul(o);if(Ml.has(s)){let l=a??e[++r];if(l===void 0)throw new f(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new f(`--${s} does not take a value`);if(!Ll.has(s))throw new f(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Ul(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 I(e,t){return e.flags[t]===!0}function We(e,t){let n=x(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new f(`--${t} must be a whole number, not "${n}"`);return r}var f,Ml,Ll,de=j(()=>{"use strict";f=class extends Error{},Ml=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","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"]);Ll=new Set(["json","window","help","version","force","no-verify-jwt","reset","stdin","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 Fl,existsSync as Vo,mkdirSync as Yo,readFileSync as Xo,rmSync as Bl,writeFileSync as Qo}from"node:fs";import{homedir as Xt}from"node:os";import{dirname as rr,join as Zo,parse as Hl,resolve as ql}from"node:path";function or(e=Xt()){return Zo(e,".snoutdata","auth.json")}function Qt(e=Xt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=or(e);if(!Vo(n))return null;try{let r=JSON.parse(Xo(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ke(e,t=Xt()){let n=or(t);Yo(rr(n),{recursive:!0}),Qo(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),Fl(n,384)}function es(e=Xt()){Bl(or(e),{force:!0})}function ts(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function ns(e){return Zo(e,".snoutdata","project.json")}function rt(e){let t=ql(e),n=Hl(t).root;for(;;){let r=ns(t);if(Vo(r))try{let o=JSON.parse(Xo(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=rr(t)}}function ot(e,t){let n=ns(e);return Yo(rr(n),{recursive:!0}),Qo(n,`${JSON.stringify(t,null,2)}
4
- `),n}function Zt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:rt(e.cwd??process.cwd())?.ref??null}function rs(e){let t=e.environment??process.env,n=rt(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Ie=j(()=>{"use strict"});import{existsSync as sr,readFileSync as os}from"node:fs";import{homedir as Gl}from"node:os";import{isAbsolute as Jl,join as Ce,resolve as st}from"node:path";function Kl(e=process.env,t=process.platform,n=Gl()){let r;return t==="win32"?r=e.APPDATA??Ce(n,"AppData","Roaming"):t==="darwin"?r=Ce(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Ce(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Ce(r,o,"localProjects.json"))}function en(e=Kl()){let t=[];for(let n of e)if(sr(n))try{let r=JSON.parse(os(n,"utf8"));for(let o of r.projects??[])typeof o.folder=="string"&&o.folder&&!t.some(s=>s.folder===o.folder)&&t.push({name:o.name??null,folder:o.folder,ref:o.ref??null})}catch{}return t}function tn(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function _t(e){return sr(Ce(e,"compose.yaml"))&&sr(Ce(e,".env"))}function Me(e,t=null){let n=st(e);if(!_t(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=tn(os(Ce(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Ce(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,anonKey:r.get("ANON_KEY")||null,serviceRoleKey:r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function ss(e,t=process.cwd(),n=en()){if(!e){if(_t(t))return Me(t);let[i]=n;if(n.length===1&&i)return Me(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=Jl(e)?e:st(t,e);if(_t(r)){let i=n.find(u=>st(u.folder)===st(r));return Me(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 Me(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 as(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}`,anonKey:e.anonKey,serviceRoleKey:e.serviceRoleKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function is(e,t,n=en()){if(t&&_t(t))try{let r=n.find(s=>st(s.folder)===st(t)),o=Me(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&_t(r.folder))return Me(r.folder,r.name);return null}var St=j(()=>{"use strict"});function D(e,t,n){throw new S(e,t,n)}function ze(e,t){return t==="no-capacity"?"no-capacity":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function cs(e){let t=e;return!t||typeof t!="object"||t.code!=="ENOENT"||typeof t.path!="string"||t.syscall==="spawn"?null:`no file at ${t.path}`}function nn(e){let t=[e],n=e;for(let r=0;r<4&&n instanceof Error&&n.cause;r+=1)n=n.cause,t.push(n);for(let r of t){let o=typeof r=="object"&&r&&"code"in r?String(r.code):"";if(Wl.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 Ae,S,Wl,oe=j(()=>{"use strict";Ae={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},S=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ae[this.code]}};Wl=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function rn(e){let t=e.env.SNOUTDATA_NO_INTERACTIVE;if(t!==void 0&&t!==""&&t!=="0"&&t!=="false")return{canAsk:!1,reason:"opted-out"};if(e.json)return{canAsk:!1,reason:"json"};for(let n of zl){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function at(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function ls(e){ar=e}function se(){return(ar??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function it(){return(ar??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var zl,ar,Ve=j(()=>{"use strict";zl=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];ar=null});import{execFile as Vl}from"node:child_process";import{existsSync as Yl,readFileSync as ms}from"node:fs";import{homedir as ir,hostname as Xl}from"node:os";import{join as ct}from"node:path";function Ql(e,t,n){if(t==="win32"){let r=n.APPDATA??ct(ir(),"AppData","Roaming");return ct(r,e)}return t==="darwin"?ct(ir(),"Library","Application Support",e):ct(n.XDG_CONFIG_HOME??ct(ir(),".config"),e)}function ps(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>ct(Ql(n,e,t),"mcp.json"))}function Zl(e=process.platform,t=process.env){return ps(e,t)[0]}function ed(e=Zl()){if(!Yl(e))return null;try{let t=JSON.parse(ms(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function td(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function rd(e,t){let n=`0100007F:${t.toString(16).toUpperCase().padStart(4,"0")}`,r=[];for(let o of e.split(`
5
- `).slice(1)){let s=o.trim().split(/\s+/);s.length>7&&s[1]?.toUpperCase()===n&&s[3]==="0A"&&r.push(Number(s[7]))}return r}function od(e,t){return e.split(`
6
- `).some(n=>n.trim()===`n127.0.0.1:${t}`)}function ds(e,t){return new Promise(n=>{Vl(e,t,{timeout:5e3,windowsHide:!0},(r,o)=>{n(r&&!o?"":String(o))})})}async function fs(e,t=process.platform){if(!Number.isInteger(e)||e<=0||e>65535)return!1;try{if(t==="linux"){let n=process.getuid?.();if(n===void 0)return!1;let r=rd(ms("/proc/net/tcp","utf8"),e);return r.length>0&&r.every(o=>o===n)}if(t==="darwin"){let n=process.getuid?.();if(n===void 0)return!1;let r=await ds("/usr/sbin/lsof",["-nP","-a",`-iTCP@127.0.0.1:${e}`,"-sTCP:LISTEN","-u",String(n),"-Fn"]);return od(r,e)}if(t==="win32"){let n=[`$c = Get-NetTCPConnection -LocalAddress 127.0.0.1 -LocalPort ${e} -State Listen -ErrorAction Stop | Select-Object -First 1`,'$p = Get-CimInstance Win32_Process -Filter "ProcessId=$($c.OwningProcess)" -ErrorAction Stop',"$o = Invoke-CimMethod -InputObject $p -MethodName GetOwner -ErrorAction Stop","$me = [Security.Principal.WindowsIdentity]::GetCurrent().Name",'if ($o.ReturnValue -eq 0 -and ("$($o.Domain)\\$($o.User)" -ieq $me)) { "mine" } else { "other" }'].join("; ");return(await ds("powershell.exe",["-NoProfile","-NonInteractive","-Command",n])).trim()==="mine"}}catch{return!1}return!1}async function gs(e,t){let n=cr.get(e);if(n!==void 0&&Date.now()-n<sd)return!0;let r=await t(e).catch(()=>!1);return r?cr.set(e,Date.now()):cr.delete(e),r}async function ur(e,t,n,r=fs){if(!await gs(e.port,r))throw new lr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function on(e,t=fs){if(e!==void 0)return us(e,t);let n="no-config";for(let r of ps()){let o=ed(r);if(!o)continue;let s=await us(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 us(e,t){let n=td(e);if(n)return{available:!1,skip:n};let r=e;if(!await gs(r.port,t))return{available:!1,skip:"not-verified"};let o;try{let s=await ur(r,"/cli/whoami",{signal:AbortSignal.timeout(nd)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch{return{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 hs(e,t){let n=await ur(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 Xl()}async function dr(e,t,n,r){let o=await ur(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 ys(e){return await dr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await dr(e,"tools/list",{},3e3)).tools??[]}async function ws(e,t,n){return dr(e,"tools/call",{name:t,arguments:n},12e4)}var nd,sd,cr,lr,an=j(()=>{"use strict";nd=1500;sd=1e4,cr=new Map;lr=class extends Error{constructor(t){super(`the process on 127.0.0.1:${t} is not one of this user's, so SnoutData Studio's token was not sent to it`),this.name="PeerNotVerified"}}});import{basename as ad}from"node:path";function ln(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=ad(e.execPath.replace(/\\/g,"/")).toLowerCase();if(e.bun&&t.startsWith("snoutdata"))return"binary";let n=e.script.replace(/\\/g,"/");return/\/_npx\//.test(n)?"npx":/\/node_modules\/snoutdata\//.test(n)?"npm":"source"}function id(e=ln(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?Te:kt}}function mr(e={}){let t=e.kind??ln(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?Te:`${kt} (or ${Te})`;return`snoutdata ${Z} is installed and ${r}. Run \`${id(t,n)}\`, or install again: ${o}`}function _s(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 Z,cn,kt,Te,vt=j(()=>{"use strict";Z="0.14.0",cn=`snoutdata-cli/${Z}`,kt="curl -fsSL https://snoutdata.com/install.sh | sh",Te="npm install -g snoutdata@latest"});async function Ss(e,t){let n=await fetch(`${ve}/functions/v1/${e}`,{method:"POST",headers:{apikey:Re,"Content-Type":"application/json","x-client-info":cn},body:JSON.stringify(t)});mn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw pn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function dn(e={}){let{status:t,body:n}=await Ss("cloud-device-start",{client:e});return t===429&&D("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&D("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)&&D("server",n.error??`could not start a pairing (${t})`),n}async function un(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&D("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 Ss("cloud-device-poll",{secret:e});switch(o===404&&D("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&D("usage",s.error??"that is not a pairing secret"),o!==200&&D("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":D("forbidden",s.error??"that pairing was refused in the browser");case"used":D("conflict",s.error??"that pairing has already been completed");case"expired":D("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 pr=j(()=>{"use strict";Y();oe();vt()});function $t(e){xt=e}function vs(e){ks=e}function xs(){return xt}function w(e){return $s?`\x1B[2m${e}\x1B[0m`:e}function $(e){return $s?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(xt){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ var Ll=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var Jo=(e,t)=>{for(var n in t)Ll(e,n,{get:t[n],enumerable:!0})};function Ko(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]=Bl(o);if(Ul.has(s)){let l=a??e[++r];if(l===void 0)throw new f(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new f(`--${s} does not take a value`);if(!Fl.has(s))throw new f(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Bl(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function $(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function D(e,t){return e.flags[t]===!0}function Ie(e,t){let n=$(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new f(`--${t} must be a whole number, not "${n}"`);return r}var f,Ul,Fl,de=j(()=>{"use strict";f=class extends Error{},Ul=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","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"]);Fl=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","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 Hl,existsSync as Vo,mkdirSync as Yo,readFileSync as Xo,rmSync as ql,writeFileSync as Qo}from"node:fs";import{homedir as Xt}from"node:os";import{dirname as rr,join as Zo,parse as Gl,resolve as Jl}from"node:path";function or(e=Xt()){return Zo(e,".snoutdata","auth.json")}function Qt(e=Xt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=or(e);if(!Vo(n))return null;try{let r=JSON.parse(Xo(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ke(e,t=Xt()){let n=or(t);Yo(rr(n),{recursive:!0}),Qo(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),Hl(n,384)}function es(e=Xt()){ql(or(e),{force:!0})}function ts(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function ns(e){return Zo(e,".snoutdata","project.json")}function rt(e){let t=Jl(e),n=Gl(t).root;for(;;){let r=ns(t);if(Vo(r))try{let o=JSON.parse(Xo(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=rr(t)}}function ot(e,t){let n=ns(e);return Yo(rr(n),{recursive:!0}),Qo(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function Zt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:rt(e.cwd??process.cwd())?.ref??null}function rs(e){let t=e.environment??process.env,n=rt(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Ce=j(()=>{"use strict"});import{existsSync as sr,readFileSync as os}from"node:fs";import{homedir as Kl}from"node:os";import{isAbsolute as Wl,join as Me,resolve as st}from"node:path";function zl(e=process.env,t=process.platform,n=Kl()){let r;return t==="win32"?r=e.APPDATA??Me(n,"AppData","Roaming"):t==="darwin"?r=Me(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Me(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Me(r,o,"localProjects.json"))}function en(e=zl()){let t=[];for(let n of e)if(sr(n))try{let r=JSON.parse(os(n,"utf8"));for(let o of r.projects??[])typeof o.folder=="string"&&o.folder&&!t.some(s=>s.folder===o.folder)&&t.push({name:o.name??null,folder:o.folder,ref:o.ref??null})}catch{}return t}function tn(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function _t(e){return sr(Me(e,"compose.yaml"))&&sr(Me(e,".env"))}function Le(e,t=null){let n=st(e);if(!_t(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=tn(os(Me(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Me(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,anonKey:r.get("ANON_KEY")||null,serviceRoleKey:r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function ss(e,t=process.cwd(),n=en()){if(!e){if(_t(t))return Le(t);let[i]=n;if(n.length===1&&i)return Le(i.folder,i.name);throw n.length===0?new Error("there is no local project to link: set one up in Studio (Projects, Local), or pass the folder of a stack you made with snout-stack"):new Error(`there are ${n.length} local projects; name one: ${n.map(u=>u.name??u.folder).join(", ")}`)}let r=Wl(e)?e:st(t,e);if(_t(r)){let i=n.find(u=>st(u.folder)===st(r));return Le(r,i?.name??null)}let o=e.toLowerCase(),s=n.filter(i=>i.name?.toLowerCase()===o||i.ref===e),[a]=s;if(s.length===1&&a)return Le(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function as(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}`,anonKey:e.anonKey,serviceRoleKey:e.serviceRoleKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function is(e,t,n=en()){if(t&&_t(t))try{let r=n.find(s=>st(s.folder)===st(t)),o=Le(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&_t(r.folder))return Le(r.folder,r.name);return null}var St=j(()=>{"use strict"});function I(e,t,n){throw new S(e,t,n)}function ze(e,t){return t==="no-capacity"?"no-capacity":e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function cs(e){let t=e;return!t||typeof t!="object"||t.code!=="ENOENT"||typeof t.path!="string"||t.syscall==="spawn"?null:`no file at ${t.path}`}function nn(e){let t=[e],n=e;for(let r=0;r<4&&n instanceof Error&&n.cause;r+=1)n=n.cause,t.push(n);for(let r of t){let o=typeof r=="object"&&r&&"code"in r?String(r.code):"";if(Vl.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 Ae,S,Vl,oe=j(()=>{"use strict";Ae={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},S=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ae[this.code]}};Vl=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function rn(e){let t=e.env.SNOUTDATA_NO_INTERACTIVE;if(t!==void 0&&t!==""&&t!=="0"&&t!=="false")return{canAsk:!1,reason:"opted-out"};if(e.json)return{canAsk:!1,reason:"json"};for(let n of Yl){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function at(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function ls(e){ar=e}function se(){return(ar??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function it(){return(ar??rn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Yl,ar,Ve=j(()=>{"use strict";Yl=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];ar=null});import{execFile as Xl}from"node:child_process";import{existsSync as Ql,readFileSync as ms}from"node:fs";import{homedir as ir,hostname as Zl}from"node:os";import{join as ct}from"node:path";function ed(e,t,n){if(t==="win32"){let r=n.APPDATA??ct(ir(),"AppData","Roaming");return ct(r,e)}return t==="darwin"?ct(ir(),"Library","Application Support",e):ct(n.XDG_CONFIG_HOME??ct(ir(),".config"),e)}function ps(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>ct(ed(n,e,t),"mcp.json"))}function td(e=process.platform,t=process.env){return ps(e,t)[0]}function nd(e=td()){if(!Ql(e))return null;try{let t=JSON.parse(ms(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function rd(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function sd(e,t){let n=`0100007F:${t.toString(16).toUpperCase().padStart(4,"0")}`,r=[];for(let o of e.split(`
5
+ `).slice(1)){let s=o.trim().split(/\s+/);s.length>7&&s[1]?.toUpperCase()===n&&s[3]==="0A"&&r.push(Number(s[7]))}return r}function ad(e,t){return e.split(`
6
+ `).some(n=>n.trim()===`n127.0.0.1:${t}`)}function ds(e,t){return new Promise(n=>{Xl(e,t,{timeout:5e3,windowsHide:!0},(r,o)=>{n(r&&!o?"":String(o))})})}async function fs(e,t=process.platform){if(!Number.isInteger(e)||e<=0||e>65535)return!1;try{if(t==="linux"){let n=process.getuid?.();if(n===void 0)return!1;let r=sd(ms("/proc/net/tcp","utf8"),e);return r.length>0&&r.every(o=>o===n)}if(t==="darwin"){let n=process.getuid?.();if(n===void 0)return!1;let r=await ds("/usr/sbin/lsof",["-nP","-a",`-iTCP@127.0.0.1:${e}`,"-sTCP:LISTEN","-u",String(n),"-Fn"]);return ad(r,e)}if(t==="win32"){let n=[`$c = Get-NetTCPConnection -LocalAddress 127.0.0.1 -LocalPort ${e} -State Listen -ErrorAction Stop | Select-Object -First 1`,'$p = Get-CimInstance Win32_Process -Filter "ProcessId=$($c.OwningProcess)" -ErrorAction Stop',"$o = Invoke-CimMethod -InputObject $p -MethodName GetOwner -ErrorAction Stop","$me = [Security.Principal.WindowsIdentity]::GetCurrent().Name",'if ($o.ReturnValue -eq 0 -and ("$($o.Domain)\\$($o.User)" -ieq $me)) { "mine" } else { "other" }'].join("; ");return(await ds("powershell.exe",["-NoProfile","-NonInteractive","-Command",n])).trim()==="mine"}}catch{return!1}return!1}async function gs(e,t){let n=cr.get(e);if(n!==void 0&&Date.now()-n<id)return!0;let r=await t(e).catch(()=>!1);return r?cr.set(e,Date.now()):cr.delete(e),r}async function ur(e,t,n,r=fs){if(!await gs(e.port,r))throw new lr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function on(e,t=fs){if(e!==void 0)return us(e,t);let n="no-config";for(let r of ps()){let o=nd(r);if(!o)continue;let s=await us(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 us(e,t){let n=rd(e);if(n)return{available:!1,skip:n};let r=e;if(!await gs(r.port,t))return{available:!1,skip:"not-verified"};let o;try{let s=await ur(r,"/cli/whoami",{signal:AbortSignal.timeout(od)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch{return{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 hs(e,t){let n=await ur(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 Zl()}async function dr(e,t,n,r){let o=await ur(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 ys(e){return await dr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await dr(e,"tools/list",{},3e3)).tools??[]}async function ws(e,t,n){return dr(e,"tools/call",{name:t,arguments:n},12e4)}var od,id,cr,lr,an=j(()=>{"use strict";od=1500;id=1e4,cr=new Map;lr=class extends Error{constructor(t){super(`the process on 127.0.0.1:${t} is not one of this user's, so SnoutData Studio's token was not sent to it`),this.name="PeerNotVerified"}}});import{basename as cd}from"node:path";function ln(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=cd(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 ld(e=ln(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?Te:kt}}function mr(e={}){let t=e.kind??ln(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?Te:`${kt} (or ${Te})`;return`snoutdata ${Z} is installed and ${r}. Run \`${ld(t,n)}\`, or install again: ${o}`}function _s(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 Z,cn,kt,Te,vt=j(()=>{"use strict";Z="0.15.0",cn=`snoutdata-cli/${Z}`,kt="curl -fsSL https://snoutdata.com/install.sh | sh",Te="npm install -g snoutdata@latest"});async function Ss(e,t){let n=await fetch(`${ve}/functions/v1/${e}`,{method:"POST",headers:{apikey:Re,"Content-Type":"application/json","x-client-info":cn},body:JSON.stringify(t)});mn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw pn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function dn(e={}){let{status:t,body:n}=await Ss("cloud-device-start",{client:e});return t===429&&I("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&I("not-found","this SnoutData does not have the pairing flow (cloud-device-start is not deployed). Use `snoutdata login` to sign in with a browser, or set SNOUTDATA_ACCESS_TOKEN."),(t!==200||!n.code||!n.secret)&&I("server",n.error??`could not start a pairing (${t})`),n}async function un(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&I("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(a=>setTimeout(a,n));let{status:o,body:s}=await Ss("cloud-device-poll",{secret:e});switch(o===404&&I("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&I("usage",s.error??"that is not a pairing secret"),o!==200&&I("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":I("forbidden",s.error??"that pairing was refused in the browser");case"used":I("conflict",s.error??"that pairing has already been completed");case"expired":I("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var pr=j(()=>{"use strict";X();oe();vt()});function $t(e){xt=e}function vs(e){ks=e}function xs(){return xt}function w(e){return $s?`\x1B[2m${e}\x1B[0m`:e}function v(e){return $s?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(xt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
7
  `);return}t()}function d(e){!xt&&!ks&&process.stderr.write(`${e}
8
- `)}function Le(e,t,n){process.stderr.write(`${t}
8
+ `)}function Ue(e,t,n){process.stderr.write(`${t}
9
9
  `),xt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
- `)}function V(e){process.stderr.write(`${e}
11
- `)}function M(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
12
- `)}function Oe(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var xt,ks,$s,z=j(()=>{"use strict";xt=!1,ks=!1;$s=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as As,randomBytes as fr}from"node:crypto";import{createServer as ld}from"node:http";import{createInterface as dd}from"node:readline/promises";import{spawn as ud}from"node:child_process";function fn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Es(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.":Ad(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
10
+ `)}function Y(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 Oe(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var xt,ks,$s,z=j(()=>{"use strict";xt=!1,ks=!1;$s=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as As,randomBytes as fr}from"node:crypto";import{createServer as ud}from"node:http";import{createInterface as md}from"node:readline/promises";import{spawn as pd}from"node:child_process";function fn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Es(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.":Rd(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,9 +21,9 @@ var Cl=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var Jo=(e,t)=>{f
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 gr(){let e=fn(fr(32)),t=fn(As("sha256").update(e).digest());return{verifier:e,challenge:t}}function md(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 pd(e){let{command:t,args:n,verbatim:r}=md(process.platform,e),o=ud(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function hr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function hd(){return"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"}function yd(e){let t=e??fn(fr(32));return{raw:t,hashed:As("sha256").update(t).digest("hex")}}function wd(e){return`${fd}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:hr(),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 bd(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 yr(e){return new Promise((t,n)=>{let r="",o=ld((a,l)=>{let i=bd(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"?Es("ok"):Es("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."),pd(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 Ts(e,t){let n=await fetch(`${ve}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:Re,"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 Rs(await n.json())}async function _d(e,t,n){let r=await fetch(gd,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:hr(),client_secret:hd(),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 Sd(e){if(!hr())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}=gr(),{raw:r,hashed:o}=yd(),s=fn(fr(16)),{code:a,redirectUri:l}=await yr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>wd({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await _d(a,t,l),u=await fetch(`${ve}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:Re,"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 Rs(await u.json())}function Rs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ke(n),n}function Ps(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 kd(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 vd(e,t,n){let r=kd(t);return e===404||/no sso provider/i.test(r)?new S("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 S("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new S(ze(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function xd(e){let t=await fetch(`${ve}/auth/v1/sso`,{method:"POST",headers:{apikey:Re,"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 vd(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new S("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 $d(e){if(e!==void 0){let r=Ps(e);if(!r)throw new f(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!se()){let r=it();throw new f(`login --sso needs --domain <your work email or company domain>${r?`, because ${at(r)}`:""}`)}let t=dd({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new f("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=Ps(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new f("no domain to sign in with: pass --domain <your work email or company domain>")}async function Ed(e){let t=await $d(e.domain),{verifier:n,challenge:r}=gr();d(`Signing in with the identity provider for ${$(t)}.`);let{code:o}=await yr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>xd({domain:t,redirectTo:s,challenge:r})});return Ts(o,n)}async function gn(e={}){return wr(e.email,await Pd(e))}function wr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ke(n),t.email&&!br(t.email,e)&&V(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function br(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Pd(e){if(e.sso)return Ed({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return Sd(e);let{verifier:n,challenge:r}=gr(),{code:o}=await yr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${ve}/auth/v1/authorize`);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 Ts(o,n)}function Ad(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var fd,gd,hn=j(()=>{"use strict";Y();de();Ie();oe();Ve();z();fd="https://accounts.google.com/o/oauth2/v2/auth",gd="https://oauth2.googleapis.com/token"});import{createInterface as Td}from"node:readline/promises";async function _r(e){let t=Td({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 Rd(){let e=await on();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 _r("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await hs(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 ke(r),d(`Signed in with "${n.name}".`),r}async function Od(){if(!await _r("Sign in with a browser?"))return null;try{let e=await gn({});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 Nd(){if(!await _r("Sign in by typing a code into a browser on another machine?"))return null;let e=await dn({name:sn(),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 un(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ke(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Os(){if(!se())return null;let e=await Rd();if(e)return e;let t=await Od();return t||Nd()}var Ns=j(()=>{"use strict";Ie();an();pr();Ve();hn();z()});var xe={};Jo(xe,{ACCOUNTS_URL:()=>ve,ANON_KEY:()=>Re,ApiError:()=>ge,FUNCTIONS:()=>Ds,NotSignedIn:()=>Ue,call:()=>b,connection:()=>me,exportStatus:()=>lt,isSettled:()=>Is,listProjects:()=>he,listTeams:()=>yn,localStack:()=>Ne,noticeFrom:()=>mn,outdated:()=>pn,requestExport:()=>Et,waitForReady:()=>At,waitForSettled:()=>Pt,whoami:()=>Ye});function jd(){let e=it();return e?`not signed in, and ${at(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function Dd(){let e=Qt();if(e||(e=await Os()),!e)throw new Ue;if(!ts(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${ve}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:Re,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Ue;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ke(r),r.accessToken}async function b(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Ne(r);if(u)throw new S("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 Dd(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${Ds}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:Re,"Content-Type":"application/json","x-client-info":cn},body:n==="GET"?void 0:JSON.stringify(t)});mn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;if(a.status===401)throw new Ue(u);if(a.status===410)throw pn(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 mn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||js||(js=!0,V(`${t} ${mr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function pn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new S("outdated",`${t.replace(/\s+$/,"")} ${mr({minimum:n})}`,{status:410,installed:Z,minimum:n??null})}async function Ye(){return b("cloud-whoami")}async function he(){return b("cloud-project-list")}async function me(e){let t=Ne(e);return t?as(t):b("cloud-project-connection",{ref:e})}function Ne(e){return is(e,rt(process.cwd())?.local??null)}async function yn(){return b("cloud-team-list")}async function Et(e){return b("cloud-project-export",{ref:e})}async function lt(e){return b("cloud-project-export",{ref:e},"GET")}async function Pt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await he(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Is(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 Is(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function At(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await he(),o=r.find(s=>s.ref===e);if(!o)throw new ge(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ge(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var ve,Re,Ds,ge,Ue,js,Y=j(()=>{"use strict";Ie();St();oe();Ve();Ns();z();vt();ve=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Re=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",Ds=`${ve}/functions/v1`,ge=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Ue=class extends Error{constructor(t){super(t??jd())}};js=!1});function so(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function vi(e,t,n){let r="";for(;r.length<n;)r+=so(e,t);return r}var oo,ki,xi=j(()=>{"use strict";oo="0123456789abcdefghjkmnpqrstvwxyz",ki="abcdefghjkmnpqrstvwxyz"});function Pi(e){return so(e,$i)+vi(e,oo,Ei-1)}function mp(e){return up.test(e)}function re(e){if(!mp(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function U(e,t=pp){return re(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var $i,Ei,up,pp,be=j(()=>{"use strict";xi();$i=ki,Ei=13,up=new RegExp(`^[${$i}][${oo}]{${Ei-1}}$`);pp="db.snoutdata.com"});var Ai=j(()=>{"use strict";be()});function Ri(e,t){let n=Ti(e,7),r=Ti(t,24);return Math.max(2,Math.ceil(n*24/r))}function Ti(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var ao=j(()=>{"use strict"});function io(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var co=j(()=>{"use strict"});var Oi=j(()=>{"use strict"});function ji(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 Di(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 Ni,lo=j(()=>{"use strict";be();Ni="select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming'), '')"});var Mw,Ii=j(()=>{"use strict";be();Mw=7*24});var Uw,Ci=j(()=>{"use strict";Uw=7*24*60*60*1e3});var fp,Mi=j(()=>{"use strict";Ci();fp=60*60*1e3});var ft,Jw,Li=j(()=>{"use strict";ft="snout_storage_admin",Jw=`do $$ begin
24
+ </body></html>`}function gr(){let e=fn(fr(32)),t=fn(As("sha256").update(e).digest());return{verifier:e,challenge:t}}function fd(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 gd(e){let{command:t,args:n,verbatim:r}=fd(process.platform,e),o=pd(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function hr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function wd(){return"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"}function bd(e){let t=e??fn(fr(32));return{raw:t,hashed:As("sha256").update(t).digest("hex")}}function _d(e){return`${hd}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:hr(),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 Sd(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 yr(e){return new Promise((t,n)=>{let r="",o=ud((a,l)=>{let i=Sd(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"?Es("ok"):Es("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."),gd(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 Ts(e,t){let n=await fetch(`${ve}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:Re,"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 Rs(await n.json())}async function kd(e,t,n){let r=await fetch(yd,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:hr(),client_secret:wd(),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 vd(e){if(!hr())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}=gr(),{raw:r,hashed:o}=bd(),s=fn(fr(16)),{code:a,redirectUri:l}=await yr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>_d({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await kd(a,t,l),u=await fetch(`${ve}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:Re,"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 Rs(await u.json())}function Rs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ke(n),n}function Ps(e){let t=e.trim().toLowerCase();if(!t||/\s/.test(t))return null;let n=t.indexOf("@");if(n===0||t.indexOf("@",n+1)!==-1)return null;let r=n===-1?t:t.slice(n+1);if(!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/.test(r))return null;let o=r.slice(r.lastIndexOf(".")+1);return/^[a-z]{2,}$/.test(o)?r:null}function xd(e){try{let t=JSON.parse(e);for(let n of["error_description","msg","message","error"]){let r=t[n];if(typeof r=="string"&&r)return r}}catch{return""}return""}function $d(e,t,n){let r=xd(t);return e===404||/no sso provider/i.test(r)?new S("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 S("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new S(ze(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Ed(e){let t=await fetch(`${ve}/auth/v1/sso`,{method:"POST",headers:{apikey:Re,"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 $d(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new S("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 Pd(e){if(e!==void 0){let r=Ps(e);if(!r)throw new f(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!se()){let r=it();throw new f(`login --sso needs --domain <your work email or company domain>${r?`, because ${at(r)}`:""}`)}let t=md({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new f("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=Ps(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new f("no domain to sign in with: pass --domain <your work email or company domain>")}async function Ad(e){let t=await Pd(e.domain),{verifier:n,challenge:r}=gr();d(`Signing in with the identity provider for ${v(t)}.`);let{code:o}=await yr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Ed({domain:t,redirectTo:s,challenge:r})});return Ts(o,n)}async function gn(e={}){return wr(e.email,await Td(e))}function wr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ke(n),t.email&&!br(t.email,e)&&Y(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function br(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Td(e){if(e.sso)return Ad({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return vd(e);let{verifier:n,challenge:r}=gr(),{code:o}=await yr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${ve}/auth/v1/authorize`);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 Ts(o,n)}function Rd(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var hd,yd,hn=j(()=>{"use strict";X();de();Ce();oe();Ve();z();hd="https://accounts.google.com/o/oauth2/v2/auth",yd="https://oauth2.googleapis.com/token"});import{createInterface as Od}from"node:readline/promises";async function _r(e){let t=Od({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 Nd(){let e=await on();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 ${v(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 _r("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await hs(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 ke(r),d(`Signed in with "${n.name}".`),r}async function jd(){if(!await _r("Sign in with a browser?"))return null;try{let e=await gn({});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 Dd(){if(!await _r("Sign in by typing a code into a browser on another machine?"))return null;let e=await dn({name:sn(),platform:process.platform});d(""),d(` Open ${v(e.verifyUrl)}`),d(` Type ${v(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 un(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ke(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Os(){if(!se())return null;let e=await Nd();if(e)return e;let t=await jd();return t||Dd()}var Ns=j(()=>{"use strict";Ce();an();pr();Ve();hn();z()});var xe={};Jo(xe,{ACCOUNTS_URL:()=>ve,ANON_KEY:()=>Re,ApiError:()=>ge,FUNCTIONS:()=>Ds,NotSignedIn:()=>Fe,call:()=>b,connection:()=>me,exportStatus:()=>lt,isSettled:()=>Is,listProjects:()=>he,listTeams:()=>yn,localStack:()=>Ne,noticeFrom:()=>mn,outdated:()=>pn,requestExport:()=>Et,waitForReady:()=>At,waitForSettled:()=>Pt,whoami:()=>Ye});function Id(){let e=it();return e?`not signed in, and ${at(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function Cd(){let e=Qt();if(e||(e=await Os()),!e)throw new Fe;if(!ts(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${ve}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:Re,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Fe;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ke(r),r.accessToken}async function b(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Ne(r);if(u)throw new S("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 Cd(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${Ds}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:Re,"Content-Type":"application/json","x-client-info":cn},body:n==="GET"?void 0:JSON.stringify(t)});mn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;if(a.status===401)throw new Fe(u);if(a.status===410)throw pn(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 mn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||js||(js=!0,Y(`${t} ${mr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function pn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new S("outdated",`${t.replace(/\s+$/,"")} ${mr({minimum:n})}`,{status:410,installed:Z,minimum:n??null})}async function Ye(){return b("cloud-whoami")}async function he(){return b("cloud-project-list")}async function me(e){let t=Ne(e);return t?as(t):b("cloud-project-connection",{ref:e})}function Ne(e){return is(e,rt(process.cwd())?.local??null)}async function yn(){return b("cloud-team-list")}async function Et(e){return b("cloud-project-export",{ref:e})}async function lt(e){return b("cloud-project-export",{ref:e},"GET")}async function Pt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await he(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Is(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 Is(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function At(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await he(),o=r.find(s=>s.ref===e);if(!o)throw new ge(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ge(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var ve,Re,Ds,ge,Fe,js,X=j(()=>{"use strict";Ce();St();oe();Ve();Ns();z();vt();ve=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Re=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",Ds=`${ve}/functions/v1`,ge=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Fe=class extends Error{constructor(t){super(t??Id())}};js=!1});function so(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function $i(e,t,n){let r="";for(;r.length<n;)r+=so(e,t);return r}var oo,xi,Ei=j(()=>{"use strict";oo="0123456789abcdefghjkmnpqrstvwxyz",xi="abcdefghjkmnpqrstvwxyz"});function Ti(e){return so(e,Pi)+$i(e,oo,Ai-1)}function fp(e){return pp.test(e)}function re(e){if(!fp(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function U(e,t=gp){return re(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Pi,Ai,pp,gp,be=j(()=>{"use strict";Ei();Pi=xi,Ai=13,pp=new RegExp(`^[${Pi}][${oo}]{${Ai-1}}$`);gp="db.snoutdata.com"});var Ri=j(()=>{"use strict";be()});function Ni(e,t){let n=Oi(e,7),r=Oi(t,24);return Math.max(2,Math.ceil(n*24/r))}function Oi(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var ao=j(()=>{"use strict"});function io(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var co=j(()=>{"use strict"});var ji=j(()=>{"use strict"});function Ii(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 Ci(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 Di,lo=j(()=>{"use strict";be();Di="select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming'), '')"});var Lw,Mi=j(()=>{"use strict";be();Lw=7*24});var Fw,Li=j(()=>{"use strict";Fw=7*24*60*60*1e3});var hp,Ui=j(()=>{"use strict";Li();hp=60*60*1e3});var ft,Kw,Fi=j(()=>{"use strict";ft="snout_storage_admin",Kw=`do $$ begin
27
27
  if exists (select from pg_roles where rolname = 'supabase_storage_admin') then
28
28
  if exists (select from pg_roles where rolname = '${ft}') then
29
29
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${ft}';
@@ -33,21 +33,21 @@ ${e==="ok"?"":'<p style="color:#8f887e;margin:14px 0 0;font-size:14px">Run <code
33
33
  end if;
34
34
  alter role supabase_storage_admin rename to ${ft};
35
35
  end if;
36
- end $$;`});var He,Ww,In,Ui,zw,Fi=j(()=>{"use strict";He="snout_realtime_admin",Ww=`do $snoutpod$
36
+ end $$;`});var qe,zw,In,Bi,Vw,Hi=j(()=>{"use strict";qe="snout_realtime_admin",zw=`do $snoutpod$
37
37
  begin
38
38
  if exists (select from pg_roles where rolname = 'supabase_admin') then
39
- if exists (select from pg_roles where rolname = '${He}') then
40
- perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${He}';
41
- reassign owned by ${He} to supabase_admin;
42
- drop owned by ${He};
43
- drop role ${He};
39
+ if exists (select from pg_roles where rolname = '${qe}') then
40
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${qe}';
41
+ reassign owned by ${qe} to supabase_admin;
42
+ drop owned by ${qe};
43
+ drop role ${qe};
44
44
  end if;
45
- alter role supabase_admin rename to ${He};
45
+ alter role supabase_admin rename to ${qe};
46
46
  end if;
47
47
  if exists (select from pg_roles where rolname = 'supabase_realtime_admin')
48
- and exists (select from pg_roles where rolname = '${He}') then
48
+ and exists (select from pg_roles where rolname = '${qe}') then
49
49
  begin
50
- reassign owned by supabase_realtime_admin to ${He};
50
+ reassign owned by supabase_realtime_admin to ${qe};
51
51
  drop owned by supabase_realtime_admin;
52
52
  drop role supabase_realtime_admin;
53
53
  exception when others then
@@ -56,13 +56,13 @@ begin
56
56
  end if;
57
57
  end
58
58
  $snoutpod$;
59
- `,In="snoutdata_realtime",Ui="supabase_realtime",zw=`do $snoutpod$
59
+ `,In="snoutdata_realtime",Bi="supabase_realtime",Vw=`do $snoutpod$
60
60
  begin
61
61
  if exists (select 1 from pg_publication where pubname = '${In}') then
62
62
  return;
63
63
  end if;
64
- if exists (select 1 from pg_publication where pubname = '${Ui}') then
65
- alter publication ${Ui} rename to ${In};
64
+ if exists (select 1 from pg_publication where pubname = '${Bi}') then
65
+ alter publication ${Bi} rename to ${In};
66
66
  return;
67
67
  end if;
68
68
  create publication ${In};
@@ -70,12 +70,12 @@ begin
70
70
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
71
71
  end
72
72
  $snoutpod$;
73
- `});var Yw,Xw,Bi=j(()=>{"use strict";Yw=2*1024*1024,Xw=64*1024});var Hi,qi,gp,hp,yp,Gi=j(()=>{"use strict";Hi="/snoutfn",qi="/_snoutpod/health",gp="x-snoutdata-door",hp="SNOUT_FUNCTIONS_DOOR_SECRET",yp=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
73
+ `});var Xw,Qw,qi=j(()=>{"use strict";Xw=2*1024*1024,Qw=64*1024});var Gi,Ji,yp,wp,bp,Ki=j(()=>{"use strict";Gi="/snoutfn",Ji="/_snoutpod/health",yp="x-snoutdata-door",wp="SNOUT_FUNCTIONS_DOOR_SECRET",bp=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
74
74
  // every time the agent starts, from packages/snoutpod/src/host/functionMain.ts.
75
- const MOUNT = ${JSON.stringify(Hi)};
76
- const HEALTH = ${JSON.stringify(qi)};
77
- const DOOR_HEADER = ${JSON.stringify(gp)};
78
- const DOOR = new TextEncoder().encode(Deno.env.get(${JSON.stringify(hp)}) ?? '');
75
+ const MOUNT = ${JSON.stringify(Gi)};
76
+ const HEALTH = ${JSON.stringify(Ji)};
77
+ const DOOR_HEADER = ${JSON.stringify(yp)};
78
+ const DOOR = new TextEncoder().encode(Deno.env.get(${JSON.stringify(wp)}) ?? '');
79
79
  if (DOOR.length === 0) {
80
80
  console.error('WARNING: no door secret is set, so a request is not asked to prove it came through the front door');
81
81
  }
@@ -236,13 +236,13 @@ Deno.serve(async (req) => {
236
236
  return refuse(500, 'This function could not be run.', detail);
237
237
  }
238
238
  });
239
- `});function uo(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Cn(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 bp(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 bp(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(uo("the subnet",n,Ji),uo("the sink",r,wp),!Wi(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Wi(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",`${uo("a denied prefix",s,Ji)},${r}`);return o.push(e.name),o}function Ki(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Wi(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Ki(e)&s)>>>0===(Ki(n??"")&s)>>>0}function zi(e){return e?.internal===!0}var Ji,wp,mo=j(()=>{"use strict";Ji=/^((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])$/,wp=/^((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 _p,Mn=j(()=>{"use strict";_p=7*24*60*60});var Vi=j(()=>{"use strict";be();Bi();Gi();mo();Mn()});var Yi=j(()=>{"use strict"});import{createHash as kp}from"node:crypto";function Ln(e){return`${U(e).pod}-lepis-tls`}function xp(e){return kp("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function ec(e,t){let n={...e},r=n[po],o=n[fo];delete n[po],delete n[fo];for(let[s,a,l]of[[po,r,t],[fo,o,xp(t)]])if(a!==void 0){if(!vp.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var Bt,po,fo,Xi,Qi,go,Zi,vp,ho=j(()=>{"use strict";be();Yi();Bt="lepis",po="SNOUTPOD_ADMIN_PASSWORD_ENV",fo="SNOUTPOD_ADMIN_TOKEN_ENV",Xi=7433,Qi=1e3;go="/tls",Zi=["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(`
240
- `),vp=/^[A-Z][A-Z0-9_]{0,63}$/});function nc(e){let t=[];for(let n of e.split(`
241
- `)){let r=n.trim();if(!r)continue;let[o,s,a,l]=r.split("|");!o||s===void 0||t.push({name:o,active:s==="t",walStatus:a??"",inactiveSeconds:l?Number(l):null})}return t}var tc,yo=j(()=>{"use strict";tc="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var $b,Eb,rc=j(()=>{"use strict";be();Mn();$b=6*60*60,Eb=5*60});var oc=j(()=>{"use strict"});var sc=j(()=>{"use strict"});var ac=j(()=>{"use strict";sc()});function cc(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"||!Ep.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!$p.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 lc(e,t){return`${e}.nspname !~ '^pg_'
242
- and ${e}.nspname <> all (array[${ic.map(n=>`'${n}'`).join(", ")}]::text[])
239
+ `});function uo(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Cn(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 Sp(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 Sp(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(uo("the subnet",n,Wi),uo("the sink",r,_p),!Vi(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Vi(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",`${uo("a denied prefix",s,Wi)},${r}`);return o.push(e.name),o}function zi(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Vi(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(zi(e)&s)>>>0===(zi(n??"")&s)>>>0}function Yi(e){return e?.internal===!0}var Wi,_p,mo=j(()=>{"use strict";Wi=/^((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])$/,_p=/^((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 kp,Mn=j(()=>{"use strict";kp=7*24*60*60});var Xi=j(()=>{"use strict";be();qi();Ki();mo();Mn()});var Qi=j(()=>{"use strict"});import{createHash as xp}from"node:crypto";function Ln(e){return`${U(e).pod}-lepis-tls`}function Ep(e){return xp("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function nc(e,t){let n={...e},r=n[po],o=n[fo];delete n[po],delete n[fo];for(let[s,a,l]of[[po,r,t],[fo,o,Ep(t)]])if(a!==void 0){if(!$p.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var Bt,po,fo,Zi,ec,go,tc,$p,ho=j(()=>{"use strict";be();Qi();Bt="lepis",po="SNOUTPOD_ADMIN_PASSWORD_ENV",fo="SNOUTPOD_ADMIN_TOKEN_ENV",Zi=7433,ec=1e3;go="/tls",tc=["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(`
240
+ `),$p=/^[A-Z][A-Z0-9_]{0,63}$/});function oc(e){let t=[];for(let n of e.split(`
241
+ `)){let r=n.trim();if(!r)continue;let[o,s,a,l]=r.split("|");!o||s===void 0||t.push({name:o,active:s==="t",walStatus:a??"",inactiveSeconds:l?Number(l):null})}return t}var rc,yo=j(()=>{"use strict";rc="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var Eb,Pb,sc=j(()=>{"use strict";be();Mn();Eb=6*60*60,Pb=5*60});var ac=j(()=>{"use strict"});var ic=j(()=>{"use strict"});var cc=j(()=>{"use strict";ic()});function dc(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"||!Ap.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Pp.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 uc(e,t){return`${e}.nspname !~ '^pg_'
242
+ and ${e}.nspname <> all (array[${lc.map(n=>`'${n}'`).join(", ")}]::text[])
243
243
  and not ${t}.rolsuper
244
- and ${t}.rolname !~ '${Pp.source}'`}function uc(e){let t=[];for(let n of e.split(`
245
- `)){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 mc(e,t){return re(e),JSON.stringify(cc(t).map(n=>({role:n.role,level:n.level})))}var $p,gt,Ep,J,ic,Pp,Fn,Ap,Un,Tp,dc,wo=j(()=>{"use strict";be();$p=["full","read"],gt="snout_oauth",Ep=/^oauth_[0-9a-f]{32}$/,J="snout_oauth_read",ic=["information_schema","auth","storage","realtime","snout_realtime","_realtime","push","graphql","graphql_public","extensions","cron","net","vault","pgsodium","pgbouncer","snouttime","snouttime_internal","supabase_functions"],Pp=/^(snout_[a-z]+_admin|supabase_[a-z_]+|authenticator|pgbouncer)$/;Fn="SNOUTPOD_DB_ACCESS",Ap=/^[a-z_][a-z0-9_]*$/;for(let e of ic)if(!Ap.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Un="snoutpod_oauth_read_new_schema",Tp=`create schema if not exists extensions;
244
+ and ${t}.rolname !~ '${Tp.source}'`}function pc(e){let t=[];for(let n of e.split(`
245
+ `)){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 fc(e,t){return re(e),JSON.stringify(dc(t).map(n=>({role:n.role,level:n.level})))}var Pp,gt,Ap,J,lc,Tp,Fn,Rp,Un,Op,mc,wo=j(()=>{"use strict";be();Pp=["full","read"],gt="snout_oauth",Ap=/^oauth_[0-9a-f]{32}$/,J="snout_oauth_read",lc=["information_schema","auth","storage","realtime","snout_realtime","_realtime","push","graphql","graphql_public","extensions","cron","net","vault","pgsodium","pgbouncer","snouttime","snouttime_internal","supabase_functions"],Tp=/^(snout_[a-z]+_admin|supabase_[a-z_]+|authenticator|pgbouncer)$/;Fn="SNOUTPOD_DB_ACCESS",Rp=/^[a-z_][a-z0-9_]*$/;for(let e of lc)if(!Rp.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Un="snoutpod_oauth_read_new_schema",Op=`create schema if not exists extensions;
246
246
  create or replace function extensions.${Un}() returns event_trigger
247
247
  language plpgsql
248
248
  security invoker
@@ -263,7 +263,7 @@ begin
263
263
  where c.command_tag = 'CREATE SCHEMA'
264
264
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
265
265
  and not c.in_extension
266
- and ${lc("n","r")}
266
+ and ${uc("n","r")}
267
267
  loop
268
268
  begin
269
269
  execute pg_catalog.format('grant usage on schema %I to ${J}', created.nspname);
@@ -286,8 +286,8 @@ begin
286
286
  end if;
287
287
  end
288
288
  $snoutpod$;
289
- `,dc=`\\getenv db_access ${Fn}
290
- ${Tp}create function pg_temp.snoutpod_member(member_name text, group_name text)
289
+ `,mc=`\\getenv db_access ${Fn}
290
+ ${Op}create function pg_temp.snoutpod_member(member_name text, group_name text)
291
291
  returns boolean
292
292
  language sql
293
293
  stable
@@ -308,7 +308,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
308
308
  set search_path = pg_catalog, pg_temp
309
309
  as $snoutpod$
310
310
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
311
- select ${lc("n","r")}
311
+ select ${uc("n","r")}
312
312
  from pg_namespace n
313
313
  join pg_roles r on r.oid = n.nspowner
314
314
  where n.oid = schema_oid
@@ -497,8 +497,8 @@ begin
497
497
  end
498
498
  $snoutpod$;
499
499
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
500
- `});var pc,$_,E_,P_,A_,T_,R_,fc=j(()=>{"use strict";Ai();ao();co();Oi();lo();Ii();Mi();Li();Fi();Vi();ho();yo();rc();oc();ac();Mn();wo();pc=120,$_=2*6e4,E_=15*60*1e3,P_=60*60*1e3,A_=60*60*1e3,T_=5*60*1e3,R_=10*60*1e3});import{execFile as Rp}from"node:child_process";function gc(){return(e,t,n={})=>new Promise((r,o)=>{let s=Rp(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 H(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
501
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Ht=j(()=>{"use strict"});import{readdir as Op,readFile as I_}from"node:fs/promises";async function hc(e,t,n={}){let r=n.binary??"podman",o=U(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??Np)(i)!=="empty":!1}async function Np(e){try{return(await Op(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var yc=j(()=>{"use strict";be();Ht()});function wc(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can pass to a pod: ${JSON.stringify(t)}`);return t}async function Sc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function kc(e){let t=!!(e?.key&&e?.secret),n=["--env",`SNOUTPOD_S3_KEY_TYPE=${t?"shared":"auto"}`];return e&&(t&&(n.push("--env",`SNOUTPOD_S3_KEY=${wc("the access key",e.key,Dp)}`),n.push("--env",bc),e.token&&n.push("--env",_c)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${wc("the endpoint",e.endpoint,jp)}`),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 vc(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{[bc]:e.secret,...e.token?{[_c]:e.token}:{}}}var bc,_c,jp,Dp,xc=j(()=>{"use strict";bc="SNOUTPOD_S3_SECRET",_c="SNOUTPOD_S3_TOKEN",jp=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Dp=/^[A-Za-z0-9/+=_.-]{1,256}$/});function Bn(e){return Ip[e]??e}var $c,Ec,De,Ip,Pc,Ac=j(()=>{"use strict";$c="authenticator",Ec=`do $snoutpod$
500
+ `});var gc,E_,P_,A_,T_,R_,O_,hc=j(()=>{"use strict";Ri();ao();co();ji();lo();Mi();Ui();Fi();Hi();Xi();ho();yo();sc();ac();cc();Mn();wo();gc=120,E_=2*6e4,P_=15*60*1e3,A_=60*60*1e3,T_=60*60*1e3,R_=5*60*1e3,O_=10*60*1e3});import{execFile as Np}from"node:child_process";function yc(){return(e,t,n={})=>new Promise((r,o)=>{let s=Np(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 H(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
501
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Ht=j(()=>{"use strict"});import{readdir as jp,readFile as C_}from"node:fs/promises";async function wc(e,t,n={}){let r=n.binary??"podman",o=U(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??Dp)(i)!=="empty":!1}async function Dp(e){try{return(await jp(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var bc=j(()=>{"use strict";be();Ht()});function _c(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 vc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function xc(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=${_c("the access key",e.key,Cp)}`),n.push("--env",Sc),e.token&&n.push("--env",kc)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${_c("the endpoint",e.endpoint,Ip)}`),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 $c(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{[Sc]:e.secret,...e.token?{[kc]:e.token}:{}}}var Sc,kc,Ip,Cp,Ec=j(()=>{"use strict";Sc="SNOUTPOD_S3_SECRET",kc="SNOUTPOD_S3_TOKEN",Ip=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Cp=/^[A-Za-z0-9/+=_.-]{1,256}$/});function Bn(e){return Mp[e]??e}var Pc,Ac,De,Mp,Tc,Rc=j(()=>{"use strict";Pc="authenticator",Ac=`do $snoutpod$
502
502
  declare
503
503
  wanted text[];
504
504
  begin
@@ -516,7 +516,7 @@ begin
516
516
  end loop;
517
517
  end
518
518
  $snoutpod$;
519
- `,De="snout_auth_admin",Ip={supabase_auth_admin:De};Pc=`do $snoutpod$
519
+ `,De="snout_auth_admin",Mp={supabase_auth_admin:De};Tc=`do $snoutpod$
520
520
  begin
521
521
  if exists (select from pg_catalog.pg_roles where rolname = 'supabase_auth_admin') then
522
522
  if exists (select from pg_catalog.pg_roles where rolname = '${De}') then
@@ -529,8 +529,8 @@ begin
529
529
  end if;
530
530
  end
531
531
  $snoutpod$;
532
- `});function Rc(e){let t=[];for(let n of e.split(`
533
- `)){let r=n.split(_o);if(r.length!==4||!bo.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 bo,_o,Tc,Oc=j(()=>{"use strict";bo=["snouttime"];for(let e of bo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);_o="",Tc=`
532
+ `});function Nc(e){let t=[];for(let n of e.split(`
533
+ `)){let r=n.split(_o);if(r.length!==4||!bo.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 bo,_o,Oc,jc=j(()=>{"use strict";bo=["snouttime"];for(let e of bo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);_o="",Oc=`
534
534
  SET search_path = pg_catalog, pg_temp;
535
535
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
536
536
  DO $snoutpod$
@@ -575,98 +575,100 @@ BEGIN
575
575
  END
576
576
  $snoutpod$;
577
577
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
578
- `});function jc(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 Nc,G_,J_,Dc=j(()=>{"use strict";Ht();So();Nc="SNOUTPOD_OAUTH_ISSUER",G_=256*1024,J_=5*6e4});import{createHash as xo,randomBytes as Cp}from"node:crypto";import{access as Mp,constants as Lp}from"node:fs/promises";import{delimiter as Up,join as Fp}from"node:path";function Bp(e,t){return xo("sha256").update($o(e)).update(e.smtpSecretEnv&&t?xo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function $o(e){if(Array.isArray(e))return`[${e.map($o).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)}:${$o(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Po(e){if(!qp.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Gt(e){if(!Gp.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Jp(e,t,n){return`postgres://${Gt(t)}:${n}@127.0.0.1:5432/${re(e)}`}function Mc(e,t,n,r){let o=ec(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=Bn(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]=Jp(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),Kp(t.image,o)}function Kp(e,t){if(!/(^|\/)(snout-auth|s-fleet\/auth)([:@]|$)/.test(e))return{...t};let n={};for(let[r,o]of Object.entries(t)){let s=r.startsWith("GOTRUE_")?`AUTH_${r.slice(7)}`:r==="API_EXTERNAL_URL"?"AUTH_API_EXTERNAL_URL":r;s!==r&&s in t||(n[s]=o)}return n}function Lc(e,t){return`${U(e).pod}-${Po(t)}`}function Hn(e){return`snoutpod-host-${Po(e)}`}function Wp(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return xo("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 zp(e,t){let n=[],r=Eo(t);for(let o of e??[])if(Po(o.name),qn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){qn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===Bt){let s=o.hostPort+Qi;qn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Xi}`)}}return n}function Eo(e){let t=e===void 0?[]:typeof e=="string"?[e]:[...e];return["127.0.0.1",...new Set(t.filter(n=>n!=="127.0.0.1"))]}function qn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Fc(e){let t=[];for(let n of Object.keys(e)){if(!Vp.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Yp(e){let t=e.healthPath;if(t==null||t==="")return[];if(!qc.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(qn(e.hostPort,`${e.name}: hostPort`),["--label",`${Ao}=${e.hostPort}:${t}`])}function Xp(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||!qc.test(r)?null:{port:n,path:r}}async function Zp(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Mp(o,Lp.X_OK));for(let o of n.split(Up))if(o)try{let s=Fp(o,e);return await r(s),s}catch{}return null}function ef(){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(`
579
- `)}function nf(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function rf(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 To(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function of(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 sf(e,t,n,r){return{image:await af(e,t,r),data:await cf(e,t,n)}}async function af(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 rf(r,n)}async function cf(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",U(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?To(s.stdout):null}catch{return null}}function lf(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:Bc;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Bc)}`),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 df(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Qp;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Zp(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Jc(e){let t=e.binary??"podman",n=e.exec,r=df(e),o=e.probe??Gc;async function s(p){try{await n(t,["exec",...ie,"--interactive",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:tf,stdin:ef()})}catch{}}async function a(){let p=await H(n,t,["ps","--all","--filter",`label=${_e}`,"--format","json"]),y=p.trim()?JSON.parse(p):[],_=new Map,N=new Map;for(let P of y){let A=P.Labels?.[_e],ee=P.Labels?.[tt];if(!A||!ee)continue;let te=_.get(A)??[];te.push(ee),_.set(A,te);let le=Number(P.Labels?.[vo]??0),T=Number.isInteger(le)?le:0;N.set(A,Math.max(N.get(A)??T,T))}let O=y.filter(P=>P.Labels?.[_e]&&!P.Labels?.[tt]),q=O.map(P=>pf(P.State)),G=await mf(O,uf,(P,A)=>q[A]==="running"?Promise.resolve(!0):e.hasData(P.Labels?.[_e]??""));return O.map((P,A)=>{let ee=P.Labels?.[_e]??"",te=Number(P.Labels?.[ko]);return{ref:ee,status:q[A]??"unknown",generation:Number.isInteger(te)?te:null,hasData:G[A]??!1,services:_.get(ee)??[],...N.has(ee)?{servicesGeneration:N.get(ee)}:{}}})}async function l(){let p=await H(n,t,["ps","--all","--filter",`label=${_e}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(_=>_.Labels?.[_e]&&!_.Labels?.[tt]).length}async function i(p){let y=await(e.majors??((_,N)=>sf(n,t,_,N)))(p.ref,p.image);return of(p.ref,y)}async function u(p){let y=U(p.ref),_=await i(p);if(_)throw new Error(_);let N=e.credentials,O=await Sc(e.repository,N?()=>N.forProject(p.ref,p.bucket):null),q={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...vc(O)},G=[...await r(),"run","--detach","--name",y.pod,"--label",`${_e}=${p.ref}`,"--label",`${ko}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...hf(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?Eo(e.podBind).flatMap(P=>["--publish",`${P}:${p.port}:5432`]):[],...zp(p.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${p.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${p.bucket}`,"--env",`SNOUTPOD_S3_REGION=${p.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...gf(p.oauth),...lf(p.tuning).flatMap(P=>["--env",P]),"--env",`SNOUTPOD_RETENTION_FULL=${Ri(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...kc(O),p.image,"snoutpod-postgres"];await H(n,t,G,{env:q})}async function c(p){let y=await H(n,t,["ps","--all","--filter",`label=${_e}=${re(p)}`,"--filter",`label=${tt}`,"--format","json"]),_=y.trim()?JSON.parse(y):[],N=[];for(let O of _){let q=O.Labels?.[tt];if(!q)continue;let G=Number(O.Labels?.[vo]??0);N.push({name:q,container:Lc(p,q),spec:O.Labels?.[Ic]??"",pod:O.Labels?.[Cc]??"",running:O.State==="running",generation:Number.isInteger(G)?G:0})}return N}async function m(p){let y=await n(t,["container","inspect","--format","{{.Id}}",U(p).pod]);return y.code===0?y.stdout.trim():""}async function h(p,y){let _=[];for(let P of y){let A=P.dbUriRole?Bn(P.dbUriRole):null;P.dbUriEnv&&A&&!_.includes(A)&&_.push(Gt(A))}let N=new Map;if(_.length===0)return N;let O={},q=[],G=_.includes(De)?[Pc]:[];return _.includes($c)&&G.push(Ec),_.forEach((P,A)=>{let ee=Cp(24).toString("base64url"),te=`SNOUTPOD_SERVICE_PASSWORD_${A}`;N.set(P,ee),O[te]=ee,q.push("--env",te),G.push(`\\getenv service_password_${A} ${te}`),G.push(`\\set service_role_${A} ${P}`),G.push(`alter role :"service_role_${A}" with password :'service_password_${A}';`)}),G.push(""),await H(n,t,["exec",...ie,"--interactive",...q,U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",U(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:O,stdin:G.join(`
580
- `)}),N}async function v(p){let y=Ln(p.ref);await H(n,t,["volume","create","--ignore",y]),await H(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${go}`,"--entrypoint","bash",p.image,"-c",Zi])}async function C(p){let y=await r();if((await n(t,[...y,"image","exists",p])).code===0)return;let _=await n(t,[...y,"pull","--quiet",p]);if(_.code===0)return;let N=nf(p);if(!N||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(_.stderr||_.stdout).trim()}`);await e.registryLogin(N),await H(n,t,[...y,"pull","--quiet",p])}async function W(p){let y=p.services??[],_=await c(p.ref),N;y.some(T=>T.smtpSecretEnv)&&(N=await e.smtpSecret?.().catch(()=>{}));let O=y.length>0?await m(p.ref):"",q=new Map(_.map(T=>[T.name,T])),G=new Map(y.map(T=>[T.name,Bp(T,N)])),P=new Set(y.filter(T=>{let ne=q.get(T.name);return!ne||!ne.running||ne.spec!==G.get(T.name)||O===""||ne.pod!==O}).map(T=>T.name)),A=p.servicesGeneration??0;if(P.size===0&&y.length>0&&!_.some(T=>T.generation>=A))for(let T of y)P.add(T.name);let ee=T=>T.dbUriEnv&&T.dbUriRole?Bn(T.dbUriRole):null;for(let T=!0;T;){T=!1;let ne=new Set(y.filter(Ke=>P.has(Ke.name)).map(ee));for(let Ke of y){let Go=ee(Ke);!P.has(Ke.name)&&Go!==null&&ne.has(Go)&&(P.add(Ke.name),T=!0)}}for(let T of new Set(y.filter(ne=>P.has(ne.name)).map(ne=>ne.image)))await C(T);for(let T of _)(P.has(T.name)||!G.has(T.name))&&await H(n,t,["rm","--force","--ignore",T.container]);let te=y.filter(T=>P.has(T.name)),le=await h(p,te);te.some(T=>T.name===Bt)&&await v(p);for(let T of te){let ne=Lc(p.ref,T.name),Ke=[...await r(),"run","--detach","--name",ne,"--label",`${_e}=${p.ref}`,"--label",`${ko}=${p.generation}`,"--label",`${tt}=${T.name}`,"--label",`${vo}=${p.servicesGeneration??0}`,"--label",`${Ic}=${G.get(T.name)??""}`,"--label",`${Cc}=${O}`,"--network",`container:${U(p.ref).pod}`,"--userns",`container:${U(p.ref).pod}`,"--memory",`${T.memoryMb}m`,"--restart","no",...Yp(T),...T.name===Bt?["--volume",`${Ln(p.ref)}:${go}:ro`]:[],...Fc(Mc(p,T,le,N)),T.image];await H(n,t,Ke,{env:Mc(p,T,le,N)})}}async function F(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let _=y.stdout.trim();return _==="t"?!0:_==="f"?!1:null}async function pe(p,y){let _=O=>n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",O]),N=Date.now()+y;do{if((await _("checkpoint")).code!==0)return!1;let q=await _("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(G=>setTimeout(G,1e3))}while(Date.now()<N);return!1}async function Pe(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",Ni]);return y.code===0?ji(y.stdout):null}async function ce(p,y){return(await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...Di(y).flatMap(N=>["-c",N])])).code===0}return{list:a,inRecovery:F,settleTimeline:pe,podHealth:Pe,setSyncNames:ce,preflight:i,create:u,createServices:W,async start(p){await H(n,t,["start",U(p).pod])},async startServices(p){let y=(await c(p)).map(_=>_.container);y.length>0&&await H(n,t,["start",...y])},async applyHostServices(p){let y=[],_=await H(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),N=_.trim()?JSON.parse(_):[],O=new Map;for(let G of N){let P=G.Labels?.[qt];P&&O.set(P,{spec:G.Labels?.[Uc]??"",state:G.State??""})}for(let G of p)try{await q(G)}catch(P){y.push({name:G.name,action:"failed",reason:P instanceof Error?P.message:String(P)})}async function q(G){let P=e.hostServiceLocal?.(G,p),A=P?{...G,...P.env&&Object.keys(P.env).length>0?{env:{...G.env,...P.env}}:{},...P.volumes?{volumes:P.volumes}:{},...P.network?{network:P.network}:{}}:G,ee=Hn(A.name),te=Wp(A),le=O.get(A.name);if(O.delete(A.name),le&&le.spec===te){le.state!=="running"&&(await H(n,t,["start",ee]),y.push({name:A.name,action:"started",reason:le.state}));return}let T=le?"the spec changed":"it was not here";await C(A.image),le&&await H(n,t,["rm","--force","--ignore",ee]),await H(n,t,[...await r(),"run","--detach","--name",ee,"--label",`${qt}=${A.name}`,"--label",`${Uc}=${te}`,...A.healthPath?["--label",`${Ao}=${A.hostPort}:${A.healthPath}`]:[],"--memory",`${A.memoryMb}m`,"--restart","no",...A.network?["--network",A.network]:e.network?["--network",e.network]:[],...(A.volumes??[]).flatMap(ne=>["--volume",ne]),...Eo(e.podBind).flatMap(ne=>["--publish",`${ne}:${A.hostPort}:${A.port}`]),...A.adminPort&&A.adminHostPort?["--publish",`127.0.0.1:${A.adminHostPort}:${A.adminPort}`]:[],...Fc(A.env),A.image,...A.command??[]],{env:{...A.env}}),y.push({name:A.name,action:le?"recreated":"created",reason:T})}for(let G of O.keys())await H(n,t,["rm","--force","--ignore",Hn(G)]),y.push({name:G,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(p){let y=await H(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),_=(y.trim()?JSON.parse(y):[]).map(O=>O.Labels?.[qt]??"").filter(O=>O!=="");if(_.length===0)return[];let N=Date.now()+p;for(;;){let O=[];for(let q of _)O.push(await Hc(n,t,q,Hn(q),o));if(O.every(q=>q.ok)||Date.now()>=N)return O;await new Promise(q=>setTimeout(q,500))}},async serviceHealth(p,y){let _=await c(p);if(_.length===0)return[];let N=Date.now()+y;for(;;){let O=await Promise.all(_.map(q=>Hc(n,t,q.name,q.container,o)));if(O.every(q=>q.ok)||Date.now()>=N)return O;await new Promise(q=>setTimeout(q,500))}},async stop(p,y){for(let _ of await c(p))await H(n,t,["stop","--time",String(Hp),_.container]);await H(n,t,["stop","--time",String(y),U(p).pod])},async remove(p){for(let y of await c(p))await H(n,t,["rm","--force","--ignore",y.container]);await H(n,t,["rm","--force","--ignore","--volumes",U(p).pod])},async removeData(p){await H(n,t,["volume","rm","--force",U(p).pod]);let y=Ln(p);(await n(t,["volume","exists",y])).code===0&&await H(n,t,["volume","rm","--force",y])},async applyOwnerPassword(p){let y=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
578
+ `});function Ic(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 Dc,J_,K_,Cc=j(()=>{"use strict";Ht();So();Dc="SNOUTPOD_OAUTH_ISSUER",J_=256*1024,K_=5*6e4});import{createHash as xo,randomBytes as Lp}from"node:crypto";import{access as Up,constants as Fp}from"node:fs/promises";import{delimiter as Bp,join as Hp}from"node:path";function qp(e,t){return xo("sha256").update($o(e)).update(e.smtpSecretEnv&&t?xo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function $o(e){if(Array.isArray(e))return`[${e.map($o).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)}:${$o(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Po(e){if(!Jp.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Gt(e){if(!Kp.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Wp(e,t,n){return`postgres://${Gt(t)}:${n}@127.0.0.1:5432/${re(e)}`}function Uc(e,t,n,r){let o=nc(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=Bn(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]=Wp(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function Fc(e,t){return`${U(e).pod}-${Po(t)}`}function Hn(e){return`snoutpod-host-${Po(e)}`}function zp(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return xo("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 Vp(e,t){let n=[],r=Eo(t);for(let o of e??[])if(Po(o.name),qn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){qn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===Bt){let s=o.hostPort+ec;qn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Zi}`)}}return n}function Eo(e){let t=e===void 0?[]:typeof e=="string"?[e]:[...e];return["127.0.0.1",...new Set(t.filter(n=>n!=="127.0.0.1"))]}function qn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Hc(e){let t=[];for(let n of Object.keys(e)){if(!Yp.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Xp(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Jc.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(qn(e.hostPort,`${e.name}: hostPort`),["--label",`${Ao}=${e.hostPort}:${t}`])}function Qp(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||!Jc.test(r)?null:{port:n,path:r}}async function ef(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Up(o,Fp.X_OK));for(let o of n.split(Bp))if(o)try{let s=Hp(o,e);return await r(s),s}catch{}return null}function tf(){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(`
579
+ `)}function rf(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function of(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 To(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function sf(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 af(e,t,n,r){return{image:await cf(e,t,r),data:await lf(e,t,n)}}async function cf(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 of(r,n)}async function lf(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",U(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?To(s.stdout):null}catch{return null}}function df(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:qc;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,qc)}`),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 uf(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Zp;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await ef(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Wc(e){let t=e.binary??"podman",n=e.exec,r=uf(e),o=e.probe??Kc;async function s(p){try{await n(t,["exec",...ie,"--interactive",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:nf,stdin:tf()})}catch{}}async function a(){let p=await H(n,t,["ps","--all","--filter",`label=${_e}`,"--format","json"]),y=p.trim()?JSON.parse(p):[],_=new Map,N=new Map;for(let P of y){let A=P.Labels?.[_e],ee=P.Labels?.[tt];if(!A||!ee)continue;let te=_.get(A)??[];te.push(ee),_.set(A,te);let le=Number(P.Labels?.[vo]??0),T=Number.isInteger(le)?le:0;N.set(A,Math.max(N.get(A)??T,T))}let O=y.filter(P=>P.Labels?.[_e]&&!P.Labels?.[tt]),q=O.map(P=>ff(P.State)),G=await pf(O,mf,(P,A)=>q[A]==="running"?Promise.resolve(!0):e.hasData(P.Labels?.[_e]??""));return O.map((P,A)=>{let ee=P.Labels?.[_e]??"",te=Number(P.Labels?.[ko]);return{ref:ee,status:q[A]??"unknown",generation:Number.isInteger(te)?te:null,hasData:G[A]??!1,services:_.get(ee)??[],...N.has(ee)?{servicesGeneration:N.get(ee)}:{}}})}async function l(){let p=await H(n,t,["ps","--all","--filter",`label=${_e}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(_=>_.Labels?.[_e]&&!_.Labels?.[tt]).length}async function i(p){let y=await(e.majors??((_,N)=>af(n,t,_,N)))(p.ref,p.image);return sf(p.ref,y)}async function u(p){let y=U(p.ref),_=await i(p);if(_)throw new Error(_);let N=e.credentials,O=await vc(e.repository,N?()=>N.forProject(p.ref,p.bucket):null),q={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...$c(O)},G=[...await r(),"run","--detach","--name",y.pod,"--label",`${_e}=${p.ref}`,"--label",`${ko}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...yf(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?Eo(e.podBind).flatMap(P=>["--publish",`${P}:${p.port}:5432`]):[],...Vp(p.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${p.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${p.bucket}`,"--env",`SNOUTPOD_S3_REGION=${p.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...hf(p.oauth),...df(p.tuning).flatMap(P=>["--env",P]),"--env",`SNOUTPOD_RETENTION_FULL=${Ni(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...xc(O),p.image,"snoutpod-postgres"];await H(n,t,G,{env:q})}async function c(p){let y=await H(n,t,["ps","--all","--filter",`label=${_e}=${re(p)}`,"--filter",`label=${tt}`,"--format","json"]),_=y.trim()?JSON.parse(y):[],N=[];for(let O of _){let q=O.Labels?.[tt];if(!q)continue;let G=Number(O.Labels?.[vo]??0);N.push({name:q,container:Fc(p,q),spec:O.Labels?.[Mc]??"",pod:O.Labels?.[Lc]??"",running:O.State==="running",generation:Number.isInteger(G)?G:0})}return N}async function m(p){let y=await n(t,["container","inspect","--format","{{.Id}}",U(p).pod]);return y.code===0?y.stdout.trim():""}async function h(p,y){let _=[];for(let P of y){let A=P.dbUriRole?Bn(P.dbUriRole):null;P.dbUriEnv&&A&&!_.includes(A)&&_.push(Gt(A))}let N=new Map;if(_.length===0)return N;let O={},q=[],G=_.includes(De)?[Tc]:[];return _.includes(Pc)&&G.push(Ac),_.forEach((P,A)=>{let ee=Lp(24).toString("base64url"),te=`SNOUTPOD_SERVICE_PASSWORD_${A}`;N.set(P,ee),O[te]=ee,q.push("--env",te),G.push(`\\getenv service_password_${A} ${te}`),G.push(`\\set service_role_${A} ${P}`),G.push(`alter role :"service_role_${A}" with password :'service_password_${A}';`)}),G.push(""),await H(n,t,["exec",...ie,"--interactive",...q,U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",U(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:O,stdin:G.join(`
580
+ `)}),N}async function x(p){let y=Ln(p.ref);await H(n,t,["volume","create","--ignore",y]),await H(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${go}`,"--entrypoint","bash",p.image,"-c",tc])}async function M(p){let y=await r();if((await n(t,[...y,"image","exists",p])).code===0)return;let _=await n(t,[...y,"pull","--quiet",p]);if(_.code===0)return;let N=rf(p);if(!N||!e.registryLogin)throw new Error(`${p} could not be pulled: ${(_.stderr||_.stdout).trim()}`);await e.registryLogin(N),await H(n,t,[...y,"pull","--quiet",p])}async function W(p){let y=p.services??[],_=await c(p.ref),N;y.some(T=>T.smtpSecretEnv)&&(N=await e.smtpSecret?.().catch(()=>{}));let O=y.length>0?await m(p.ref):"",q=new Map(_.map(T=>[T.name,T])),G=new Map(y.map(T=>[T.name,qp(T,N)])),P=new Set(y.filter(T=>{let ne=q.get(T.name);return!ne||!ne.running||ne.spec!==G.get(T.name)||O===""||ne.pod!==O}).map(T=>T.name)),A=p.servicesGeneration??0;if(P.size===0&&y.length>0&&!_.some(T=>T.generation>=A))for(let T of y)P.add(T.name);let ee=T=>T.dbUriEnv&&T.dbUriRole?Bn(T.dbUriRole):null;for(let T=!0;T;){T=!1;let ne=new Set(y.filter(We=>P.has(We.name)).map(ee));for(let We of y){let Go=ee(We);!P.has(We.name)&&Go!==null&&ne.has(Go)&&(P.add(We.name),T=!0)}}for(let T of new Set(y.filter(ne=>P.has(ne.name)).map(ne=>ne.image)))await M(T);for(let T of _)(P.has(T.name)||!G.has(T.name))&&await H(n,t,["rm","--force","--ignore",T.container]);let te=y.filter(T=>P.has(T.name)),le=await h(p,te);te.some(T=>T.name===Bt)&&await x(p);for(let T of te){let ne=Fc(p.ref,T.name),We=[...await r(),"run","--detach","--name",ne,"--label",`${_e}=${p.ref}`,"--label",`${ko}=${p.generation}`,"--label",`${tt}=${T.name}`,"--label",`${vo}=${p.servicesGeneration??0}`,"--label",`${Mc}=${G.get(T.name)??""}`,"--label",`${Lc}=${O}`,"--network",`container:${U(p.ref).pod}`,"--userns",`container:${U(p.ref).pod}`,"--memory",`${T.memoryMb}m`,"--restart","no",...Xp(T),...T.name===Bt?["--volume",`${Ln(p.ref)}:${go}:ro`]:[],...Hc(Uc(p,T,le,N)),T.image];await H(n,t,We,{env:Uc(p,T,le,N)})}}async function F(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let _=y.stdout.trim();return _==="t"?!0:_==="f"?!1:null}async function pe(p,y){let _=O=>n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",O]),N=Date.now()+y;do{if((await _("checkpoint")).code!==0)return!1;let q=await _("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(G=>setTimeout(G,1e3))}while(Date.now()<N);return!1}async function Pe(p){let y=await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",Di]);return y.code===0?Ii(y.stdout):null}async function ce(p,y){return(await n(t,["exec",U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...Ci(y).flatMap(N=>["-c",N])])).code===0}return{list:a,inRecovery:F,settleTimeline:pe,podHealth:Pe,setSyncNames:ce,preflight:i,create:u,createServices:W,async start(p){await H(n,t,["start",U(p).pod])},async startServices(p){let y=(await c(p)).map(_=>_.container);y.length>0&&await H(n,t,["start",...y])},async applyHostServices(p){let y=[],_=await H(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),N=_.trim()?JSON.parse(_):[],O=new Map;for(let G of N){let P=G.Labels?.[qt];P&&O.set(P,{spec:G.Labels?.[Bc]??"",state:G.State??""})}for(let G of p)try{await q(G)}catch(P){y.push({name:G.name,action:"failed",reason:P instanceof Error?P.message:String(P)})}async function q(G){let P=e.hostServiceLocal?.(G,p),A=P?{...G,...P.env&&Object.keys(P.env).length>0?{env:{...G.env,...P.env}}:{},...P.volumes?{volumes:P.volumes}:{},...P.network?{network:P.network}:{}}:G,ee=Hn(A.name),te=zp(A),le=O.get(A.name);if(O.delete(A.name),le&&le.spec===te){le.state!=="running"&&(await H(n,t,["start",ee]),y.push({name:A.name,action:"started",reason:le.state}));return}let T=le?"the spec changed":"it was not here";await M(A.image),le&&await H(n,t,["rm","--force","--ignore",ee]),await H(n,t,[...await r(),"run","--detach","--name",ee,"--label",`${qt}=${A.name}`,"--label",`${Bc}=${te}`,...A.healthPath?["--label",`${Ao}=${A.hostPort}:${A.healthPath}`]:[],"--memory",`${A.memoryMb}m`,"--restart","no",...A.network?["--network",A.network]:e.network?["--network",e.network]:[],...(A.volumes??[]).flatMap(ne=>["--volume",ne]),...Eo(e.podBind).flatMap(ne=>["--publish",`${ne}:${A.hostPort}:${A.port}`]),...A.adminPort&&A.adminHostPort?["--publish",`127.0.0.1:${A.adminHostPort}:${A.adminPort}`]:[],...Hc(A.env),A.image,...A.command??[]],{env:{...A.env}}),y.push({name:A.name,action:le?"recreated":"created",reason:T})}for(let G of O.keys())await H(n,t,["rm","--force","--ignore",Hn(G)]),y.push({name:G,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(p){let y=await H(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),_=(y.trim()?JSON.parse(y):[]).map(O=>O.Labels?.[qt]??"").filter(O=>O!=="");if(_.length===0)return[];let N=Date.now()+p;for(;;){let O=[];for(let q of _)O.push(await Gc(n,t,q,Hn(q),o));if(O.every(q=>q.ok)||Date.now()>=N)return O;await new Promise(q=>setTimeout(q,500))}},async serviceHealth(p,y){let _=await c(p);if(_.length===0)return[];let N=Date.now()+y;for(;;){let O=await Promise.all(_.map(q=>Gc(n,t,q.name,q.container,o)));if(O.every(q=>q.ok)||Date.now()>=N)return O;await new Promise(q=>setTimeout(q,500))}},async stop(p,y){for(let _ of await c(p))await H(n,t,["stop","--time",String(Gp),_.container]);await H(n,t,["stop","--time",String(y),U(p).pod])},async remove(p){for(let y of await c(p))await H(n,t,["rm","--force","--ignore",y.container]);await H(n,t,["rm","--force","--ignore","--volumes",U(p).pod])},async removeData(p){await H(n,t,["volume","rm","--force",U(p).pod]);let y=Ln(p);(await n(t,["volume","exists",y])).code===0&&await H(n,t,["volume","rm","--force",y])},async applyOwnerPassword(p){let y=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${p.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
581
581
  `);await H(n,t,["exec",...ie,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:p.ownerPassword},stdin:y})},async applyRolePassword(p,y,_){let N=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Gt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
582
- `);await H(n,t,["exec",...ie,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",U(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:_},stdin:N})},async updateExtensions(p){let y=U(p),_=await H(n,t,["exec",...ie,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-tA","-F",_o,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Tc});return Rc(_)},async applyDbAccess(p,y){let _=U(p),N=await H(n,t,["exec",...ie,"--interactive","--env",Fn,_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Fn]:mc(p,y)},stdin:dc,timeoutMs:6e4});return uc(N)},async runProjectSql(p,y){let _=U(p);await H(n,t,["exec",...ie,"--interactive",_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(p,y,_){let N=Hn(p),O=Gt(y),q=Gt(_),G=A=>["exec",N,"psql","--username",O,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",A];(await H(n,t,G(`select 1 from pg_database where datname = '${q}'`))).trim()!=="1"&&await H(n,t,G(`create database "${q}"`))},async replicationSlots(p){let y=await H(n,t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",tc]);return nc(y)},async dropReplicationSlot(p,y){if(!/^[a-z0-9_]{1,63}$/.test(y))throw new Error(`not a replication slot name: ${JSON.stringify(y)}`);await H(n,t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-v","ON_ERROR_STOP=1","-c",`select pg_catalog.pg_drop_replication_slot('${y}'::pg_catalog.name)`])},async renameProject(p,y){y!==p.ref&&await H(n,t,["exec",...ie,"--interactive",U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:ff(y,p.ref)})},async waitReady(p,y){let _=U(p).pod,N=Date.now()+y;for(;;){let O=await n(t,["exec",...ie,_,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(O.code===0&&O.stdout.trim()==="t")return s(p),!0;if(Date.now()>=N)return!1;await new Promise(q=>setTimeout(q,500))}},async waitStandby(p,y){let _=Date.now()+y;for(;;){if(await F(p)===!0)return!0;if(Date.now()>=_)return!1;await new Promise(N=>setTimeout(N,500))}},async capacity(){let[p,y]=await Promise.all([l(),e.capacity()]);return{pods:p,...y}},async databaseBytes(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return y.code===0?io(y.stdout):null},async storageBytes(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select coalesce(sum((metadata->>'size')::bigint), 0) from storage.objects"]);return y.code===0?io(y.stdout):null},async setReadOnly(p,y){let _=U(p);await H(n,t,["exec",...ie,_.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${_.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(y.code!==0)return null;let[_="",N=""]=y.stdout.trim().split(`
583
- `)[0]?.split("|")??[],O=Number(N);return{lastArchivedAt:_.trim()||null,failures:Number.isInteger(O)&&O>=0?O:0}}}}async function Hc(e,t,n,r,o=Gc){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let m=JSON.parse(s.stdout);a=m[0]?.State,l=m[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=Xp(l?.[Ao]);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 mf(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 pf(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function ff(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(`
584
- `)}function gf(e){if(!e?.issuer)return[];try{return["--env",`${Nc}=${jc(e.issuer)}`]}catch{return[]}}function hf(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var _e,ko,tt,vo,Ic,Cc,Hp,qp,Gp,qt,Uc,Vp,qc,Ao,Gc,Qp,ie,tf,Bc,uf,So=j(()=>{"use strict";be();ao();yo();lo();Ht();xc();co();Ac();Oc();wo();Dc();ho();_e="snoutpod.ref",ko="snoutpod.generation",tt="snoutpod.service",vo="snoutpod.services-generation",Ic="snoutpod.service-spec",Cc="snoutpod.service-pod";Hp=10,qp=/^[a-z][a-z0-9-]{0,30}$/;Gp=/^[a-z][a-z0-9_]{0,62}$/;qt="snoutpod.host-service",Uc="snoutpod.host-service-spec";Vp=/^[A-Za-z_][A-Za-z0-9_]*$/;qc=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Ao="snoutpod.health";Gc=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()})},Qp="crun";ie=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],tf=3e4;Bc=200;uf=8});var al={};Jo(al,{DEFAULT_LOCAL_MEMORY_MB:()=>Yc,DEFAULT_LOCAL_PORT:()=>Sf,ENGINE:()=>K,ENGINE_NAME:()=>Ro,IMAGE:()=>jo,NETWORK:()=>qe,NEW_PROJECT_MAJOR:()=>No,NO_BLACKHOLE_ROUTES:()=>el,PUBLIC_IMAGE:()=>$f,PUBLISHED_MAJORS:()=>ht,capacity:()=>Zc,chooseLocalImage:()=>Wt,dropIfUnused:()=>ol,ensureImage:()=>Ef,ensureNetwork:()=>Of,exec:()=>Q,hasData:()=>Wn,localImageFor:()=>Qc,localPsql:()=>Df,localRuntime:()=>Rf,localSpec:()=>rl,localStatus:()=>Do,localUri:()=>nl,networkPolicyInPlace:()=>tl,newLocalPassword:()=>vf,newLocalRef:()=>kf,podLog:()=>sl,podName:()=>yt,podmanReady:()=>Af,removeLocal:()=>Mf,staleImage:()=>Oo,startLocal:()=>jf,stopLocal:()=>Cf,volumePgVersion:()=>Gn});import{randomBytes as zc}from"node:crypto";import{existsSync as Kc}from"node:fs";import{platform as Kn}from"node:os";import{delimiter as yf,join as wf}from"node:path";function bf(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Kn()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(yf))if(t&&e.some(n=>Kc(wf(t,n))))return"podman";return Kc("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Wt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Kt(No);let n=Number(To(e));if(!ht.includes(n)){let r=e.trim()||"(empty)",o=ht.map(s=>Kt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ht.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Kt(n)}function Kt(e){return{name:`${_f}:${e}`,published:`${Vc}:${e}`,major:e}}function kf(){return Pi(e=>zc(e))}function vf(){return zc(18).toString("base64url")}async function Ef(e,t=Q,n=Wt(null)){let r=n.published;if(!r)return null;if((await t(K,["image","exists",n.name])).code===0)return await Pf(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(K,["pull",r],{timeoutMs:Xc}).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(K,["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 Pf(e,t,n,r){let o=await t(K,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
585
- `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(K,["pull",r],{timeoutMs:Xc}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(l));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
586
- `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(K,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(K,["tag",r,n]);if(m.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${m.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function Af(e=xf,t=jo){if(Ro!=="podman")return`SNOUTPOD_ENGINE=${Ro}: 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 Q(K,["--version"])}catch{return`${K} 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`${K} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await Q(K,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await Q(K,["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 Tf(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 Q(K,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
587
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Wn(e){let t=Kn()==="darwin"||Kn()==="win32";return hc(Q,e,{binary:K,look:t?Tf:void 0})}async function Gn(e,t=Q,n=Kn()){let r=U(e).pod;if((await t(K,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of ht){let l=Kt(a).name;if((await t(K,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${ht.map(a=>Kt(a).published).join(" or ")}).`);o=await t(K,["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(K,["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(K,["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 Qc(e,t=Gn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Wt(null));re(e);let n=t===Gn?Jn.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Wt(o)}catch(s){throw new Error(`${yt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Gn&&(Jn.set(e,r),r.catch(()=>Jn.delete(e))),r}async function Zc(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Rf(e){return Jc({exec:Q,binary:K,network:qe,hasData:Wn,capacity:Zc,repository:e})}function yt(e){return U(e).pod}async function Of(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await Nf()||await Q(K,Cn({name:qe}));return}let t=Cn({name:qe,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await tl())return;await e?.(),await Q(K,["network","rm","--force",qe]).catch(()=>{});let o=await Q(K,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${el}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${qe} with egress blocked${n?`: ${n}`:""}`)}async function Nf(){return(await Q(K,["network","inspect",qe,"--format","json"])).code===0}async function tl(){let e=await Q(K,["network","inspect",qe,"--format","json"]);if(e.code!==0)return!1;try{return zi(JSON.parse(e.stdout)?.[0])}catch{return!1}}function nl(e){let t=U(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function rl(e,t={}){return re(e.ref),{ref:e.ref,generation:1,image:t.image??jo,memoryMb:t.memoryMb??Yc,cpus:null,bucket:"",region:"us-east-1",ownerPassword:e.ownerPassword,adminPassword:e.adminPassword,port:e.port,tuning:t.tuning??null,backup:null,services:t.services??null}}function Jt(e,t){e.onProgress?.(t)}async function jf(e,t,n={}){let r=n.image??(await Qc(t.ref,n.pgVersion)).name,o=rl(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Wn,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??Oo)(t.ref,r))?(Jt(n,"Starting the database on the newer image. Its data stays as it is."),await e.remove(t.ref),await e.create(o),await(n.dropImage??ol)(i)):l.status==="stopped"?(Jt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Jt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??Oo)(t.ref,r)&&Jt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Jt(n,await a(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(o)),!await e.waitReady(t.ref,s))throw new Error(await If(t.ref,s,n.podLog??sl));return await e.applyOwnerPassword(o),await e.createServices(o),Do(e,t,n)}function Wc(e){return e.trim().replace(/^sha256:/,"")}async function Oo(e,t){let[n,r]=await Promise.all([Q(K,["container","inspect","--format","{{.Image}}",yt(e)]).catch(()=>null),Q(K,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Wc(n.stdout),s=Wc(r.stdout);return o&&s&&o!==s?o:null}async function ol(e){let t=await Q(K,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await Q(K,["rmi",e]).catch(()=>{})}async function Df(e,t){let n=U(e.ref);return Q(K,["exec","--interactive","-e","PGPASSWORD",yt(e.ref),"psql","--host","127.0.0.1","--port","5432","--username",n.ownerRole,"--dbname",n.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA","--file","-"],{stdin:t,env:{PGPASSWORD:e.ownerPassword}})}async function sl(e){let t=await Q(K,["logs","--tail","20",yt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function If(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${yt(e)} never became ready in ${t}ms
582
+ `);await H(n,t,["exec",...ie,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",U(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:_},stdin:N})},async updateExtensions(p){let y=U(p),_=await H(n,t,["exec",...ie,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-tA","-F",_o,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Oc});return Nc(_)},async applyDbAccess(p,y){let _=U(p),N=await H(n,t,["exec",...ie,"--interactive","--env",Fn,_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Fn]:fc(p,y)},stdin:mc,timeoutMs:6e4});return pc(N)},async runProjectSql(p,y){let _=U(p);await H(n,t,["exec",...ie,"--interactive",_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(p,y,_){let N=Hn(p),O=Gt(y),q=Gt(_),G=A=>["exec",N,"psql","--username",O,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",A];(await H(n,t,G(`select 1 from pg_database where datname = '${q}'`))).trim()!=="1"&&await H(n,t,G(`create database "${q}"`))},async replicationSlots(p){let y=await H(n,t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",rc]);return oc(y)},async dropReplicationSlot(p,y){if(!/^[a-z0-9_]{1,63}$/.test(y))throw new Error(`not a replication slot name: ${JSON.stringify(y)}`);await H(n,t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-v","ON_ERROR_STOP=1","-c",`select pg_catalog.pg_drop_replication_slot('${y}'::pg_catalog.name)`])},async renameProject(p,y){y!==p.ref&&await H(n,t,["exec",...ie,"--interactive",U(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:gf(y,p.ref)})},async waitReady(p,y){let _=U(p).pod,N=Date.now()+y;for(;;){let O=await n(t,["exec",...ie,_,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(O.code===0&&O.stdout.trim()==="t")return s(p),!0;if(Date.now()>=N)return!1;await new Promise(q=>setTimeout(q,500))}},async waitStandby(p,y){let _=Date.now()+y;for(;;){if(await F(p)===!0)return!0;if(Date.now()>=_)return!1;await new Promise(N=>setTimeout(N,500))}},async capacity(){let[p,y]=await Promise.all([l(),e.capacity()]);return{pods:p,...y}},async databaseBytes(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return y.code===0?io(y.stdout):null},async storageBytes(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select coalesce(sum((metadata->>'size')::bigint), 0) from storage.objects"]);return y.code===0?io(y.stdout):null},async setReadOnly(p,y){let _=U(p);await H(n,t,["exec",...ie,_.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${_.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(p){let y=await n(t,["exec",...ie,U(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(y.code!==0)return null;let[_="",N=""]=y.stdout.trim().split(`
583
+ `)[0]?.split("|")??[],O=Number(N);return{lastArchivedAt:_.trim()||null,failures:Number.isInteger(O)&&O>=0?O:0}}}}async function Gc(e,t,n,r,o=Kc){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let m=JSON.parse(s.stdout);a=m[0]?.State,l=m[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=Qp(l?.[Ao]);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 pf(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 ff(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function gf(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(`
584
+ `)}function hf(e){if(!e?.issuer)return[];try{return["--env",`${Dc}=${Ic(e.issuer)}`]}catch{return[]}}function yf(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var _e,ko,tt,vo,Mc,Lc,Gp,Jp,Kp,qt,Bc,Yp,Jc,Ao,Kc,Zp,ie,nf,qc,mf,So=j(()=>{"use strict";be();ao();yo();lo();Ht();Ec();co();Rc();jc();wo();Cc();ho();_e="snoutpod.ref",ko="snoutpod.generation",tt="snoutpod.service",vo="snoutpod.services-generation",Mc="snoutpod.service-spec",Lc="snoutpod.service-pod";Gp=10,Jp=/^[a-z][a-z0-9-]{0,30}$/;Kp=/^[a-z][a-z0-9_]{0,62}$/;qt="snoutpod.host-service",Bc="snoutpod.host-service-spec";Yp=/^[A-Za-z_][A-Za-z0-9_]*$/;Jc=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Ao="snoutpod.health";Kc=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()})},Zp="crun";ie=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],nf=3e4;qc=200;mf=8});var cl={};Jo(cl,{DEFAULT_LOCAL_MEMORY_MB:()=>Qc,DEFAULT_LOCAL_PORT:()=>kf,ENGINE:()=>K,ENGINE_NAME:()=>Ro,IMAGE:()=>jo,NETWORK:()=>Ge,NEW_PROJECT_MAJOR:()=>No,NO_BLACKHOLE_ROUTES:()=>nl,PUBLIC_IMAGE:()=>Ef,PUBLISHED_MAJORS:()=>ht,capacity:()=>tl,chooseLocalImage:()=>Wt,dropIfUnused:()=>al,ensureImage:()=>Pf,ensureNetwork:()=>Nf,exec:()=>Q,hasData:()=>Wn,localImageFor:()=>el,localPsql:()=>If,localRuntime:()=>Of,localSpec:()=>sl,localStatus:()=>Do,localUri:()=>ol,networkPolicyInPlace:()=>rl,newLocalPassword:()=>xf,newLocalRef:()=>vf,podLog:()=>il,podName:()=>yt,podmanReady:()=>Tf,removeLocal:()=>Lf,staleImage:()=>Oo,startLocal:()=>Df,stopLocal:()=>Mf,volumePgVersion:()=>Gn});import{randomBytes as Yc}from"node:crypto";import{existsSync as zc}from"node:fs";import{platform as Kn}from"node:os";import{delimiter as wf,join as bf}from"node:path";function _f(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Kn()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(wf))if(t&&e.some(n=>zc(bf(t,n))))return"podman";return zc("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Wt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Kt(No);let n=Number(To(e));if(!ht.includes(n)){let r=e.trim()||"(empty)",o=ht.map(s=>Kt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ht.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Kt(n)}function Kt(e){return{name:`${Sf}:${e}`,published:`${Xc}:${e}`,major:e}}function vf(){return Ti(e=>Yc(e))}function xf(){return Yc(18).toString("base64url")}async function Pf(e,t=Q,n=Wt(null)){let r=n.published;if(!r)return null;if((await t(K,["image","exists",n.name])).code===0)return await Af(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(K,["pull",r],{timeoutMs:Zc}).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(K,["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 Af(e,t,n,r){let o=await t(K,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
585
+ `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(K,["pull",r],{timeoutMs:Zc}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(l));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
586
+ `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(K,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(K,["tag",r,n]);if(m.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${m.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function Tf(e=$f,t=jo){if(Ro!=="podman")return`SNOUTPOD_ENGINE=${Ro}: 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 Q(K,["--version"])}catch{return`${K} 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`${K} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await Q(K,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await Q(K,["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 Rf(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 Q(K,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
587
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Wn(e){let t=Kn()==="darwin"||Kn()==="win32";return wc(Q,e,{binary:K,look:t?Rf:void 0})}async function Gn(e,t=Q,n=Kn()){let r=U(e).pod;if((await t(K,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of ht){let l=Kt(a).name;if((await t(K,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${ht.map(a=>Kt(a).published).join(" or ")}).`);o=await t(K,["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(K,["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(K,["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 el(e,t=Gn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Wt(null));re(e);let n=t===Gn?Jn.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Wt(o)}catch(s){throw new Error(`${yt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Gn&&(Jn.set(e,r),r.catch(()=>Jn.delete(e))),r}async function tl(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Of(e){return Wc({exec:Q,binary:K,network:Ge,hasData:Wn,capacity:tl,repository:e})}function yt(e){return U(e).pod}async function Nf(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await jf()||await Q(K,Cn({name:Ge}));return}let t=Cn({name:Ge,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await rl())return;await e?.(),await Q(K,["network","rm","--force",Ge]).catch(()=>{});let o=await Q(K,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${nl}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ge} with egress blocked${n?`: ${n}`:""}`)}async function jf(){return(await Q(K,["network","inspect",Ge,"--format","json"])).code===0}async function rl(){let e=await Q(K,["network","inspect",Ge,"--format","json"]);if(e.code!==0)return!1;try{return Yi(JSON.parse(e.stdout)?.[0])}catch{return!1}}function ol(e){let t=U(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function sl(e,t={}){return re(e.ref),{ref:e.ref,generation:1,image:t.image??jo,memoryMb:t.memoryMb??Qc,cpus:null,bucket:"",region:"us-east-1",ownerPassword:e.ownerPassword,adminPassword:e.adminPassword,port:e.port,tuning:t.tuning??null,backup:null,services:t.services??null}}function Jt(e,t){e.onProgress?.(t)}async function Df(e,t,n={}){let r=n.image??(await el(t.ref,n.pgVersion)).name,o=sl(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Wn,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??Oo)(t.ref,r))?(Jt(n,"Starting the database on the newer image. Its data stays as it is."),await e.remove(t.ref),await e.create(o),await(n.dropImage??al)(i)):l.status==="stopped"?(Jt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Jt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??Oo)(t.ref,r)&&Jt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Jt(n,await a(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(o)),!await e.waitReady(t.ref,s))throw new Error(await Cf(t.ref,s,n.podLog??il));return await e.applyOwnerPassword(o),await e.createServices(o),Do(e,t,n)}function Vc(e){return e.trim().replace(/^sha256:/,"")}async function Oo(e,t){let[n,r]=await Promise.all([Q(K,["container","inspect","--format","{{.Image}}",yt(e)]).catch(()=>null),Q(K,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Vc(n.stdout),s=Vc(r.stdout);return o&&s&&o!==s?o:null}async function al(e){let t=await Q(K,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await Q(K,["rmi",e]).catch(()=>{})}async function If(e,t){let n=U(e.ref);return Q(K,["exec","--interactive","-e","PGPASSWORD",yt(e.ref),"psql","--host","127.0.0.1","--port","5432","--username",n.ownerRole,"--dbname",n.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA","--file","-"],{stdin:t,env:{PGPASSWORD:e.ownerPassword}})}async function il(e){let t=await Q(K,["logs","--tail","20",yt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Cf(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${yt(e)} never became ready in ${t}ms
588
588
  --- pod log ---
589
- ${r}`}async function Cf(e,t,n={}){re(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,pc),Do(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Mf(e,t,n){re(t),await e.remove(t),n&&(await e.removeData(t),Jn.delete(t))}async function Do(e,t,n={}){re(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Wn;return{ref:t.ref,exists:!!r,status:r?.status??"absent",ready:o?await e.waitReady(t.ref,1):!1,hasData:r?.hasData??await s(t.ref),port:t.port,databaseBytes:o?await e.databaseBytes(t.ref):null,services:r?.services??[],uri:t.port>0&&t.ownerPassword?nl(t):null}}var Ro,K,No,ht,_f,Vc,jo,qe,Sf,Yc,Q,xf,$f,Xc,Jn,el,il=j(()=>{"use strict";fc();Ht();yc();mo();So();be();Ro=process.env.SNOUTPOD_ENGINE??"podman";K=bf(),No=18,ht=[17,18],_f="snoutpod/postgres",Vc="ghcr.io/snoutdata/snoutpod-postgres";jo=Wt(null).name,qe=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Sf=54322,Yc=512,Q=gc();xf="cd packages/snoutpod && npm run image:build",$f=`${Vc}:${No}`;Xc=20*60*1e3;Jn=new Map;el="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});de();var fe=["--admin","--token","--project"],er=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],nt=[...fe,"--plan","--no-wait","--timeout",...er],ue=[...nt,"--yes"],tr=[{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: signInAnonymously() 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 presence, 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 delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions 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 anon and service_role API keys for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...fe]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...fe]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...fe]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...nt]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...nt,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...ue,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ue]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...nt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...nt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ue,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ue]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ue]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ue,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ue]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ue,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ue,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ue,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ue,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...nt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ue,"--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:[...ue]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...fe,...er]},{name:"cluster jobs",summary:"The latest jobs",flags:[...fe]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...fe]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...fe,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...fe,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...fe,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...fe,...er,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...fe,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],Wo={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","--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 nr(e,t){return Wo[`${e} ${t}`]??Wo[t]??""}function zo(e,t){let n=tr.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}Y();oe();z();import{spawn as Id}from"node:child_process";function Cd(e){return`snout-${e}`}function dt(e,t,n){return new Promise((r,o)=>{let s=Id("docker",["compose","-p",Cd(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 S("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 S("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Md(e){let t=await dt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new S("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 Cs(e){let t=await Md(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${$(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${M([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
590
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Ms(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await dt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new S("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function Ls(e){let t=await dt(e,["stop"],!0);if(t.code!==0)throw new S("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Us(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${M([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
591
- `)})}St();import{cpSync as Hs,existsSync as Rt,readdirSync as Jd,readFileSync as qs,rmSync as Gs,statSync as Kd,writeFileSync as Js}from"node:fs";import{join as ye,resolve as wn}from"node:path";var Ld=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Ud(e){return Ld.test(e)}function Xe(e){if(!Ud(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 Fd=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Bd(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!==".."&&Fd.test(n))}var Sr=2*1024*1024,Fs=200;function Bs(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>Fs)throw new Error(`a function may hold ${Fs} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Bd(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=Hd(o);if(s.length>Sr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Sr/1024} KB. A function is source code; put large files in Storage.`);return o}function Hd(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 qd=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Gd=["SNOUTDATA_","SNOUT_"];function Tt(e){if(!qd.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Gd.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 Hh=64*1024;oe();de();z();function Ks(e){let t=ye(e.folder,"functions");return Rt(t)?Jd(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Kd(ye(t,n)).isDirectory()).sort():[]}async function bn(e){d(w(" docker compose run --rm functions-deploy"));let t=await dt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new S("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Ws(e,t,n){let r=ye(e.folder,".env"),o=qs(r,"utf8"),s=(tn(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return Js(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
589
+ ${r}`}async function Mf(e,t,n={}){re(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,gc),Do(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Lf(e,t,n){re(t),await e.remove(t),n&&(await e.removeData(t),Jn.delete(t))}async function Do(e,t,n={}){re(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Wn;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?ol(t):null}}var Ro,K,No,ht,Sf,Xc,jo,Ge,kf,Qc,Q,$f,Ef,Zc,Jn,nl,ll=j(()=>{"use strict";hc();Ht();bc();mo();So();be();Ro=process.env.SNOUTPOD_ENGINE??"podman";K=_f(),No=18,ht=[17,18],Sf="snoutpod/postgres",Xc="ghcr.io/snoutdata/snoutpod-postgres";jo=Wt(null).name,Ge=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",kf=54322,Qc=512,Q=yc();$f="cd packages/snoutpod && npm run image:build",Ef=`${Xc}:${No}`;Zc=20*60*1e3;Jn=new Map;nl="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});de();var fe=["--admin","--token","--project"],er=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],nt=[...fe,"--plan","--no-wait","--timeout",...er],ue=[...nt,"--yes"],tr=[{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: signInAnonymously() 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 presence, 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:"secrets set",summary:"Set the environment functions 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 anon and service_role API keys for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...fe]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...fe]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...fe]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...nt]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...nt,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...ue,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ue]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...nt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...nt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ue,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ue]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ue]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ue,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ue]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ue,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ue,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ue,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ue,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...nt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ue,"--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:[...ue]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...fe,...er]},{name:"cluster jobs",summary:"The latest jobs",flags:[...fe]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...fe]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...fe,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...fe,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...fe,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...fe,...er,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...fe,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],Wo={"--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","--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 nr(e,t){return Wo[`${e} ${t}`]??Wo[t]??""}function zo(e,t){let n=tr.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}X();oe();z();import{spawn as Md}from"node:child_process";function Ld(e){return`snout-${e}`}function dt(e,t,n){return new Promise((r,o)=>{let s=Md("docker",["compose","-p",Ld(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 S("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 S("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Ud(e){let t=await dt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new S("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 Cs(e){let t=await Ud(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${v(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])])}
590
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Ms(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await dt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new S("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function Ls(e){let t=await dt(e,["stop"],!0);if(t.code!==0)throw new S("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Us(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${L([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
591
+ `)})}St();import{cpSync as Hs,existsSync as Rt,readdirSync as Wd,readFileSync as qs,rmSync as Gs,statSync as zd,writeFileSync as Js}from"node:fs";import{join as ye,resolve as wn}from"node:path";var Fd=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Bd(e){return Fd.test(e)}function Xe(e){if(!Bd(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 Hd=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function qd(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!==".."&&Hd.test(n))}var Sr=2*1024*1024,Fs=200;function Bs(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>Fs)throw new Error(`a function may hold ${Fs} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!qd(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=Gd(o);if(s.length>Sr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Sr/1024} KB. A function is source code; put large files in Storage.`);return o}function Gd(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 Jd=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Kd=["SNOUTDATA_","SNOUT_"];function Tt(e){if(!Jd.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Kd.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 qh=64*1024;oe();de();z();function Ks(e){let t=ye(e.folder,"functions");return Rt(t)?Wd(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&zd(ye(t,n)).isDirectory()).sort():[]}async function bn(e){d(w(" docker compose run --rm functions-deploy"));let t=await dt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new S("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Ws(e,t,n){let r=ye(e.folder,".env"),o=qs(r,"utf8"),s=(tn(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return Js(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
592
592
  `)}${l}
593
- `),!0}async function kr(e,t,n){Xe(t);let r=wn(n.dir??ye(process.cwd(),"functions",t));if(!Rt(ye(r,"index.ts")))throw new f(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ye(e.folder,"functions",t);if(wn(o)!==r){Gs(o,{recursive:!0,force:!0}),Hs(r,o,{recursive:!0});let a=ye(r,"..","_shared");Rt(a)&&wn(a)!==wn(ye(e.folder,"functions","_shared"))&&Hs(a,ye(e.folder,"functions","_shared"),{recursive:!0})}let s=Ws(e,t,!n.verifyJwt);if(await bn(e),s){let a=await dt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new S("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:`${e.apiUrl}/functions/v1/${t}`,verifyJwt:n.verifyJwt,local:e.folder}}async function zs(e,t,n){let r=await kr(e,t,n);g(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 _n(e){return{ref:e.ref,local:e.folder,functions:Ks(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function Vs(e){let t=Ks(e);g(_n(e),()=>{if(t.length===0){d(`No functions in ${ye(e.folder,"functions")}.`);return}process.stdout.write(`${M([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
593
+ `),!0}async function kr(e,t,n){Xe(t);let r=wn(n.dir??ye(process.cwd(),"functions",t));if(!Rt(ye(r,"index.ts")))throw new f(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ye(e.folder,"functions",t);if(wn(o)!==r){Gs(o,{recursive:!0,force:!0}),Hs(r,o,{recursive:!0});let a=ye(r,"..","_shared");Rt(a)&&wn(a)!==wn(ye(e.folder,"functions","_shared"))&&Hs(a,ye(e.folder,"functions","_shared"),{recursive:!0})}let s=Ws(e,t,!n.verifyJwt);if(await bn(e),s){let a=await dt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new S("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:`${e.apiUrl}/functions/v1/${t}`,verifyJwt:n.verifyJwt,local:e.folder}}async function zs(e,t,n){let r=await kr(e,t,n);g(r,()=>{d(`${v(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 _n(e){return{ref:e.ref,local:e.folder,functions:Ks(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function Vs(e){let t=Ks(e);g(_n(e),()=>{if(t.length===0){d(`No functions in ${ye(e.folder,"functions")}.`);return}process.stdout.write(`${L([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
594
594
  `)})}async function vr(e,t){Xe(t);let n=ye(e.folder,"functions",t);if(!Rt(n))throw new S("not-found",`this local project has no function called ${t}`);return Gs(n,{recursive:!0,force:!0}),Ws(e,t,!1),await bn(e),{ref:e.ref,name:t,removed:!0}}async function Ys(e,t){g(await vr(e,t),()=>d(`${t} is gone.`))}function Sn(e){return ye(e.folder,"functions",".env")}function xr(e){let t=Sn(e);return Rt(t)?tn(qs(t,"utf8")):new Map}function Xs(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Js(Sn(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
595
595
  `)+(t.size?`
596
- `:""))}async function Qs(e,t){for(let r of t){let o=Tt(r.name);if(o)throw new f(o)}let n=xr(e);for(let r of t)n.set(r.name,r.value);Xs(e,n),await bn(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${$(r.name)} set in ${Sn(e)}`)})}function kn(e){return{ref:e.ref,local:e.folder,secrets:[...xr(e).keys()].sort().map(t=>({name:t}))}}function Zs(e){let t=kn(e),n=t.secrets.map(r=>r.name);g(t,()=>{d(n.length?n.join(`
597
- `):`No function secrets in ${Sn(e)}.`)})}async function ea(e,t){let n=xr(e);if(!n.delete(t))throw new S("not-found",`${t} is not set for this local project`);Xs(e,n),await bn(e),g({ref:e.ref,unset:t},()=>d(`${t} unset.`))}oe();Ve();Ie();pr();an();z();hn();Y();Y();oe();Ie();z();async function ta(){let{projects:e,allowance:t}=await he();g({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${M(Wd(e))}
598
- `)})}function Wd(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):"-"]:[],Oe(n.lastConnectionAt)])]}async function $r(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await At(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&ot(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;g({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
599
- `),d(""),d(`Ready. ${$(n)} in ${r.region}, at ${r.host}.`),d(e.showUrl?"The password is stored for you: `snoutdata db url` prints this again.":"`snoutdata db url` prints the connection string (it holds the password, so it is not shown here).")})}async function na(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Pt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new S("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ra(e){let t=await b("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
600
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function oa(){let{teams:e}=await yn();g({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${M([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
601
- `)})}async function sa(e){let{teams:t}=await yn(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}Y();z();async function aa(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
602
- `),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 ia(){let{tokens:e}=await b("cloud-token-list");g({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${M([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?$(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Oe(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
603
- `)})}async function ca(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);g(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}Y();de();z();async function la(e){let t=await me(e);g({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${ua(t.anonKey,t.serviceRoleKey)}
604
- `),Yd(t.keysIssuedAt)})}async function da(e,t){if(!t.force)throw d(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),d("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new f("keys rotate needs --force, because this cannot be undone");let n=await b("cloud-project-rotate-jwt",{ref:e});g(n,()=>{process.stdout.write(`${ua(n.anonKey,n.serviceRoleKey)}
605
- `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function ua(e,t){return M([["KEY","VALUE"],["anon",e],["service_role",t]])}function Yd(e){d(""),d(`${$("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`${$("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}Y();de();z();import{readdir as Qd,readFile as Zd,stat as eu}from"node:fs/promises";import{join as Pr,relative as tu,sep as nu}from"node:path";var ma=["functions"];async function Er(e){try{return(await eu(e)).isDirectory()}catch{return!1}}async function ru(e,t){if(t){if(!await Er(t))throw new f(`${t} is not a directory`);return t}for(let n of ma){let r=Pr(n,e);if(await Er(r))return r}throw new f(`no folder found for ${e} at ${ma[0]}/${e}; use --dir to say where it is.`)}async function pa(e){let t=[];async function n(r){for(let o of await Qd(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Pr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Zd(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new f(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:tu(e,s).split(nu).join("/"),text:l})}}return await n(e),t}async function ou(e,t,n){let r=await pa(t);if(r.length===0)throw new f(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new f(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Pr(t,"..","_shared");if(!await Er(s))return{entrypoint:o,files:r};let a=(await pa(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function Ar(e,t,n){Xe(t);let r=await ru(t,n.dir),{entrypoint:o,files:s}=await ou(t,r,n.entrypoint),a=Bs({entrypoint:o,files:s});return{answer:await b("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function fa(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Ar(e,t,n),l=r.functions.find(i=>i.name===t);g(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 Tr(e){return b("cloud-project-functions",{ref:e})}function Rr(e,t){return Xe(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function Or(e,t,n){if(Xe(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new f("functions size needs --memory MB, --concurrency N, or --reset");return b("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function su(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 ga(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 ha(e,t,n){let r=await Or(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?su(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=ga(r.limit);a&&d(w(` ${a}`))})}async function ya(e){let t=await Tr(e);g(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${M([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":$("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])])}
606
- `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=ga(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}async function wa(e,t){let n=await Rr(e,t);g(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}Y();de();z();function ba(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function _a(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Sa(e,t){if(e.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await _a()}]}return e.map(n=>{let r=ba(n);if(!r)throw new f(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function ka(e,t,n){if(t.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await _a()})}else for(let s of t){let a=ba(s);if(!a)throw new f(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Tt(s.name);if(a)throw new f(a)}let o=null;for(let s of r)o=await b("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)d(`${$(s.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 va(e,t){let n=Tt(t);if(n)throw new f(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{d(`${t} is gone.`)})}async function xa(e){let t=await b("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${M([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Oe(n.updatedAt)])])}
607
- `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Mt}from"node:child_process";import{appendFileSync as Ka,existsSync as Tn,readFileSync as Wa,statSync as Wu,unlinkSync as zu,writeFileSync as mt}from"node:fs";import{dirname as Vu,join as Rn}from"node:path";import{tmpdir as Yu}from"node:os";import{open as za}from"node:fs/promises";function $a(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 Ea(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 Pa(e){let t=e.split(`
608
- `).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 Nr={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},cu=/^(snoutpod_admin|snout_[a-z]+_admin)$/,vn=new Set(["auth","storage","push","realtime","snout_realtime","_realtime","graphql","graphql_public","extensions","net","cron","pgbouncer","vault","pgsodium"]),lu=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,du={auth:"auth",storage:"storage",push:"push"},uu=new Set(["push.settings"]),mu=["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 pu(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=mu.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 fu(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 Aa(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
609
- `)){let u=pu(i);if(!u)continue;let{type:c,schema:m,name:h,owner:v}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let W=/^(schema_migrations|migrations)$/.test(h)&&vn.has(m);if(/realtime/.test(m)||W||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(m==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let F=du[m];if(F){F==="storage"&&h==="objects"&&o.add("Storage objects are restored as rows; the files themselves are not part of an export. Upload them again into this project."),n.set(F,[...n.get(F)??[],u]);continue}if(vn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?vn.has(h):vn.has(m))||lu.test(h)||cu.test(v)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=fu(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(h=>h.type==="TABLE DATA"&&uu.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),m=u.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:c,tables:m,lines:gu(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function gu(e,t){let n=l=>`${l.schema}.${l.name}`,r=new Set(e.map(n)),o=new Set,s=[],a=[...e];for(;a.length>0;){let i=a.find(u=>[...t.get(n(u))??[]].every(c=>!r.has(c)||o.has(c)||c===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}Y();de();z();Y();oe();var Dr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],Ra=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Ot(e){return e==="done"||e==="failed"||e==="cancelled"}function Nt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new S("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new $e(_u(e.project))}let n=e.admin??Ta(t.LEPIS_ADMIN_URL);if(!n)throw new S("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??Ta(t.LEPIS_ADMIN_TOKEN);if(!r)throw new S("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new jr(hu(n),r)}function Ta(e){return e===void 0||e===""?void 0:e}function hu(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new S("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new S("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new S("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!yu(n.hostname))throw new S("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 yu(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var wu=6e4,jr=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(wu)})}catch(i){let u=nn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new S(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 S("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw Oa(s.status,l);return l}},bu={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function Oa(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?bu[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":ze(e));return s==="not-signed-in"?new S(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new S(s,r,o)}function _u(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new S("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var Su={"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"},ku="cloud-project-shards",$e=class{target;ref;#t;constructor(t,n=b){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Fe(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=Su[t.op];if(!n)throw new S("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 S("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 S("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(ku,{ref:this.ref,...t})}catch(n){throw vu(n)}}};function Fe(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 vu(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return Oa(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":ze(e.status);return new S(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Dt(e){if(Array.isArray(e))return`[${e.map(Dt).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)}:${Dt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Mr=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}
596
+ `:""))}async function Qs(e,t){for(let r of t){let o=Tt(r.name);if(o)throw new f(o)}let n=xr(e);for(let r of t)n.set(r.name,r.value);Xs(e,n),await bn(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${v(r.name)} set in ${Sn(e)}`)})}function kn(e){return{ref:e.ref,local:e.folder,secrets:[...xr(e).keys()].sort().map(t=>({name:t}))}}function Zs(e){let t=kn(e),n=t.secrets.map(r=>r.name);g(t,()=>{d(n.length?n.join(`
597
+ `):`No function secrets in ${Sn(e)}.`)})}async function ea(e,t){let n=xr(e);if(!n.delete(t))throw new S("not-found",`${t} is not set for this local project`);Xs(e,n),await bn(e),g({ref:e.ref,unset:t},()=>d(`${t} unset.`))}oe();Ve();Ce();pr();an();z();hn();X();X();oe();Ce();z();async function ta(){let{projects:e,allowance:t}=await he();g({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${L(Vd(e))}
598
+ `)})}function Vd(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?v("read-only"):n.state==="ready"?n.state:v(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Oe(n.lastConnectionAt)])]}async function $r(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await At(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&ot(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;g({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
599
+ `),d(""),d(`Ready. ${v(n)} in ${r.region}, at ${r.host}.`),d(e.showUrl?"The password is stored for you: `snoutdata db url` prints this again.":"`snoutdata db url` prints the connection string (it holds the password, so it is not shown here).")})}async function na(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Pt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new S("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ra(e){let t=await b("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
600
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function oa(){let{teams:e}=await yn();g({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${L([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
601
+ `)})}async function sa(e){let{teams:t}=await yn(),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}X();z();async function aa(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
602
+ `),d(""),d(`${v(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 ia(){let{tokens:e}=await b("cloud-token-list");g({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${L([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?v(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Oe(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
603
+ `)})}async function ca(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);g(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}X();de();z();async function la(e){let t=await me(e);g({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${ua(t.anonKey,t.serviceRoleKey)}
604
+ `),Qd(t.keysIssuedAt)})}async function da(e,t){if(!t.force)throw d(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),d("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new f("keys rotate needs --force, because this cannot be undone");let n=await b("cloud-project-rotate-jwt",{ref:e});g(n,()=>{process.stdout.write(`${ua(n.anonKey,n.serviceRoleKey)}
605
+ `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function ua(e,t){return L([["KEY","VALUE"],["anon",e],["service_role",t]])}function Qd(e){d(""),d(`${v("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`${v("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}X();de();z();import{readdir as eu,readFile as tu,stat as nu}from"node:fs/promises";import{join as Pr,relative as ru,sep as ou}from"node:path";var ma=["functions"];async function Er(e){try{return(await nu(e)).isDirectory()}catch{return!1}}async function su(e,t){if(t){if(!await Er(t))throw new f(`${t} is not a directory`);return t}for(let n of ma){let r=Pr(n,e);if(await Er(r))return r}throw new f(`no folder found for ${e} at ${ma[0]}/${e}; use --dir to say where it is.`)}async function pa(e){let t=[];async function n(r){for(let o of await eu(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Pr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await tu(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new f(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:ru(e,s).split(ou).join("/"),text:l})}}return await n(e),t}async function au(e,t,n){let r=await pa(t);if(r.length===0)throw new f(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new f(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Pr(t,"..","_shared");if(!await Er(s))return{entrypoint:o,files:r};let a=(await pa(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function Ar(e,t,n){Xe(t);let r=await su(t,n.dir),{entrypoint:o,files:s}=await au(t,r,n.entrypoint),a=Bs({entrypoint:o,files:s});return{answer:await b("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function ga(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Ar(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${v(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d(v(" 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 Tr(e){return b("cloud-project-functions",{ref:e})}function Rr(e,t){return Xe(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function Or(e,t,n){if(Xe(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new f("functions size needs --memory MB, --concurrency N, or --reset");return b("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function iu(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 ha(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 ya(e,t,n){let r=await Or(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?iu(o,r.limit):null;d(`${v(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=ha(r.limit);a&&d(w(` ${a}`))})}async function wa(e){let t=await Tr(e);g(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${L([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":v("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])])}
606
+ `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=ha(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function fa(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"?v("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function ba(e,t,n){let r=a=>b("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(g({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${fa(a)}
607
+ `)}),!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(`${fa(l)}
608
+ `),s=l.at}}async function _a(e,t){let n=await Rr(e,t);g(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}X();de();z();function Sa(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function ka(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function va(e,t){if(e.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await ka()}]}return e.map(n=>{let r=Sa(n);if(!r)throw new f(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function xa(e,t,n){if(t.length===0)throw new f("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new f("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await ka()})}else for(let s of t){let a=Sa(s);if(!a)throw new f(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Tt(s.name);if(a)throw new f(a)}let o=null;for(let s of r)o=await b("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)d(`${v(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 $a(e,t){let n=Tt(t);if(n)throw new f(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{d(`${t} is gone.`)})}async function Ea(e){let t=await b("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${L([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Oe(n.updatedAt)])])}
609
+ `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Mt}from"node:child_process";import{appendFileSync as za,existsSync as Tn,readFileSync as Va,statSync as Vu,unlinkSync as Yu,writeFileSync as mt}from"node:fs";import{dirname as Xu,join as Rn}from"node:path";import{tmpdir as Qu}from"node:os";import{open as Ya}from"node:fs/promises";function Pa(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 Aa(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 Ta(e){let t=e.split(`
610
+ `).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 Nr={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},du=/^(snoutpod_admin|snout_[a-z]+_admin)$/,vn=new Set(["auth","storage","push","realtime","snout_realtime","_realtime","graphql","graphql_public","extensions","net","cron","pgbouncer","vault","pgsodium"]),uu=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,mu={auth:"auth",storage:"storage",push:"push"},pu=new Set(["push.settings"]),fu=["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 gu(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=fu.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 hu(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 Ra(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
611
+ `)){let u=gu(i);if(!u)continue;let{type:c,schema:m,name:h,owner:x}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let W=/^(schema_migrations|migrations)$/.test(h)&&vn.has(m);if(/realtime/.test(m)||W||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(m==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let F=mu[m];if(F){F==="storage"&&h==="objects"&&o.add("Storage objects are restored as rows; the files themselves are not part of an export. Upload them again into this project."),n.set(F,[...n.get(F)??[],u]);continue}if(vn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?vn.has(h):vn.has(m))||uu.test(h)||du.test(x)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=hu(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(h=>h.type==="TABLE DATA"&&pu.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),m=u.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:c,tables:m,lines:yu(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function yu(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}X();de();z();X();oe();var Dr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],Na=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Ot(e){return e==="done"||e==="failed"||e==="cancelled"}function Nt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new S("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new $e(ku(e.project))}let n=e.admin??Oa(t.LEPIS_ADMIN_URL);if(!n)throw new S("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??Oa(t.LEPIS_ADMIN_TOKEN);if(!r)throw new S("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new jr(wu(n),r)}function Oa(e){return e===void 0||e===""?void 0:e}function wu(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new S("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new S("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new S("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!bu(n.hostname))throw new S("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 bu(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var _u=6e4,jr=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(_u)})}catch(i){let u=nn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new S(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 S("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw ja(s.status,l);return l}},Su={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function ja(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?Su[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":ze(e));return s==="not-signed-in"?new S(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new S(s,r,o)}function ku(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new S("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var vu={"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"},xu="cloud-project-shards",$e=class{target;ref;#t;constructor(t,n=b){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Be(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=vu[t.op];if(!n)throw new S("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 S("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 S("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(xu,{ref:this.ref,...t})}catch(n){throw $u(n)}}};function Be(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 $u(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return ja(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":ze(e.status);return new S(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Dt(e){if(Array.isArray(e))return`[${e.map(Dt).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)}:${Dt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Mr=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}
610
612
  ${Dt(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
611
613
  ${Dt(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
612
- ${Dt(t)}`)}},xu=new Mr,Lr=["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"],$u="2024-11-05";function B(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var L={type:"string"},Na={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:[...Lr]}},required:["op"],additionalProperties:!0},xn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},Be={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)."},Ct=[{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:B({})},{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:B({})},{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:B({name:L,region:L,teamId:L},["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:B({ref:L},["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:B({ref:L},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:B({ref:L},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:B({ref:L,dir:L,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:B({ref:L,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:B({ref:L},["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:B({ref:L},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:B({ref:L},["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:B({})},{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:B({})},{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:B({name:L,expires:L,project:L},["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:B({id:L},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/functions/v1/<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:B({ref:L,name:L,dir:L,entrypoint:L,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:B({ref:L},["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:B({ref:L,name:L,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:B({ref:L,name:L},["ref","name"])},{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:B({ref:L},["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:B({ref:L},["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:B({ref:L},["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:B({ref:L,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:B({ref:L},["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:B({ref:L,hostname:L},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:B({ref:L,hostname:L},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:B({ref:L,hostname:L},["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:B({ref:L},["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:B({ref:L,at:L,name:L},["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:B({project:Be})},{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:B({project:Be,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:B({project:Be,operation:Na},["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:B({project:Be,operation:Na,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:B({project:Be,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:B({project:Be,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:B({project:Be,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:B({project:Be,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:B({ref:L},["ref"])}],Eu=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Pu=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),ja=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Ur(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Ur)};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 Fr(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 Au(e){return{...e,annotations:Eu.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Pu.has(e.name)}}}function Tu(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function It(e,t){return{jsonrpc:"2.0",id:e,result:t}}function $n(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function k(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return It(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Ru(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function jt(e){return{op:e.op,cutovers:e.cutovers,moves:(e.moves??[]).map(t=>({from:t.source,to:t.targets,tables:t.tables,estimated_rows:t.estimated_rows,estimated_bytes:t.estimated_bytes,estimated_copy_seconds:t.estimated_copy_seconds,expected_pause_ms:t.expected_pause_ms})),steps:(e.steps??[]).map(t=>t.kind),estimated_rows:e.estimated_rows,estimated_bytes:e.estimated_bytes,estimated_copy_seconds:e.estimated_copy_seconds,expected_pause_ms:e.expected_pause_ms,max_write_pause_ms:e.max_write_pause_ms,warnings:e.warnings??[],...e.assumptions?{assumptions:e.assumptions}:{}}}function Ir(e){return{id:e.id,op:e.op,state:e.state,finished:Ot(e.state),error:e.error,steps:e.steps.map(t=>({n:t.n+1,kind:t.kind,state:t.state,...typeof t.detail?.phase=="string"?{phase:t.detail.phase}:{}})),...e.state==="failed"?{next:`cluster_resume_job {"id": ${e.id}} runs it again from the failed step.`}:{}}}function Da(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"||!Lr.includes(r.op))return`${t}: operation.op must be one of ${Lr.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 Cr(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 Ou(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var Nu="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 ju(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 k(e,Nu,!0);s=r.shards(a)}catch(u){return k(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??xu,i=a??"";try{switch(t){case"cluster_status":return k(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let m of c.advice)m.plan&&l.remember(m.request,i);return k(e,{summary:c.summary,advice:c.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:jt(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with cluster_plan.`}:{},...m.plan_error?{plan_error:m.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=Da(n,t);if(typeof u=="string")return k(e,u,!0);let c=await s.plan(u);return l.remember(u,i),k(e,{plan:jt(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=Da(n,t);if(typeof u=="string")return k(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let m=await s.plan(u);l.remember(u,i);let h=n.confirm===!0;return k(e,{ran:!1,plan:jt(m),next:h?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},h)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?k(e,{ran:!0,done:c.done,answer:c.answer}):k(e,{ran:!0,job:c.job,plan:jt(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return k(e,{jobs:await s.jobs()});let u=Cr(n);if(u===null)return k(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return k(e,{...Ir(c),plan:c.plan&&Array.isArray(c.plan.steps)?jt(c.plan):null})}case"cluster_watch_job":{let u=Cr(n);if(u===null)return k(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+c*1e3;for(;;){let h=await s.job(u);if(Ot(h.state))return k(e,Ir(h),h.state!=="done");if(Date.now()>=m)return k(e,{...Ir(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(v=>setTimeout(v,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=Cr(n);if(u===null)return k(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return k(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return $n(e,-32602,`unknown tool: ${t}`)}}catch(u){return k(e,Ou(u),!0)}}function X(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ia(e,t,n){let r=e.id??null,o=e.method??"",s=e.params??{};if(e.id===void 0||e.id===null)return null;switch(o){case"initialize":return It(r,{protocolVersion:$u,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return It(r,{});case"tools/list":return It(r,{tools:Tu([...Ct.map(Au),...n.borrowed??[]])});case"tools/call":return Du(r,s,t,n);default:return $n(r,-32601,`unknown method: ${o}`)}}async function Du(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Ct.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return $n(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_")&&Ct.some(c=>c.name===o))return ju(e,o,s,n,r);let i=X(s,"ref");if(l.includes(o)&&!i)return k(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!ja.includes(o))return k(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${ja.join(", ")}.`,!0);try{switch(o){case"whoami":return k(e,await n.whoami());case"list_projects":return k(e,Ur(await n.listProjects()));case"create_project":{let c=X(s,"name");return c?k(e,await n.createProject({name:c,region:X(s,"region")??void 0,teamId:X(s,"teamId")??void 0})):k(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return k(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 k(e,await n.setProjectState("pause",i));case"resume_project":return k(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return k(e,await n.usage(i,c))}case"export_status":return k(e,await n.exportStatus(i));case"start_export":return k(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),m=await n.connection(i);return k(e,{url:m.uri,appliesIn:c.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return k(e,await n.listTeams());case"get_project":return k(e,Ur(await n.getProject(i)));case"list_products":return k(e,await n.getProducts(i));case"set_product":{let c=X(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?k(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?k(e,"set_product needs enabled: true or false.",!0):k(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return k(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=X(s,"hostname");return c?k(e,await n.domainAction(i,o.split("_")[0],c)):k(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),m=c&&c.restore?Fr(i,c.restore):null;return k(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=X(s,"at");return c?k(e,await n.restoreTo(i,c,X(s,"name")??void 0)):k(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=X(s,"ref"),m=X(s,"name");return!c||!m?k(e,"deploy_function needs a project ref and a function name.",!0):k(e,await n.deployFunction(c,m,{dir:X(s,"dir")??void 0,entrypoint:X(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=X(s,"ref");return c?k(e,await n.listFunctions(c)):k(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=X(s,"ref"),m=X(s,"name");if(!c||!m)return k(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,v=typeof s.concurrency=="number"?s.concurrency:void 0,C=s.reset===!0;return!C&&h===void 0&&v===void 0?k(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):k(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:v,reset:C}))}case"delete_function":{let c=X(s,"ref"),m=X(s,"name");return!c||!m?k(e,"delete_function needs a project ref and a function name.",!0):k(e,await n.deleteFunction(c,m))}case"list_function_secrets":{let c=X(s,"ref");return c?k(e,await n.listFunctionSecrets(c)):k(e,"list_function_secrets needs a project ref.",!0)}case"list_tokens":return k(e,await n.listTokens());case"create_token":{let c=X(s,"name");if(!c)return k(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=X(s,"expires"),h=m?Ru(m):void 0;return h===null?k(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):k(e,await n.createToken(c,h,X(s,"project")??void 0))}case"revoke_token":{let c=X(s,"id");return c?k(e,await n.revokeToken(c)):k(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return k(e,await n.pushMigrations(i,{dir:X(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?k(e,await n.setProjectState("delete",i)):k(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(c=>c.name===o)?n.callBorrowed?It(e,await n.callBorrowed(o,s)):k(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):$n(e,-32602,`unknown tool: ${o}`)}}catch(c){return k(e,c instanceof Error?c.message:String(c),!0)}}function Ca(e){let t=e.split(`
613
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var En=["auth","storage","data-api","push"];function Qe(e){return{error:e instanceof Error?e.message:String(e)}}async function ut(e){let[t,n,r,o]=await Promise.all([b("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Qe),b("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Qe),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Qe),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Qe)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Iu}}var Iu={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Ma(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!En.includes(t))throw new f(`say which product: ${En.join(", ")}`);return t}async function Br(e,t,n){if(t==="auth"){let r=await b("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await b("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await b(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function La(e){let t=await ut(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?$("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${M([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"not on this plan":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":""),n("realtime",t.realtime,"always on: broadcast, presence; table changes on Plus and Pro")])}
614
- `);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 Ua(e,t,n){let r=await Br(e,t,n);g(r,()=>d(r.note))}async function Pn(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Hr(e,t,n){if(!n)throw new f(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await b("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Cu(e,t){let n=[["TYPE","NAME","VALUE"]];e.record&&n.push([e.record.type,e.record.name,e.record.value]),e.certificateRecord&&n.push([e.certificateRecord.type,e.certificateRecord.name,e.certificateRecord.value]),n.push(["CNAME",e.hostname,`${t}.api.snoutdata.com`]),process.stdout.write(`${M(n)}
615
- `)}async function Fa(e,t,n){if(t===void 0||t==="list"){let o=await Pn(e);g(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${M([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":$("no"),s.certificate?.state??""])])}
616
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new f(`unknown command: domains ${t}`);let r=await Hr(e,t,n??"");g(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Cu(r.domain,e)),r.next&&d(r.next)})}async function qr(e){return b("cloud-project-restore",{ref:e},"GET")}async function Gr(e,t,n){if(Number.isNaN(Date.parse(t)))throw new f(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Ba(e){let t=await qr(e);g(t,()=>{let n=t.restore,r=Fr(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 Ha(e,t,n){let r=await Gr(e,t,n);g(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 Jr(e){let{projects:t}=await he(),n=t.find(l=>l.ref===e);if(!n)throw new f(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([ut(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Qe),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Qe),Pn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Qe)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function qa(e){let t=await Jr(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${M([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (broadcast and presence; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
617
- `)})}Y();oe();Ve();Y();z();import{existsSync as Mu,mkdirSync as Lu,readFileSync as Uu,renameSync as Fu,writeFileSync as Bu}from"node:fs";import{homedir as Kr}from"node:os";import{dirname as Hu,join as qu}from"node:path";import{rootCertificates as Gu}from"node:tls";var Ju="SNOUTDATA_DB_SSLMODE";function Ku(e=Kr()){let t=qu(e,".snoutdata","ca-roots.pem"),n=`${Gu.join(`
614
+ ${Dt(t)}`)}},Eu=new Mr,Lr=["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"],Pu="2024-11-05";function B(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var C={type:"string"},Da={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:[...Lr]}},required:["op"],additionalProperties:!0},xn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},He={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)."},Ct=[{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:B({})},{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:B({})},{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:B({name:C,region:C,teamId:C},["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:B({ref:C},["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:B({ref:C},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:B({ref:C},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:B({ref:C,dir:C,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:B({ref:C,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:B({ref:C},["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:B({ref:C},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:B({ref:C},["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:B({})},{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:B({})},{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:B({name:C,expires:C,project:C},["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:B({id:C},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/functions/v1/<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:B({ref:C,name:C,dir:C,entrypoint:C,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:B({ref:C},["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:B({ref:C,name:C,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:B({ref:C,name:C},["ref","name"])},{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:B({ref:C},["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:B({ref:C,name:C,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:B({ref:C},["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:B({ref:C},["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:B({ref:C,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:B({ref:C},["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:B({ref:C,hostname:C},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:B({ref:C,hostname:C},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:B({ref:C,hostname:C},["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:B({ref:C},["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:B({ref:C,at:C,name:C},["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:B({project:He})},{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:B({project:He,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:B({project:He,operation:Da},["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:B({project:He,operation:Da,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:B({project:He,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:B({project:He,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:B({project:He,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:B({project:He,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:B({ref:C},["ref"])}],Au=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Tu=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Ia=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Ur(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Ur)};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 Fr(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 Ru(e){return{...e,annotations:Au.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Tu.has(e.name)}}}function Ou(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function It(e,t){return{jsonrpc:"2.0",id:e,result:t}}function $n(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function k(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return It(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Nu(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function jt(e){return{op:e.op,cutovers:e.cutovers,moves:(e.moves??[]).map(t=>({from:t.source,to:t.targets,tables:t.tables,estimated_rows:t.estimated_rows,estimated_bytes:t.estimated_bytes,estimated_copy_seconds:t.estimated_copy_seconds,expected_pause_ms:t.expected_pause_ms})),steps:(e.steps??[]).map(t=>t.kind),estimated_rows:e.estimated_rows,estimated_bytes:e.estimated_bytes,estimated_copy_seconds:e.estimated_copy_seconds,expected_pause_ms:e.expected_pause_ms,max_write_pause_ms:e.max_write_pause_ms,warnings:e.warnings??[],...e.assumptions?{assumptions:e.assumptions}:{}}}function Ir(e){return{id:e.id,op:e.op,state:e.state,finished:Ot(e.state),error:e.error,steps:e.steps.map(t=>({n:t.n+1,kind:t.kind,state:t.state,...typeof t.detail?.phase=="string"?{phase:t.detail.phase}:{}})),...e.state==="failed"?{next:`cluster_resume_job {"id": ${e.id}} runs it again from the failed step.`}:{}}}function Ca(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"||!Lr.includes(r.op))return`${t}: operation.op must be one of ${Lr.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 Cr(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 ju(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var Du="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 Iu(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 k(e,Du,!0);s=r.shards(a)}catch(u){return k(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??Eu,i=a??"";try{switch(t){case"cluster_status":return k(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let m of c.advice)m.plan&&l.remember(m.request,i);return k(e,{summary:c.summary,advice:c.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:jt(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with cluster_plan.`}:{},...m.plan_error?{plan_error:m.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=Ca(n,t);if(typeof u=="string")return k(e,u,!0);let c=await s.plan(u);return l.remember(u,i),k(e,{plan:jt(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=Ca(n,t);if(typeof u=="string")return k(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let m=await s.plan(u);l.remember(u,i);let h=n.confirm===!0;return k(e,{ran:!1,plan:jt(m),next:h?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},h)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?k(e,{ran:!0,done:c.done,answer:c.answer}):k(e,{ran:!0,job:c.job,plan:jt(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return k(e,{jobs:await s.jobs()});let u=Cr(n);if(u===null)return k(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return k(e,{...Ir(c),plan:c.plan&&Array.isArray(c.plan.steps)?jt(c.plan):null})}case"cluster_watch_job":{let u=Cr(n);if(u===null)return k(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+c*1e3;for(;;){let h=await s.job(u);if(Ot(h.state))return k(e,Ir(h),h.state!=="done");if(Date.now()>=m)return k(e,{...Ir(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(x=>setTimeout(x,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=Cr(n);if(u===null)return k(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return k(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return $n(e,-32602,`unknown tool: ${t}`)}}catch(u){return k(e,ju(u),!0)}}function V(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ma(e,t,n){let r=e.id??null,o=e.method??"",s=e.params??{};if(e.id===void 0||e.id===null)return null;switch(o){case"initialize":return It(r,{protocolVersion:Pu,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return It(r,{});case"tools/list":return It(r,{tools:Ou([...Ct.map(Ru),...n.borrowed??[]])});case"tools/call":return Cu(r,s,t,n);default:return $n(r,-32601,`unknown method: ${o}`)}}async function Cu(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Ct.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return $n(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_")&&Ct.some(c=>c.name===o))return Iu(e,o,s,n,r);let i=V(s,"ref");if(l.includes(o)&&!i)return k(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!Ia.includes(o))return k(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${Ia.join(", ")}.`,!0);try{switch(o){case"whoami":return k(e,await n.whoami());case"list_projects":return k(e,Ur(await n.listProjects()));case"create_project":{let c=V(s,"name");return c?k(e,await n.createProject({name:c,region:V(s,"region")??void 0,teamId:V(s,"teamId")??void 0})):k(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return k(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 k(e,await n.setProjectState("pause",i));case"resume_project":return k(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return k(e,await n.usage(i,c))}case"export_status":return k(e,await n.exportStatus(i));case"start_export":return k(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),m=await n.connection(i);return k(e,{url:m.uri,appliesIn:c.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return k(e,await n.listTeams());case"get_project":return k(e,Ur(await n.getProject(i)));case"list_products":return k(e,await n.getProducts(i));case"set_product":{let c=V(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?k(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?k(e,"set_product needs enabled: true or false.",!0):k(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return k(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=V(s,"hostname");return c?k(e,await n.domainAction(i,o.split("_")[0],c)):k(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),m=c&&c.restore?Fr(i,c.restore):null;return k(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=V(s,"at");return c?k(e,await n.restoreTo(i,c,V(s,"name")??void 0)):k(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=V(s,"ref"),m=V(s,"name");return!c||!m?k(e,"deploy_function needs a project ref and a function name.",!0):k(e,await n.deployFunction(c,m,{dir:V(s,"dir")??void 0,entrypoint:V(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=V(s,"ref");return c?k(e,await n.listFunctions(c)):k(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=V(s,"ref"),m=V(s,"name");if(!c||!m)return k(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,x=typeof s.concurrency=="number"?s.concurrency:void 0,M=s.reset===!0;return!M&&h===void 0&&x===void 0?k(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):k(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:x,reset:M}))}case"delete_function":{let c=V(s,"ref"),m=V(s,"name");return!c||!m?k(e,"delete_function needs a project ref and a function name.",!0):k(e,await n.deleteFunction(c,m))}case"list_function_secrets":{let c=V(s,"ref");return c?k(e,await n.listFunctionSecrets(c)):k(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=V(s,"ref");if(!c)return k(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return k(e,await n.functionLogs(c,{name:V(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return k(e,await n.listTokens());case"create_token":{let c=V(s,"name");if(!c)return k(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=V(s,"expires"),h=m?Nu(m):void 0;return h===null?k(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):k(e,await n.createToken(c,h,V(s,"project")??void 0))}case"revoke_token":{let c=V(s,"id");return c?k(e,await n.revokeToken(c)):k(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return k(e,await n.pushMigrations(i,{dir:V(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?k(e,await n.setProjectState("delete",i)):k(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(c=>c.name===o)?n.callBorrowed?It(e,await n.callBorrowed(o,s)):k(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):$n(e,-32602,`unknown tool: ${o}`)}}catch(c){return k(e,c instanceof Error?c.message:String(c),!0)}}function La(e){let t=e.split(`
615
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var En=["auth","storage","data-api","push"];function Qe(e){return{error:e instanceof Error?e.message:String(e)}}async function ut(e){let[t,n,r,o]=await Promise.all([b("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Qe),b("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Qe),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Qe),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Qe)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Mu}}var Mu={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Ua(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!En.includes(t))throw new f(`say which product: ${En.join(", ")}`);return t}async function Br(e,t,n){if(t==="auth"){let r=await b("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await b("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await b(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function Fa(e){let t=await ut(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?v("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: broadcast, presence; table changes on Plus and Pro")])}
616
+ `);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 Ba(e,t,n){let r=await Br(e,t,n);g(r,()=>d(r.note))}async function Pn(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Hr(e,t,n){if(!n)throw new f(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await b("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Lu(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)}
617
+ `)}async function Ha(e,t,n){if(t===void 0||t==="list"){let o=await Pn(e);g(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${L([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":v("no"),s.certificate?.state??""])])}
618
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new f(`unknown command: domains ${t}`);let r=await Hr(e,t,n??"");g(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Lu(r.domain,e)),r.next&&d(r.next)})}async function qr(e){return b("cloud-project-restore",{ref:e},"GET")}async function Gr(e,t,n){if(Number.isNaN(Date.parse(t)))throw new f(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function qa(e){let t=await qr(e);g(t,()=>{let n=t.restore,r=Fr(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 Ga(e,t,n){let r=await Gr(e,t,n);g(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${v(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function Jr(e){let{projects:t}=await he(),n=t.find(l=>l.ref===e);if(!n)throw new f(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([ut(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Qe),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Qe),Pn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Qe)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ja(e){let t=await Jr(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${L([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (broadcast and presence; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
619
+ `)})}X();oe();Ve();X();z();import{existsSync as Uu,mkdirSync as Fu,readFileSync as Bu,renameSync as Hu,writeFileSync as qu}from"node:fs";import{homedir as Kr}from"node:os";import{dirname as Gu,join as Ju}from"node:path";import{rootCertificates as Ku}from"node:tls";var Wu="SNOUTDATA_DB_SSLMODE";function zu(e=Kr()){let t=Ju(e,".snoutdata","ca-roots.pem"),n=`${Ku.join(`
618
620
  `)}
619
- `,r=null;try{r=Mu(t)?Uu(t,"utf8"):null}catch{r=null}if(r!==n){Lu(Hu(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Bu(o,n,{mode:420}),Fu(o,t)}return t}function Wr(e,t=process.env,n=Kr()){if(e==="disable")return{mode:"disable"};let r=t[Ju]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Ku(n)}}function Ze(e,t=process.env,n=Kr()){let{mode:r,rootCert:o}=Wr(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Va(e){try{let{projects:t}=await he(),n=t.find(r=>r.ref===e);n?.readOnly?V(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&V(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Vr(e){await Va(e);let t=await me(e);g(t,()=>{process.stdout.write(`${t.uri}
620
- `)})}async function Ya(e,t){if(t.length===0&&!se()){let o=it();D("usage",`db psql with no statements opens an interactive session, and ${o?at(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Va(e);let n=await me(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Mt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Ze(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new S("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new S("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Xa(e,t,n="DATABASE_URL"){let r=Rn(e,".env"),o=`${n}=${t}`,s=Xu(e);if(!Tn(r))return mt(r,`${o}
621
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Wu(r).mode&63&&V(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Wa(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(mt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Ka(r,a.endsWith(`
621
+ `,r=null;try{r=Uu(t)?Bu(t,"utf8"):null}catch{r=null}if(r!==n){Fu(Gu(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;qu(o,n,{mode:420}),Hu(o,t)}return t}function Wr(e,t=process.env,n=Kr()){if(e==="disable")return{mode:"disable"};let r=t[Wu]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||zu(n)}}function Ze(e,t=process.env,n=Kr()){let{mode:r,rootCert:o}=Wr(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Xa(e){try{let{projects:t}=await he(),n=t.find(r=>r.ref===e);n?.readOnly?Y(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&Y(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Vr(e){await Xa(e);let t=await me(e);g(t,()=>{process.stdout.write(`${t.uri}
622
+ `)})}async function Qa(e,t){if(t.length===0&&!se()){let o=it();I("usage",`db psql with no statements opens an interactive session, and ${o?at(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Xa(e);let n=await me(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Mt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Ze(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new S("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new S("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Za(e,t,n="DATABASE_URL"){let r=Rn(e,".env"),o=`${n}=${t}`,s=Zu(e);if(!Tn(r))return mt(r,`${o}
623
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Vu(r).mode&63&&Y(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Va(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(mt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(za(r,a.endsWith(`
622
624
  `)?`${o}
623
625
  `:`
624
626
  ${o}
625
- `),{path:r,replaced:!1,ignored:s})}function Xu(e){let t=e;for(;!Tn(Rn(t,".git"));){let s=Vu(t);if(s===t)return!1;t=s}let n=Rn(e,".gitignore"),r=Tn(n)?Wa(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Ka(n,r===""||r.endsWith(`
627
+ `),{path:r,replaced:!1,ignored:s})}function Zu(e){let t=e;for(;!Tn(Rn(t,".git"));){let s=Xu(t);if(s===t)return!1;t=s}let n=Rn(e,".gitignore"),r=Tn(n)?Va(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(za(n,r===""||r.endsWith(`
626
628
  `)?`.env
627
629
  `:`
628
630
  .env
629
- `),!0)}async function Qa(e,t={}){if(t.statusOnly){let{export:a}=await lt(e);return g({ref:e,export:a},()=>zr(a)),0}let n=await Et(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>zr(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Zu(e,t.timeoutMs);if(r.error&&D("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>zr(r)),0;r.url||D("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await em(r.url,t.out),s=r.rolesSql?Qu(t.out):null;return s&&r.rolesSql&&mt(s,r.rolesSql.endsWith(`
631
+ `),!0)}async function ei(e,t={}){if(t.statusOnly){let{export:a}=await lt(e);return g({ref:e,export:a},()=>zr(a)),0}let n=await Et(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>zr(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await tm(e,t.timeoutMs);if(r.error&&I("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>zr(r)),0;r.url||I("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await nm(r.url,t.out),s=r.rolesSql?em(t.out):null;return s&&r.rolesSql&&mt(s,r.rolesSql.endsWith(`
630
632
  `)?r.rolesSql:`${r.rolesSql}
631
- `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${je(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 Qu(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Zu(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await lt(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function em(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 za(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 zr(e){if(e.error){V(`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 ${Oe(e.completedAt)}, ${je(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
632
- `),e.urlExpiresAt&&d(w(`That link stops working ${Oe(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function je(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 Za(e,t){let n=t.file;n||D("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Tn(n)||D("usage",`no file at ${n}`,{file:n});let r=await za(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=$a(o.slice(0,s.bytesRead)),l=await me(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await he(),u=i.find(W=>W.ref===e),c=await tm(l);c===null&&D("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let m=Ea(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});m.ok||D("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let W=await Ja("pg_restore",["--list",n]),F=await Ja("pg_restore",["--section=post-data","--file=-",n]);if((W.code!==0||F.code!==0)&&D("failed",`pg_restore could not read ${n}: ${(W.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=Aa(W.stdout,F.stdout),h.products.length>0){let pe=await ut(e),Pe=p=>!!(p&&typeof p=="object"&&"enabled"in p&&p.enabled),ce=h.products.filter(p=>!Pe(pe[p]));ce.length>0&&D("conflict",`this dump holds ${ce.length>1?`${ce.slice(0,-1).join(", ")} and ${ce[ce.length-1]}`:ce[0]} data, and ${e} has ${ce.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ce.length===1?"it":"them"} on, wait a minute, and run this again:
633
+ `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${je(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 em(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function tm(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await lt(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function nm(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 Ya(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 zr(e){if(e.error){Y(`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 ${Oe(e.completedAt)}, ${je(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
634
+ `),e.urlExpiresAt&&d(w(`That link stops working ${Oe(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function je(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 ti(e,t){let n=t.file;n||I("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Tn(n)||I("usage",`no file at ${n}`,{file:n});let r=await Ya(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=Pa(o.slice(0,s.bytesRead)),l=await me(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await he(),u=i.find(W=>W.ref===e),c=await rm(l);c===null&&I("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let m=Aa(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});m.ok||I("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let W=await Wa("pg_restore",["--list",n]),F=await Wa("pg_restore",["--section=post-data","--file=-",n]);if((W.code!==0||F.code!==0)&&I("failed",`pg_restore could not read ${n}: ${(W.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=Ra(W.stdout,F.stdout),h.products.length>0){let pe=await ut(e),Pe=p=>!!(p&&typeof p=="object"&&"enabled"in p&&p.enabled),ce=h.products.filter(p=>!Pe(pe[p]));ce.length>0&&I("conflict",`this dump holds ${ce.length>1?`${ce.slice(0,-1).join(", ")} and ${ce[ce.length-1]}`:ce[0]} data, and ${e} has ${ce.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ce.length===1?"it":"them"} on, wait a minute, and run this again:
633
635
  ${ce.map(p=>` snoutdata products enable ${p} --ref ${e}`).join(`
634
- `)}`,{ref:e,file:n,refusal:"products-off",products:ce})}}if(h&&h.platformData.length>0){let W=h.platformData.flatMap(({tables:Pe})=>Pe),F=await nm(l,W);F===null&&D("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let pe=h.platformData.filter(({tables:Pe})=>Pe.some(ce=>F.includes(ce))).map(({product:Pe})=>Pe);pe.length>0&&D("conflict",`${pe.join(", ")} on ${e} ${pe.length===1?"has":"have"} not finished setting up ${pe.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:pe})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let v=h?await rm(l,n,h):await An(l,m.tool,n),C=Pa(v.stderr);for(let W of h?.notes??[])d(w(W));return v.code!==0&&C.fatal.length===0&&C.total>0?(g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:C.harmless},()=>{d(`Restored into ${e}.`),d(`${C.harmless} error${C.harmless===1?"":"s"} above ${C.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):(v.code!==0&&D("failed",`${m.tool} exited ${v.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:C.fatal}),g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function tm(e){return new Promise(t=>{let n=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e') and pg_get_userbyid(c.relowner) !~ '^(snoutpod_admin|snout_[a-z]+_admin)$'"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{V(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 nm(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc",`select t from unnest(array[${n}]) t where to_regclass(t) is null`],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(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(`
635
- `).map(l=>l.trim()).filter(Boolean):null))})}function Ja(e,t){return new Promise(n=>{let r=Mt(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{V(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 rm(e,t,n){let r=Rn(Yu(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){mt(`${r}.sql`,`set role ${Nr[l]};
636
+ `)}`,{ref:e,file:n,refusal:"products-off",products:ce})}}if(h&&h.platformData.length>0){let W=h.platformData.flatMap(({tables:Pe})=>Pe),F=await om(l,W);F===null&&I("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let pe=h.platformData.filter(({tables:Pe})=>Pe.some(ce=>F.includes(ce))).map(({product:Pe})=>Pe);pe.length>0&&I("conflict",`${pe.join(", ")} on ${e} ${pe.length===1?"has":"have"} not finished setting up ${pe.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:pe})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let x=h?await sm(l,n,h):await An(l,m.tool,n),M=Ta(x.stderr);for(let W of h?.notes??[])d(w(W));return x.code!==0&&M.fatal.length===0&&M.total>0?(g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:M.harmless},()=>{d(`Restored into ${e}.`),d(`${M.harmless} error${M.harmless===1?"":"s"} above ${M.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(x.code!==0&&I("failed",`${m.tool} exited ${x.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:M.fatal}),g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function rm(e){return new Promise(t=>{let n=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e') and pg_get_userbyid(c.relowner) !~ '^(snoutpod_admin|snout_[a-z]+_admin)$'"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{Y(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 om(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Mt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc",`select t from unnest(array[${n}]) t where to_regclass(t) is null`],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(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(`
637
+ `).map(l=>l.trim()).filter(Boolean):null))})}function Wa(e,t){return new Promise(n=>{let r=Mt(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{Y(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 sm(e,t,n){let r=Rn(Qu(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){mt(`${r}.sql`,`set role ${Nr[l]};
636
638
  ${i.map(h=>`delete from ${h};`).join(`
637
639
  `)}
638
640
  `,"utf8");let m=await An(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}mt(`${r}.platform`,`${u.join(`
639
641
  `)}
640
642
  `,"utf8");let c=await An(e,"pg_restore",t,["--data-only",`--role=${Nr[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}mt(`${r}.rest`,`${n.rest.join(`
641
643
  `)}
642
- `,"utf8");let a=await An(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{zu(`${r}${a}`)}catch{}}}function An(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=Mt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{V(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),l.on("close",u=>a({code:u??0,stderr:i}))})}Y();de();z();function sm(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new f(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new f("db access list takes no argument");if(t!==void 0)throw new f("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new f("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new f(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new f("db access revoke needs an email address or a role name");if(t!==void 0)throw new f("--level is for db access grant");return{action:"revoke",who:r};default:throw new f(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function ei(e,t,n){let r=sm(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>am(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>im(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>lm(o))}function am(e){if(e.note&&d(e.note),e.access.length===0){d(`Nobody signs in to ${e.ref}'s database as themselves yet. \`snoutdata db access grant you@example.com --level full\`.`);return}process.stdout.write(`${M([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?$(n.email):n.email,n.level,n.role])])}
643
- `);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 im(e){e.connection&&process.stdout.write(`${e.connection.keywords}
644
- `),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 cm(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function lm(e){let t=cm(e);for(let n of t.slice(0,-1))d(n);V(t[t.length-1])}Y();de();z();var dm=6e4;async function Yr(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function um(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new f(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function mm(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function ti(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 pm={connect:"connected",connect_refused:"refused at connect",join:"joined",join_refused:"join refused",leave:"left",channel_closed:"channel closed by the server",disconnect:"disconnected"};function ni(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[mm(e.at),pm[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function fm(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function gm(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} broadcast${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} presence update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,ti(Date.now()-a.joined_at),ti(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>dm?" (silent: likely gone)":"")]);d(M([["SOCKET","PRESENCE KEY","JOINED","LAST HEARD"],...s]).split(`
644
+ `,"utf8");let a=await An(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{Yu(`${r}${a}`)}catch{}}}function An(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=Mt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{Y(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}))})}X();de();z();function im(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new f(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new f("db access list takes no argument");if(t!==void 0)throw new f("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new f("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new f(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new f("db access revoke needs an email address or a role name");if(t!==void 0)throw new f("--level is for db access grant");return{action:"revoke",who:r};default:throw new f(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function ni(e,t,n){let r=im(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>cm(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>lm(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>um(o))}function cm(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?v(n.email):n.email,n.level,n.role])])}
645
+ `);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 lm(e){e.connection&&process.stdout.write(`${e.connection.keywords}
646
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${v(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 dm(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function um(e){let t=dm(e);for(let n of t.slice(0,-1))d(n);Y(t[t.length-1])}X();de();z();var mm=6e4;async function Yr(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function pm(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new f(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function fm(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function ri(e){if(e===null)return"-";let t=Math.round(e/1e3);if(t<60)return`${t}s ago`;let n=Math.round(t/60);return n<60?`${n}m ago`:`${Math.round(n/60)}h ago`}var gm={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 oi(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[fm(e.at),gm[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function hm(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function ym(e){let t=e.inspect;if(d(v(`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(v(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(w(` last ${r.window_seconds}s: ${r.broadcasts_received} broadcast${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} presence update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,ri(Date.now()-a.joined_at),ri(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>mm?" (silent: likely gone)":"")]);d(L([["SOCKET","PRESENCE KEY","JOINED","LAST HEARD"],...s]).split(`
645
647
  `).map(a=>` ${a}`).join(`
646
- `))}let o=Object.keys(n.presence);if(o.length>0){d(" presence:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${fm(a)}`)}}}async function ri(e,t={}){if(t.watch&&xs())throw new f("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await Yr(e,{channel:t.channel});if(g(n.inspect,()=>gm(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 Yr(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${ni(a)}
648
+ `))}let o=Object.keys(n.presence);if(o.length>0){d(" presence:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${hm(a)}`)}}}async function si(e,t={}){if(t.watch&&xs())throw new f("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await Yr(e,{channel:t.channel});if(g(n.inspect,()=>ym(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 Yr(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${oi(a)}
647
649
  `);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"}
648
- `))}}}async function oi(e,t={}){let n=t.since?um(t.since):0,r=await Yr(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${ni(o)}
649
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}Y();de();z();import{readFile as ym}from"node:fs/promises";var wm="api.snoutdata.com";function ii(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}.${wm}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function bm(e){return ii(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function On(e,t){return ii(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Xr(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(`${M([["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"]])}
650
- `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function _m(){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 ci(e,t,n,r){if(t===void 0||t==="show"){let o=await bm(e);g(o,()=>Xr(o));return}if(t==="google"){if(n[0]==="off"){let a=await On(e,{googleEnabled:!1});g(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new f('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await _m();if(!a)throw new f("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await On(e,o);g(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Xr(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new f('auth anonymous takes "on" or "off"');let s=await On(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: signInAnonymously() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new f("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await On(e,o);g(s,()=>Xr(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>ai(o.templates));return}if(t==="template"){let o=n[0];if(!o||!si.includes(o))throw new f(`auth template needs a kind: ${si.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new f(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await ym(r.file,"utf8"),i=await b("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await b("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{ai(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new f(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var si=["confirmation","recovery","magic_link","invite","email_change"];function ai(e){process.stdout.write(`${M([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?$("yours"):"ours",t.subject])])}
651
- `)}Y();z();oe();import{spawn as km}from"node:child_process";import{createHash as vm}from"node:crypto";import{existsSync as xm,readdirSync as $m,readFileSync as Em,statSync as Pm}from"node:fs";import{isAbsolute as Am,join as Qr,relative as Tm,resolve as Rm}from"node:path";function Sm(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Nn(e,t,n={}){let r=Sm(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){s.push({code:"missing",name:u.name,reason:`${u.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}c.checksum!==u.checksum&&s.push({code:"changed",name:u.name,reason:`${u.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(u=>u.name).sort((u,c)=>u<c?-1:u>c?1:0)[t.length-1],l=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!l.has(u.name)){if(a!==void 0&&u.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function Lt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function di(e){let t=Tm(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Ut="_snoutdata_migrations",Om=`create table if not exists ${Ut} (
650
+ `))}}}async function ai(e,t={}){let n=t.since?pm(t.since):0,r=await Yr(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${oi(o)}
651
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}X();de();z();import{readFile as bm}from"node:fs/promises";var _m="api.snoutdata.com";function li(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}.${_m}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function Sm(e){return li(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function On(e,t){return li(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Xr(e){let t=e.google,n="off";t.ready?n=v("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?v("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?v("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
652
+ `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function km(){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 di(e,t,n,r){if(t===void 0||t==="show"){let o=await Sm(e);g(o,()=>Xr(o));return}if(t==="google"){if(n[0]==="off"){let a=await On(e,{googleEnabled:!1});g(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new f('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await km();if(!a)throw new f("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await On(e,o);g(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Xr(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new f('auth anonymous takes "on" or "off"');let s=await On(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?d("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?d("Guest sign-in is on: signInAnonymously() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new f("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await On(e,o);g(s,()=>Xr(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>ci(o.templates));return}if(t==="template"){let o=n[0];if(!o||!ii.includes(o))throw new f(`auth template needs a kind: ${ii.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new f(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await bm(r.file,"utf8"),i=await b("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await b("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{ci(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new f(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var ii=["confirmation","recovery","magic_link","invite","email_change"];function ci(e){process.stdout.write(`${L([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?v("yours"):"ours",t.subject])])}
653
+ `)}X();z();oe();import{spawn as xm}from"node:child_process";import{createHash as $m}from"node:crypto";import{existsSync as Em,readdirSync as Pm,readFileSync as Am,statSync as Tm}from"node:fs";import{isAbsolute as Rm,join as Qr,relative as Om,resolve as Nm}from"node:path";function vm(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Nn(e,t,n={}){let r=vm(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){s.push({code:"missing",name:u.name,reason:`${u.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}c.checksum!==u.checksum&&s.push({code:"changed",name:u.name,reason:`${u.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(u=>u.name).sort((u,c)=>u<c?-1:u>c?1:0)[t.length-1],l=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!l.has(u.name)){if(a!==void 0&&u.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function Lt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function mi(e){let t=Om(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Ut="_snoutdata_migrations",jm=`create table if not exists ${Ut} (
652
654
  name text primary key,
653
655
  checksum text not null,
654
656
  applied_at timestamptz not null default now()
655
- );`;function Nm(e){if(!xm(e)||!Pm(e).isDirectory())throw new Error(`no migrations folder at ${di(e)}. Put your .sql files there, or pass --dir.`);return $m(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Qr(e,t),r=Em(n,"utf8");return{name:t,path:n,sql:r,checksum:vm("sha256").update(r,"utf8").digest("hex")}})}function Zr(e,t,n=[]){return new Promise(r=>{let o=km("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function jm(e){let t=await Zr(e,Om);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Ut}`);let n=await Zr(e,`select name || E'\\t' || checksum from ${Ut};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Ut}`);return n.out.split(`
656
- `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function li(e){return`'${e.replace(/'/g,"''")}'`}async function ui(e,t={}){let n=await eo(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
657
- `);D("conflict",`Refused. This folder and ${e} do not agree:
658
- ${r}`,{ref:e,problems:n.problems})}D("failed",`${n.failed} failed, and nothing after it was tried.
659
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function eo(e,t={},n=()=>{}){let r=Rm(t.dir===void 0?Qr(process.cwd(),"migrations"):Am(t.dir)?t.dir:Qr(process.cwd(),t.dir)),o=[];function s(v){o.push(v),n(v)}let a=Nm(r);if(a.length===0)return s(`No .sql files in ${di(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await me(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await jm(l),u=Nn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let c=new Map(a.map(v=>[v.name,v])),m=u.pending.map(v=>c.get(v.name));if(m.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let v of m)Lt(v.sql)||s(`${v.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(v=>v.name),notes:o}}let h=[];for(let v of m){let C=`insert into ${Ut} (name, checksum) values (${li(v.name)}, ${li(v.checksum)});`,W=Lt(v.sql);W||s(`${v.name} runs outside a transaction, so an interruption can leave it half applied.`);let F=W?`begin;
660
- ${v.sql}
661
- ${C}
657
+ );`;function Dm(e){if(!Em(e)||!Tm(e).isDirectory())throw new Error(`no migrations folder at ${mi(e)}. Put your .sql files there, or pass --dir.`);return Pm(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Qr(e,t),r=Am(n,"utf8");return{name:t,path:n,sql:r,checksum:$m("sha256").update(r,"utf8").digest("hex")}})}function Zr(e,t,n=[]){return new Promise(r=>{let o=xm("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ze(e.ssl)}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function Im(e){let t=await Zr(e,jm);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Ut}`);let n=await Zr(e,`select name || E'\\t' || checksum from ${Ut};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Ut}`);return n.out.split(`
658
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function ui(e){return`'${e.replace(/'/g,"''")}'`}async function pi(e,t={}){let n=await eo(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
659
+ `);I("conflict",`Refused. This folder and ${e} do not agree:
660
+ ${r}`,{ref:e,problems:n.problems})}I("failed",`${n.failed} failed, and nothing after it was tried.
661
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function eo(e,t={},n=()=>{}){let r=Nm(t.dir===void 0?Qr(process.cwd(),"migrations"):Rm(t.dir)?t.dir:Qr(process.cwd(),t.dir)),o=[];function s(x){o.push(x),n(x)}let a=Dm(r);if(a.length===0)return s(`No .sql files in ${mi(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await me(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await Im(l),u=Nn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let c=new Map(a.map(x=>[x.name,x])),m=u.pending.map(x=>c.get(x.name));if(m.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let x of m)Lt(x.sql)||s(`${x.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(x=>x.name),notes:o}}let h=[];for(let x of m){let M=`insert into ${Ut} (name, checksum) values (${ui(x.name)}, ${ui(x.checksum)});`,W=Lt(x.sql);W||s(`${x.name} runs outside a transaction, so an interruption can leave it half applied.`);let F=W?`begin;
662
+ ${x.sql}
663
+ ${M}
662
664
  commit;
663
- `:`${v.sql}
664
- ${C}
665
- `,pe=await Zr(l,F);if(pe.code!==0)return W&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:v.name,error:pe.err.trim(),notes:o};h.push(v.name),s(` applied ${v.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}Y();de();z();import{readFile as mi}from"node:fs/promises";function to(e){if(e==="apns"||e==="fcm")return e;throw new f("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Dm(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function pi(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new f("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new f("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await mi(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function fi(e){if(!e)throw new f("push credentials set fcm needs --file service-account.json");return{service_account:await mi(e,"utf8")}}async function Im(e,t,n){let r=await b("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Cm(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function gi(e){let t=await Dm(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${M([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
666
- `),(!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 hi(e,t,n){let r=await Im(e,t,n);g(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function yi(e,t){let n=await Cm(e,t);g(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}Y();oe();z();import{spawn as Ym}from"node:child_process";import{writeFileSync as Xm}from"node:fs";import{isAbsolute as Qm,resolve as Zm}from"node:path";var Lm=" ";function R(e){return Lm.repeat(e)}function ae(e){return JSON.stringify(e)}function Ee(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:ae(e)}function et(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Um(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 Fm={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"},Bm={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Hm(e,t){let n=Bm[e]?.[t];return n||(Fm[t]??"unknown")}function jn(e,t){let{base:n,array:r}=Um(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Hm(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function qm(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function no(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${R(n)}${Ee(t.name)}?: never`;let s=jn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${R(n)}${Ee(t.name)}${r?"?":""}: ${a}`}function Gm(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 Jm(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(`${R(t)}Relationships: []`),n;n.push(`${R(t)}Relationships: [`);for(let o of r)n.push(`${R(t+1)}{`),n.push(`${R(t+2)}foreignKeyName: ${ae(o.constraintName)}`),n.push(`${R(t+2)}columns: [${o.columns.map(ae).join(", ")}]`),n.push(`${R(t+2)}isOneToOne: ${Gm(e,o)?"true":"false"}`),n.push(`${R(t+2)}referencedRelation: ${ae(o.referencedTable)}`),n.push(`${R(t+2)}referencedColumns: [${o.referencedColumns.map(ae).join(", ")}]`),n.push(`${R(t+1)}},`);return n.push(`${R(t)}]`),n}function wi(e,t,n){let r=et(t.columns),o=[];o.push(`${R(n)}${Ee(t.name)}: {`),o.push(`${R(n+1)}Row: {`);for(let s of r)o.push(no(e,s,n+2,!1));if(o.push(`${R(n+1)}}`),t.kind!=="view"){o.push(`${R(n+1)}Insert: {`);for(let s of r)o.push(no(e,s,n+2,qm(s),!0));o.push(`${R(n+1)}}`),o.push(`${R(n+1)}Update: {`);for(let s of r)o.push(no(e,s,n+2,!0,!0));o.push(`${R(n+1)}}`)}return o.push(...Jm(t,n+1)),o.push(`${R(n)}}`),o}function Ft(e,t){return[`${R(t)}${e}: {`,`${R(t+1)}[_ in never]: never`,`${R(t)}}`]}function Km(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}[]`:`${jn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??jn(e,n)}function Wm(e,t,n){let r=[],o=et((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${R(n)}${Ee(t.name)}: {`),o.length===0)r.push(`${R(n+1)}Args: never`);else{r.push(`${R(n+1)}Args: {`);for(let s of o)r.push(`${R(n+2)}${Ee(s.name)}: ${jn(e,s.dataType)}`);r.push(`${R(n+1)}}`)}return r.push(`${R(n+1)}Returns: ${Km(e,t)}`),r.push(`${R(n)}}`),r}function pt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function bi(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let h=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${ae(h)}][${ae("Enums")}][${ae(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=pt(m,n),v=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${ae(h)}][${ae(v)}][${ae(m.name)}][${ae("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(pt(m,n));for(let m of e.routines??[])i.add(pt(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let m of u){let h=et(e.tables.filter(F=>pt(F,n)===m&&F.kind!=="view")),v=et(e.tables.filter(F=>pt(F,n)===m&&F.kind==="view")),C=et((e.routines??[]).filter(F=>pt(F,n)===m)),W=et(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(c.push(`${R(1)}${Ee(m)}: {`),h.length===0)c.push(...Ft("Tables",2));else{c.push(`${R(2)}Tables: {`);for(let F of h)c.push(...wi(l,F,3));c.push(`${R(2)}}`)}if(v.length===0)c.push(...Ft("Views",2));else{c.push(`${R(2)}Views: {`);for(let F of v)c.push(...wi(l,F,3));c.push(`${R(2)}}`)}if(C.length===0)c.push(...Ft("Functions",2));else{c.push(`${R(2)}Functions: {`);for(let F of C)c.push(...Wm(l,F,3));c.push(`${R(2)}}`)}if(W.length===0)c.push(...Ft("Enums",2));else{c.push(`${R(2)}Enums: {`);for(let F of W)c.push(`${R(3)}${Ee(F.name)}: ${F.values.map(ae).join(" | ")}`);c.push(`${R(2)}}`)}c.push(...Ft("CompositeTypes",2)),c.push(`${R(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Vm(n))),t.constants!==!1&&(c.push(""),c.push(zm(u,o,n))),`${c.join(`
665
+ `:`${x.sql}
666
+ ${M}
667
+ `,pe=await Zr(l,F);if(pe.code!==0)return W&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:x.name,error:pe.err.trim(),notes:o};h.push(x.name),s(` applied ${x.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}X();de();z();import{readFile as fi}from"node:fs/promises";function to(e){if(e==="apns"||e==="fcm")return e;throw new f("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Cm(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function gi(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new f("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new f("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await fi(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function hi(e){if(!e)throw new f("push credentials set fcm needs --file service-account.json");return{service_account:await fi(e,"utf8")}}async function Mm(e,t,n){let r=await b("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Lm(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function yi(e){let t=await Cm(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${L([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
668
+ `),(!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 wi(e,t,n){let r=await Mm(e,t,n);g(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function bi(e,t){let n=await Lm(e,t);g(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}X();oe();z();import{spawn as Qm}from"node:child_process";import{writeFileSync as Zm}from"node:fs";import{isAbsolute as ep,resolve as tp}from"node:path";var Fm=" ";function R(e){return Fm.repeat(e)}function ae(e){return JSON.stringify(e)}function Ee(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:ae(e)}function et(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Bm(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 Hm={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"},qm={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Gm(e,t){let n=qm[e]?.[t];return n||(Hm[t]??"unknown")}function jn(e,t){let{base:n,array:r}=Bm(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Gm(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Jm(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function no(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${R(n)}${Ee(t.name)}?: never`;let s=jn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${R(n)}${Ee(t.name)}${r?"?":""}: ${a}`}function Km(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 Wm(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(`${R(t)}Relationships: []`),n;n.push(`${R(t)}Relationships: [`);for(let o of r)n.push(`${R(t+1)}{`),n.push(`${R(t+2)}foreignKeyName: ${ae(o.constraintName)}`),n.push(`${R(t+2)}columns: [${o.columns.map(ae).join(", ")}]`),n.push(`${R(t+2)}isOneToOne: ${Km(e,o)?"true":"false"}`),n.push(`${R(t+2)}referencedRelation: ${ae(o.referencedTable)}`),n.push(`${R(t+2)}referencedColumns: [${o.referencedColumns.map(ae).join(", ")}]`),n.push(`${R(t+1)}},`);return n.push(`${R(t)}]`),n}function _i(e,t,n){let r=et(t.columns),o=[];o.push(`${R(n)}${Ee(t.name)}: {`),o.push(`${R(n+1)}Row: {`);for(let s of r)o.push(no(e,s,n+2,!1));if(o.push(`${R(n+1)}}`),t.kind!=="view"){o.push(`${R(n+1)}Insert: {`);for(let s of r)o.push(no(e,s,n+2,Jm(s),!0));o.push(`${R(n+1)}}`),o.push(`${R(n+1)}Update: {`);for(let s of r)o.push(no(e,s,n+2,!0,!0));o.push(`${R(n+1)}}`)}return o.push(...Wm(t,n+1)),o.push(`${R(n)}}`),o}function Ft(e,t){return[`${R(t)}${e}: {`,`${R(t+1)}[_ in never]: never`,`${R(t)}}`]}function zm(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}[]`:`${jn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??jn(e,n)}function Vm(e,t,n){let r=[],o=et((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${R(n)}${Ee(t.name)}: {`),o.length===0)r.push(`${R(n+1)}Args: never`);else{r.push(`${R(n+1)}Args: {`);for(let s of o)r.push(`${R(n+2)}${Ee(s.name)}: ${jn(e,s.dataType)}`);r.push(`${R(n+1)}}`)}return r.push(`${R(n+1)}Returns: ${zm(e,t)}`),r.push(`${R(n)}}`),r}function pt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Si(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let h=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${ae(h)}][${ae("Enums")}][${ae(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=pt(m,n),x=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${ae(h)}][${ae(x)}][${ae(m.name)}][${ae("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(pt(m,n));for(let m of e.routines??[])i.add(pt(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let m of u){let h=et(e.tables.filter(F=>pt(F,n)===m&&F.kind!=="view")),x=et(e.tables.filter(F=>pt(F,n)===m&&F.kind==="view")),M=et((e.routines??[]).filter(F=>pt(F,n)===m)),W=et(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(c.push(`${R(1)}${Ee(m)}: {`),h.length===0)c.push(...Ft("Tables",2));else{c.push(`${R(2)}Tables: {`);for(let F of h)c.push(..._i(l,F,3));c.push(`${R(2)}}`)}if(x.length===0)c.push(...Ft("Views",2));else{c.push(`${R(2)}Views: {`);for(let F of x)c.push(..._i(l,F,3));c.push(`${R(2)}}`)}if(M.length===0)c.push(...Ft("Functions",2));else{c.push(`${R(2)}Functions: {`);for(let F of M)c.push(...Vm(l,F,3));c.push(`${R(2)}}`)}if(W.length===0)c.push(...Ft("Enums",2));else{c.push(`${R(2)}Enums: {`);for(let F of W)c.push(`${R(3)}${Ee(F.name)}: ${F.values.map(ae).join(" | ")}`);c.push(`${R(2)}}`)}c.push(...Ft("CompositeTypes",2)),c.push(`${R(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Xm(n))),t.constants!==!1&&(c.push(""),c.push(Ym(u,o,n))),`${c.join(`
667
669
  `)}
668
- `}function zm(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=et(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${R(1)}${Ee(o)}: {`),r.push(`${R(2)}Enums: {},`),r.push(`${R(1)}},`);continue}r.push(`${R(1)}${Ee(o)}: {`),r.push(`${R(2)}Enums: {`);for(let a of s)r.push(`${R(3)}${Ee(a.name)}: [${a.values.map(ae).join(", ")}],`);r.push(`${R(2)}},`),r.push(`${R(1)}},`)}return r.push("} as const"),r.join(`
669
- `)}function Vm(e){return`type DatabaseWithoutInternals = Database
670
+ `}function Ym(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=et(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${R(1)}${Ee(o)}: {`),r.push(`${R(2)}Enums: {},`),r.push(`${R(1)}},`);continue}r.push(`${R(1)}${Ee(o)}: {`),r.push(`${R(2)}Enums: {`);for(let a of s)r.push(`${R(3)}${Ee(a.name)}: [${a.values.map(ae).join(", ")}],`);r.push(`${R(2)}},`),r.push(`${R(1)}},`)}return r.push("} as const"),r.join(`
671
+ `)}function Xm(e){return`type DatabaseWithoutInternals = Database
670
672
 
671
673
  type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${ae(e)}>]
672
674
 
@@ -781,7 +783,7 @@ export type CompositeTypes<
781
783
  ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
782
784
  : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
783
785
  ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
784
- : never`}function ep(e){let t=Wr(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 Dn(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 ro(e,t){return new Promise(n=>{let r=Ym("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 tp=`
786
+ : never`}function np(e){let t=Wr(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 Dn(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 ro(e,t){return new Promise(n=>{let r=Qm("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 rp=`
785
787
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
786
788
  select json_build_object(
787
789
  'tables', coalesce((
@@ -885,40 +887,40 @@ select json_build_object(
885
887
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
886
888
  ), '[]'::json)
887
889
  )::text
888
- `;function np(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function rp(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(m=>typeof m=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var op=`select exists (
890
+ `;function op(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function sp(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(m=>typeof m=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}var ap=`select exists (
889
891
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
890
892
  where n.nspname = 'lepis' and c.relname = 'relation'
891
- )::text`,sp=`select coalesce(json_agg(json_build_object(
893
+ )::text`,ip=`select coalesce(json_agg(json_build_object(
892
894
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
893
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function ap(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:ip(t)}function ip(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 cp(e,t){let n=await e(op);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(sp);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 lp(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Dn(e.dbUrl):ep(await me(dp(e))),r=!1,o=async C=>{if(e.sql)return e.sql(C);if(r&&e.ref)return _i(e.ref,C);let W=await ro(n,C);return W.code===127&&!e.dbUrl&&e.ref?(r=!0,_i(e.ref,C)):W},s=tp.replace("$SCHEMAS$",np(t)),a=await o(s);a.code===127&&D("tool-missing",a.err.trim()),a.code!==0&&D("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||D("failed",`${n.label} returned nothing for the schema query`);let i=ap(l);i||D("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await cp(o,t),{schema:c,enums:m}=rp(i,n.database,Date.now()),h=bi(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),v=null;return e.out&&(v=Qm(e.out)?e.out:Zm(process.cwd(),e.out),Xm(v,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(C=>C.kind!=="view").length,views:c.tables.filter(C=>C.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:v,distribution:u,types:h}}async function _i(e,t,n=b){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function dp(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 Si(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&D("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await lp(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}oe();import{createHash as Lf}from"node:crypto";import{existsSync as Io,mkdirSync as Uf,readFileSync as Co,readdirSync as Ff,statSync as Bf,writeFileSync as Hf}from"node:fs";import{dirname as qf,isAbsolute as ul,join as Vt,resolve as ml}from"node:path";import{pathToFileURL as Gf}from"node:url";z();function Jf(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:ul(e)||e.startsWith("./")||e.startsWith("../")?Gf(ml(e)).href:e}function Kf(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(Jf(e)):Promise.resolve().then(()=>(il(),al))}function Wf(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 cl="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 ll(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Yt(){let e;try{e=await Kf()}catch(t){D("tool-missing",Wf(t))}return{ready:async t=>{let n=await ll(e,t);return typeof n=="string"?await e.podmanReady(cl)??n:e.podmanReady(cl,n.name)},ensureImage:async(t,n)=>{let r=await ll(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 pl(e){return Vt(e,".snoutdata","local.json")}function zn(e){let t=pl(e);if(!Io(t))return null;try{let n=JSON.parse(Co(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 zf(e,t){let n=pl(e);return Uf(qf(n),{recursive:!0}),Hf(n,`${JSON.stringify(t,null,2)}
894
- `,{encoding:"utf8",mode:384}),n}var Vf=54322;async function fl(e,t){let n=await e.ensureImage(t,o=>d(o));n&&D("tool-missing",n);let r=await e.ready(t);r&&D("tool-missing",r),await e.prepare()}function gl(e,t,n,r=ro){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 Yf(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);await fl(e,r?.ref??null);let o=r??e.mint(t.port??Vf),s=r?null:zf(n,o),a=await e.start(o,m=>d(m)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=gl(e,o,Dn(l)),u=await Xf(i,n,t),c=await Qf(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function Xf(e,t,n){let r=ml(n.dir===void 0?Vt(t,"migrations"):ul(n.dir)?n.dir:Vt(t,n.dir));if(!Io(r)||!Bf(r).isDirectory())return[];let o=Ff(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Co(Vt(r,m),"utf8");return{name:m,sql:h,checksum:Lf("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Zf);s.code!==0&&D(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${zt}`);let a=await e(`select name || E'\\t' || checksum from ${zt};`);a.code!==0&&D("failed",a.err.trim()||`could not read ${zt}`);let l=a.out.split(`
895
- `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let h=m.indexOf(" ");return{name:m.slice(0,h),checksum:m.slice(h+1)}}),i=Nn(o,l,{outOfOrder:n.outOfOrder});i.ok||D("conflict",`Refused. This folder and the local database do not agree:
895
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function cp(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:lp(t)}function lp(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 dp(e,t){let n=await e(ap);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(ip);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 up(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Dn(e.dbUrl):np(await me(mp(e))),r=!1,o=async M=>{if(e.sql)return e.sql(M);if(r&&e.ref)return ki(e.ref,M);let W=await ro(n,M);return W.code===127&&!e.dbUrl&&e.ref?(r=!0,ki(e.ref,M)):W},s=rp.replace("$SCHEMAS$",op(t)),a=await o(s);a.code===127&&I("tool-missing",a.err.trim()),a.code!==0&&I("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||I("failed",`${n.label} returned nothing for the schema query`);let i=cp(l);i||I("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await dp(o,t),{schema:c,enums:m}=sp(i,n.database,Date.now()),h=Si(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),x=null;return e.out&&(x=ep(e.out)?e.out:tp(process.cwd(),e.out),Zm(x,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(M=>M.kind!=="view").length,views:c.tables.filter(M=>M.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:x,distribution:u,types:h}}async function ki(e,t,n=b){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function mp(e){if(!e.ref)throw new Error("no project: pass --ref, --db-url, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return e.ref}async function vi(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&I("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await up(t);return r.path&&d(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}oe();import{createHash as Uf}from"node:crypto";import{existsSync as Io,mkdirSync as Ff,readFileSync as Co,readdirSync as Bf,statSync as Hf,writeFileSync as qf}from"node:fs";import{dirname as Gf,isAbsolute as pl,join as Vt,resolve as fl}from"node:path";import{pathToFileURL as Jf}from"node:url";z();function Kf(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:pl(e)||e.startsWith("./")||e.startsWith("../")?Jf(fl(e)).href:e}function Wf(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(Kf(e)):Promise.resolve().then(()=>(ll(),cl))}function zf(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 dl="It is fetched from ghcr.io/snoutdata/snoutpod-postgres on the first `snoutdata start`, so this usually means that registry could not be reached from here.";async function ul(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Yt(){let e;try{e=await Wf()}catch(t){I("tool-missing",zf(t))}return{ready:async t=>{let n=await ul(e,t);return typeof n=="string"?await e.podmanReady(dl)??n:e.podmanReady(dl,n.name)},ensureImage:async(t,n)=>{let r=await ul(e,t);return typeof r=="string"?r:e.ensureImage(n,void 0,r)},prepare:()=>e.ensureNetwork(),start:(t,n)=>e.startLocal(e.localRuntime(),t,{onProgress:n}),stop:t=>e.stopLocal(e.localRuntime(),t),status:t=>e.localStatus(e.localRuntime(),t),remove:(t,n)=>e.removeLocal(e.localRuntime(),t,n),uri:t=>e.localUri(t),psql:async(t,n)=>{let r=await e.localPsql(t,n);return{code:r.code,out:r.stdout,err:r.stderr}},mint:t=>({ref:e.newLocalRef(),port:t,ownerPassword:e.newLocalPassword(),adminPassword:e.newLocalPassword()})}}function gl(e){return Vt(e,".snoutdata","local.json")}function zn(e){let t=gl(e);if(!Io(t))return null;try{let n=JSON.parse(Co(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 Vf(e,t){let n=gl(e);return Ff(Gf(n),{recursive:!0}),qf(n,`${JSON.stringify(t,null,2)}
896
+ `,{encoding:"utf8",mode:384}),n}var Yf=54322;async function hl(e,t){let n=await e.ensureImage(t,o=>d(o));n&&I("tool-missing",n);let r=await e.ready(t);r&&I("tool-missing",r),await e.prepare()}function yl(e,t,n,r=ro){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 Xf(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);await hl(e,r?.ref??null);let o=r??e.mint(t.port??Yf),s=r?null:Vf(n,o),a=await e.start(o,m=>d(m)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=yl(e,o,Dn(l)),u=await Qf(i,n,t),c=await Zf(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function Qf(e,t,n){let r=fl(n.dir===void 0?Vt(t,"migrations"):pl(n.dir)?n.dir:Vt(t,n.dir));if(!Io(r)||!Hf(r).isDirectory())return[];let o=Bf(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=Co(Vt(r,m),"utf8");return{name:m,sql:h,checksum:Uf("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(eg);s.code!==0&&I(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${zt}`);let a=await e(`select name || E'\\t' || checksum from ${zt};`);a.code!==0&&I("failed",a.err.trim()||`could not read ${zt}`);let l=a.out.split(`
897
+ `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let h=m.indexOf(" ");return{name:m.slice(0,h),checksum:m.slice(h+1)}}),i=Nn(o,l,{outOfOrder:n.outOfOrder});i.ok||I("conflict",`Refused. This folder and the local database do not agree:
896
898
  ${i.problems.map(m=>` ${m.reason}`).join(`
897
- `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),c=[];for(let m of i.pending){let h=u.get(m.name),v=`insert into ${zt} (name, checksum) values (${dl(h.name)}, ${dl(h.checksum)});`,C=Lt(h.sql);C||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let W=C?`begin;
899
+ `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),c=[];for(let m of i.pending){let h=u.get(m.name),x=`insert into ${zt} (name, checksum) values (${ml(h.name)}, ${ml(h.checksum)});`,M=Lt(h.sql);M||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let W=M?`begin;
898
900
  ${h.sql}
899
- ${v}
901
+ ${x}
900
902
  commit;
901
903
  `:`${h.sql}
902
- ${v}
903
- `,F=await e(W);F.code!==0&&D("failed",`${h.name} failed, and nothing after it was tried.
904
- ${F.err.trim()}`,{failed:h.name,applied:c}),c.push(h.name),d(` applied ${h.name}`)}return c}async function Qf(e,t,n){let r=Vt(t,"seed.sql");if(!n||!Io(r))return!1;let o=await e(Co(r,"utf8"));return o.code!==0&&D("failed",`seed.sql failed.
905
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var zt="_snoutdata_migrations",Zf=`create table if not exists ${zt} (
904
+ ${x}
905
+ `,F=await e(W);F.code!==0&&I("failed",`${h.name} failed, and nothing after it was tried.
906
+ ${F.err.trim()}`,{failed:h.name,applied:c}),c.push(h.name),d(` applied ${h.name}`)}return c}async function Zf(e,t,n){let r=Vt(t,"seed.sql");if(!n||!Io(r))return!1;let o=await e(Co(r,"utf8"));return o.code!==0&&I("failed",`seed.sql failed.
907
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var zt="_snoutdata_migrations",eg=`create table if not exists ${zt} (
906
908
  name text primary key,
907
909
  checksum text not null,
908
910
  applied_at timestamptz not null default now()
909
- );`;function dl(e){return`'${e.replace(/'/g,"''")}'`}function eg(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 hl(e,t={}){let n=await Yf(e,t);return g(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}
910
- `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function yl(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);r||D("not-found","there is no local database for this folder. `snoutdata start` makes one."),await fl(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function wl(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{d(` ${$("Project")} ${s.ref}`),d(` ${$("State")} ${s.ready?"running":s.status}`),d(` ${$("Port")} ${s.port}`),d(` ${$("Size")} ${eg(s.databaseBytes)}`),s.services.length>0&&d(` ${$("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
911
- `)}),0}async function bl(e,t=process.cwd()){let n=zn(t);n||D("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:gl(e,n,Dn(r))}}Y();z();an();St();Ie();import{resolve as _l}from"node:path";function tg(){let e=n=>Ne(n),t;return{shards:n=>{if(n!==void 0)return Nt({project:n});if(!process.env.LEPIS_ADMIN_URL)throw new Error("The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref, from list_projects), or start snoutdata mcp with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set.");return t??=Nt({}),t},whoami:()=>Ye(),listProjects:async()=>({...await he(),local:ng()}),localProject:n=>e(n),createProject:async n=>{let r=await b("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await At(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(n,r)=>b("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>lt(n),requestExport:n=>Et(n),resetPassword:n=>b("cloud-project-reset-password",{ref:n}),listTeams:()=>b("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?kr(s,r,o):Ar(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?_n(r):Tr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?vr(o,r):Rr(n,r)},sizeFunction:(n,r,o)=>Or(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?kn(r):b("cloud-project-secrets",{ref:n})},listTokens:()=>b("cloud-token-list",{}),createToken:(n,r,o)=>b("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>b("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:_n(r).functions.map(o=>o.name),secretNames:kn(r).secrets.map(o=>o.name)}:Jr(n)},getProducts:n=>ut(n),setProduct:(n,r,o)=>Br(n,r,o),listDomains:n=>Pn(n),domainAction:(n,r,o)=>Hr(n,r,o),restoreWindow:n=>qr(n),restoreTo:(n,r,o)=>Gr(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await Pt(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>me(n),pushMigrations:(n,r)=>eo(n,{dir:r.dir,dryRun:r.dryRun})}}function ng(){let e=en().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=rt(process.cwd())?.local;if(t&&!e.some(n=>_l(n.folder)===_l(t)))try{let n=Me(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Sl="app_";async function rg(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await on();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await ys(t)).map(o=>({name:`${Sl}${o.name}`,description:`[SnoutData Studio, on this machine] ${o.description??o.name}`,inputSchema:o.inputSchema??{type:"object",properties:{}}})),config:t}}catch{return{tools:[],config:null}}}async function kl(e){$t(!0);let{tools:t,config:n}=await rg(),r=tg();n&&(r.callBorrowed=(i,u)=>ws(n,i.slice(Sl.length),u));let o={...e,borrowed:t};V(t.length>0?`snoutdata mcp: ${Ct.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await Ye();V(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(V(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return V("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;V(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}V(`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=Ca(s+u);s=c.rest;for(let m of c.messages)l(og(m,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function og(e,t,n){let r;try{r=JSON.parse(e)}catch{V(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ia(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
911
+ );`;function ml(e){return`'${e.replace(/'/g,"''")}'`}function tg(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 wl(e,t={}){let n=await Xf(e,t);return g(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${v("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${v("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${v("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
912
+ `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function bl(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);r||I("not-found","there is no local database for this folder. `snoutdata start` makes one."),await hl(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function _l(e,t={}){let n=t.cwd??process.cwd(),r=zn(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{d(` ${v("Project")} ${s.ref}`),d(` ${v("State")} ${s.ready?"running":s.status}`),d(` ${v("Port")} ${s.port}`),d(` ${v("Size")} ${tg(s.databaseBytes)}`),s.services.length>0&&d(` ${v("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
913
+ `)}),0}async function Sl(e,t=process.cwd()){let n=zn(t);n||I("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:yl(e,n,Dn(r))}}X();z();an();St();Ce();import{resolve as kl}from"node:path";function ng(){let e=n=>Ne(n),t;return{shards:n=>{if(n!==void 0)return Nt({project:n});if(!process.env.LEPIS_ADMIN_URL)throw new Error("The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref, from list_projects), or start snoutdata mcp with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set.");return t??=Nt({}),t},whoami:()=>Ye(),listProjects:async()=>({...await he(),local:rg()}),localProject:n=>e(n),createProject:async n=>{let r=await b("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await At(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(n,r)=>b("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>lt(n),requestExport:n=>Et(n),resetPassword:n=>b("cloud-project-reset-password",{ref:n}),listTeams:()=>b("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?kr(s,r,o):Ar(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?_n(r):Tr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?vr(o,r):Rr(n,r)},sizeFunction:(n,r,o)=>Or(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?kn(r):b("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>b("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listTokens:()=>b("cloud-token-list",{}),createToken:(n,r,o)=>b("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>b("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:_n(r).functions.map(o=>o.name),secretNames:kn(r).secrets.map(o=>o.name)}:Jr(n)},getProducts:n=>ut(n),setProduct:(n,r,o)=>Br(n,r,o),listDomains:n=>Pn(n),domainAction:(n,r,o)=>Hr(n,r,o),restoreWindow:n=>qr(n),restoreTo:(n,r,o)=>Gr(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await Pt(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>me(n),pushMigrations:(n,r)=>eo(n,{dir:r.dir,dryRun:r.dryRun})}}function rg(){let e=en().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=rt(process.cwd())?.local;if(t&&!e.some(n=>kl(n.folder)===kl(t)))try{let n=Le(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var vl="app_";async function og(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await on();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await ys(t)).map(o=>({name:`${vl}${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 xl(e){$t(!0);let{tools:t,config:n}=await og(),r=ng();n&&(r.callBorrowed=(i,u)=>ws(n,i.slice(vl.length),u));let o={...e,borrowed:t};Y(t.length>0?`snoutdata mcp: ${Ct.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await Ye();Y(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(Y(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return Y("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;Y(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}Y(`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=La(s+u);s=c.rest;for(let m of c.messages)l(sg(m,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function sg(e,t,n){let r;try{r=JSON.parse(e)}catch{Y(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ma(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
912
914
  `)}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)}})}
913
- `)}}St();Y();z();function sg(e){return e>=1?"over":e>=.8?"near":"ok"}function vl(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:sg(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Mo(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 xl(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=vl(n.days,n.limit);return g({...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=`${je(r.storage.usedBytes)} of ${je(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(`${je(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"}: ${Mo(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 ${je(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(M([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":je(o.dbBytes),o.repoBytes===null?"-":je(o.repoBytes),Mo(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}de();oe();Ve();z();import{createInterface as ag}from"node:readline/promises";var ig=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),Lo={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"},cg="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function $l(e,t,n=!1){let[r,o,...s]=e,a=c=>x(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new f(m);return c},i,u=`${r??""}${o!==void 0&&lg(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new f("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. It joins the cluster on its own once it is ready.");return{body:I(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new f("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new f("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Je(t,"nodes","nodes");if(!c.nodes)throw new f("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(Lo))Object.assign(i,Je(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...Je(t,"port","port"),...Ge(t,"dbname","dbname"),...Ge(t,"sslmode","sslmode"),...Ge(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:Xn(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...Je(t,"ranges","ranges"),...a("seed")!==void 0?{seed:dg(a("seed"))}:{},...a("nodes")?{nodes:Xn(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new f("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=Vn(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:Uo(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=Vn(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new f("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=Vn(s[0],t),m=Vn(s[1],t);if(c.keyspace!==m.keyspace)throw new f(`range merge: ${c.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:m.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(m,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Ge(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?Xn(a("add")).map(h=>ug(h,t)):[],m=Je(t,"remove","remove");if(c.length===0&&!m.remove)throw new f("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...m};break}case"verify":i={op:"verify",...Ge(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Ge(t,"node","node")};break;default:throw new f(`unknown operation: cluster ${e.join(" ")}. One of ${cg}`)}for(let[c,m]of Object.entries(Lo)){let h=Je(t,m,c);Object.assign(i,h)}return{body:i,destructive:ig.has(i.op)}}function lg(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function Xn(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Ge(e,t,n){let r=x(e,t);return r===void 0?{}:{[n]:r}}function Je(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 f(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Uo(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new f(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function dg(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new f(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Vn(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 f(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Uo(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new f("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Uo(o,"--range")}}function ug(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new f(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Ge(t,"dbname","dbname"),...Ge(t,"sslmode","sslmode")}}function Yn(e,t){if(e===void 0||!/^\d+$/.test(e))throw new f(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Al(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??Nt({project:a,admin:x(e,"admin"),token:x(e,"token")});switch(r){case"enable":{if(!l)throw new f("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof $e))throw new f("cluster enable needs --project <ref>");if(I(e,"plan")){let h=await c.enable(!0);return g(h,()=>we(h,process.stdout)),0}if(!I(e,"yes")){if(!t.confirm&&!se())throw new f("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.enable(!0),process.stderr),!await(t.confirm??bt)(`Make a cluster of ${c.target}?`))throw new S("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return g(m,()=>d(Fe(m))),0}case"disable":{if(!l)throw new f("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof $e))throw new f("cluster disable needs --project <ref>");if(I(e,"plan")){let h=await c.disable(!0);return g(h,()=>we(h,process.stdout)),0}if(!I(e,"yes")){if(!t.confirm&&!se())throw new f("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.disable(!0),process.stderr),!await(t.confirm??bt)(`Turn the cluster of ${c.target} off?`))throw new S("failed","Not turned off. Nothing was changed.")}let m=await c.disable(!1);return g(m,()=>d(Fe(m))),0}case"spread":{if(!l)throw new f("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof $e))throw new f("cluster spread needs --project <ref>");let m={table:o??wt(x(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:wt(x(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:wt(x(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(I(e,"plan")){let v=await c.spread(m,!0);return g(v,()=>we(v,process.stdout)),0}if(!I(e,"yes")){if(!t.confirm&&!se())throw new f("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.spread(m,!0),process.stderr),!await(t.confirm??bt)(`Spread ${m.table} over ${c.target}?`))throw new S("failed","Not spread. Nothing was changed.")}let h=await c.spread(m,!1);return g(h,()=>d(Fe(h))),0}case"retry":{if(!l)throw new f("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof $e))throw new f("cluster retry needs --project <ref>");let m=I(e,"queue")?await c.retry({queue:!0}):await c.retry({node:wt(o??x(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return g(m,()=>d(Fe(m))),0}case"replica":{if(!l)throw new f("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new f("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof $e))throw new f("cluster replica needs --project <ref>");let m=o==="remove",h=wt(s??x(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(I(e,"plan")){let C=await c.replica(h,m,!0);return g(C,()=>we(C,process.stdout)),0}if(!I(e,"yes")){if(!t.confirm&&!se())throw new f(m?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.replica(h,m,!0),process.stderr),!await(t.confirm??bt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new S("failed","Nothing was changed.")}let v=await c.replica(h,m,!1);return g(v,()=>d(Fe(v))),0}case"alerts":{if(!l)throw new f("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof $e))throw new f("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await c.alerts();return g({alerts:m},()=>Rl(m,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let m=wt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new f(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await c.acknowledge(m.toLowerCase());return g(h,()=>d(Fe(h))),0}throw new f(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return g(c,()=>wg(c)),0}case"ops":{let c=await i().ops();return g(c,()=>{process.stdout.write(`${c.ops.join(`
915
+ `)}}St();X();z();function ag(e){return e>=1?"over":e>=.8?"near":"ok"}function $l(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:ag(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Mo(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 El(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=$l(n.days,n.limit);return g({...n,summary:r},()=>{if(d(v(`${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=`${je(r.storage.usedBytes)} of ${je(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(`${je(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"}: ${Mo(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 ${je(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?"-":je(o.dbBytes),o.repoBytes===null?"-":je(o.repoBytes),Mo(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}de();oe();Ve();z();import{createInterface as ig}from"node:readline/promises";var cg=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),Lo={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"},lg="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Pl(e,t,n=!1){let[r,o,...s]=e,a=c=>$(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new f(m);return c},i,u=`${r??""}${o!==void 0&&dg(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new f("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. It joins the cluster on its own once it is ready.");return{body:D(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new f("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new f("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Ke(t,"nodes","nodes");if(!c.nodes)throw new f("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(Lo))Object.assign(i,Ke(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...Ke(t,"port","port"),...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode"),...Je(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:Xn(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, ...)"),...Ke(t,"ranges","ranges"),...a("seed")!==void 0?{seed:ug(a("seed"))}:{},...a("nodes")?{nodes:Xn(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new f("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=Vn(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:Uo(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=Vn(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new f("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=Vn(s[0],t),m=Vn(s[1],t);if(c.keyspace!==m.keyspace)throw new f(`range merge: ${c.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:m.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(m,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Je(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?Xn(a("add")).map(h=>mg(h,t)):[],m=Ke(t,"remove","remove");if(c.length===0&&!m.remove)throw new f("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...m};break}case"verify":i={op:"verify",...Je(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Je(t,"node","node")};break;default:throw new f(`unknown operation: cluster ${e.join(" ")}. One of ${lg}`)}for(let[c,m]of Object.entries(Lo)){let h=Ke(t,m,c);Object.assign(i,h)}return{body:i,destructive:cg.has(i.op)}}function dg(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function Xn(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Je(e,t,n){let r=$(e,t);return r===void 0?{}:{[n]:r}}function Ke(e,t,n){let r=$(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new f(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function Uo(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new f(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function ug(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new f(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Vn(e,t){let n=$(t,"keyspace"),r=$(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new f(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:Uo(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new f("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:Uo(o,"--range")}}function mg(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new f(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode")}}function Yn(e,t){if(e===void 0||!/^\d+$/.test(e))throw new f(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function Rl(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=$(e,"project")??$(e,"ref"),l=a!==void 0,i=()=>t.client??Nt({project:a,admin:$(e,"admin"),token:$(e,"token")});switch(r){case"enable":{if(!l)throw new f("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof $e))throw new f("cluster enable needs --project <ref>");if(D(e,"plan")){let h=await c.enable(!0);return g(h,()=>we(h,process.stdout)),0}if(!D(e,"yes")){if(!t.confirm&&!se())throw new f("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.enable(!0),process.stderr),!await(t.confirm??bt)(`Make a cluster of ${c.target}?`))throw new S("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return g(m,()=>d(Be(m))),0}case"disable":{if(!l)throw new f("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof $e))throw new f("cluster disable needs --project <ref>");if(D(e,"plan")){let h=await c.disable(!0);return g(h,()=>we(h,process.stdout)),0}if(!D(e,"yes")){if(!t.confirm&&!se())throw new f("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.disable(!0),process.stderr),!await(t.confirm??bt)(`Turn the cluster of ${c.target} off?`))throw new S("failed","Not turned off. Nothing was changed.")}let m=await c.disable(!1);return g(m,()=>d(Be(m))),0}case"spread":{if(!l)throw new f("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof $e))throw new f("cluster spread needs --project <ref>");let m={table:o??wt($(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:wt($(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:wt($(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(D(e,"plan")){let x=await c.spread(m,!0);return g(x,()=>we(x,process.stdout)),0}if(!D(e,"yes")){if(!t.confirm&&!se())throw new f("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.spread(m,!0),process.stderr),!await(t.confirm??bt)(`Spread ${m.table} over ${c.target}?`))throw new S("failed","Not spread. Nothing was changed.")}let h=await c.spread(m,!1);return g(h,()=>d(Be(h))),0}case"retry":{if(!l)throw new f("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof $e))throw new f("cluster retry needs --project <ref>");let m=D(e,"queue")?await c.retry({queue:!0}):await c.retry({node:wt(o??$(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return g(m,()=>d(Be(m))),0}case"replica":{if(!l)throw new f("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new f("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof $e))throw new f("cluster replica needs --project <ref>");let m=o==="remove",h=wt(s??$(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(D(e,"plan")){let M=await c.replica(h,m,!0);return g(M,()=>we(M,process.stdout)),0}if(!D(e,"yes")){if(!t.confirm&&!se())throw new f(m?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.replica(h,m,!0),process.stderr),!await(t.confirm??bt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new S("failed","Nothing was changed.")}let x=await c.replica(h,m,!1);return g(x,()=>d(Be(x))),0}case"alerts":{if(!l)throw new f("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof $e))throw new f("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await c.alerts();return g({alerts:m},()=>Nl(m,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let m=wt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new f(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await c.acknowledge(m.toLowerCase());return g(h,()=>d(Be(h))),0}throw new f(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return g(c,()=>bg(c)),0}case"ops":{let c=await i().ops();return g(c,()=>{process.stdout.write(`${c.ops.join(`
914
916
  `)}
915
- `),d(w(`Default settings: ${Object.entries(c.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return fg(e,i);case"advise":case"advice":{let c=Je(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return g(m,()=>yg(m,process.stdout)),0}case"jobs":return gg(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return g({nodes:c.nodes??[]},()=>Tl(c)),0}break;case"plan":{let c=$l(n.slice(1),e,l),m=await i().plan(c.body);return g(m,()=>we(m,process.stdout)),0}}let u=$l(n,e,l);if(I(e,"plan")){let c=await i().plan(u.body);return g(c,()=>we(c,process.stdout)),0}return mg(u,e,i,t)}async function mg(e,t,n,r){let o=I(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!se())throw new f(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(we(u,process.stderr),!await(r.confirm??bt)(`Run ${s} on ${a.target}?`))throw new S("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return g(l.answer,()=>d(l.done)),0;if(I(t,"no-wait"))return g({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
916
- `),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 Fo(a,l.job,r);return Bo(i)}function wt(e,t){if(e===void 0||e==="")throw new f(t);return e}async function bt(e){let t=ag({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 pg(e){let t=[...Dr,...Ra],n={};for(let r of Xn(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new f(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new f(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function fg(e,t){let n={};for(let s of Dr)Object.assign(n,Je(e,Lo[s],s));let r=x(e,"set");if(r!==void 0&&Object.assign(n,pg(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return g({settings:a},()=>process.stdout.write(`${M(Object.entries(a).map(([l,i])=>[l,String(i)]))}
917
- `)),0}let o=await t().settings(n);return g(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function gg(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return g({jobs:s},()=>xg(s)),0}case"show":{let s=await r.job(Yn(t,"show"));return g(s,()=>$g(s)),0}case"watch":{let s=await Fo(r,Yn(t,"watch"),o);return Bo(s)}case"cancel":{let s=Yn(t,"cancel");if(!I(n,"yes")){if(!o.confirm&&!se())throw new f(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??bt)(`Cancel job ${s} on ${r.target}?`))throw new S("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return g(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Yn(t,"resume"),a=await r.resume(s);return I(n,"no-wait")?(g(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):Bo(await Fo(r,s,o))}default:throw new f(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Fo(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(w(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Ot(a.state))return a;if(Date.now()>=o)throw new S("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 Bo(e){if(e.state==="done")return g(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
917
+ `),d(w(`Default settings: ${Object.entries(c.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return gg(e,i);case"advise":case"advice":{let c=Ke(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return g(m,()=>wg(m,process.stdout)),0}case"jobs":return hg(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return g({nodes:c.nodes??[]},()=>Ol(c)),0}break;case"plan":{let c=Pl(n.slice(1),e,l),m=await i().plan(c.body);return g(m,()=>we(m,process.stdout)),0}}let u=Pl(n,e,l);if(D(e,"plan")){let c=await i().plan(u.body);return g(c,()=>we(c,process.stdout)),0}return pg(u,e,i,t)}async function pg(e,t,n,r){let o=D(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!se())throw new f(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(we(u,process.stderr),!await(r.confirm??bt)(`Run ${s} on ${a.target}?`))throw new S("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return g(l.answer,()=>d(l.done)),0;if(D(t,"no-wait"))return g({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
918
+ `),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 Fo(a,l.job,r);return Bo(i)}function wt(e,t){if(e===void 0||e==="")throw new f(t);return e}async function bt(e){let t=ig({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 fg(e){let t=[...Dr,...Na],n={};for(let r of Xn(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new f(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new f(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function gg(e,t){let n={};for(let s of Dr)Object.assign(n,Ke(e,Lo[s],s));let r=$(e,"set");if(r!==void 0&&Object.assign(n,fg(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return g({settings:a},()=>process.stdout.write(`${L(Object.entries(a).map(([l,i])=>[l,String(i)]))}
919
+ `)),0}let o=await t().settings(n);return g(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function hg(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return g({jobs:s},()=>$g(s)),0}case"show":{let s=await r.job(Yn(t,"show"));return g(s,()=>Eg(s)),0}case"watch":{let s=await Fo(r,Yn(t,"watch"),o);return Bo(s)}case"cancel":{let s=Yn(t,"cancel");if(!D(n,"yes")){if(!o.confirm&&!se())throw new f(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??bt)(`Cancel job ${s} on ${r.target}?`))throw new S("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return g(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Yn(t,"resume"),a=await r.resume(s);return D(n,"no-wait")?(g(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):Bo(await Fo(r,s,o))}default:throw new f(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Fo(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(w(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Ot(a.state))return a;if(Date.now()>=o)throw new S("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 Bo(e){if(e.state==="done")return g(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
918
920
  `)),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 S("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function E(e,t=""){e.write(`${t}
919
- `)}function Ho(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function we(e,t){E(t,`${$(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&E(t,M([[" FROM","TO","TABLES","ROWS","SIZE","COPY","PAUSE"],...e.moves.map(n=>[` node ${n.source}`,n.targets.map(r=>`node ${r}`).join(", ")||"-",n.tables.join(", ")||"-",`~${n.estimated_rows.toLocaleString("en-US")}`,Ho(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),E(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Ho(e.estimated_bytes)}, ~${e.estimated_copy_seconds}s to copy, expected write pause ~${e.expected_pause_ms}ms (ceiling ${e.max_write_pause_ms}ms)`),E(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)E(t,` warning: ${n}`);e.assumptions&&E(t,w(` ${e.assumptions}`))}function Se(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function hg(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Se(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Se(t.at)}`:""}${t.to!==void 0?` --to ${Se(t.to)}`:""}`;case"range.move":return`${n} range move ${Se(`${t.keyspace}:${t.range}`)} --to ${Se(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Se(t.keyspace)} --value ${Se(t.value)}${t.node!==void 0?` --node ${Se(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Se(t.name)} --host ${t.host?Se(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Se(t.keyspace)}`:""}`;default:return null}}function yg(e,t){E(t,e.summary),e.advice.forEach((n,r)=>{E(t),E(t,`${$(`${r+1}. ${n.op}`)} (${n.metric})`),E(t,` ${n.reason}`);let o=hg(n.request);if(E(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;E(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Ho(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])E(t,` warning: ${a}`)}else n.needs.length>0?E(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&E(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(E(t),E(t,w(e.assumptions)))}function El(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Tl(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}E(process.stdout,M([["ID","NAME","ADDRESS","STATE","HOME","RANGES","POSTGRES"],...t.map(n=>[String(n.id),n.name,`${n.host}:${n.port}/${n.dbname}`,n.state,n.home?"yes":"",String(n.ranges.length),n.server_version_num?String(Math.floor(n.server_version_num/1e4)):"?"])]))}function wg(e){let t=process.stdout;if(e.cloud&&(vg(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){E(t,"The home node has no Lepis catalog yet (schema lepis).");return}E(t,`${$("Cluster")} at epoch ${e.epoch}`),E(t),Tl(e);for(let s of e.keyspaces??[]){E(t),E(t,`${$(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),E(t,M([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,El(e,a.node)])]));for(let a of s.pins)E(t,` pinned: ${a.value} on ${El(e,a.node)}`)}let n=e.tables??[];n.length>0&&(E(t),E(t,M([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(E(t),E(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&&E(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 bg(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 _g(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function Pl(e){return e.severity.toUpperCase()}function Rl(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)E(t,`${$(Pl(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&E(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)E(t,`${$(Pl(a))} ${a.message}`),E(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&E(t),E(t,"Resolved in the last day:");for(let a of s)E(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Sg(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function kg(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 vg(e,t){let n=kg(e);if(!e.sharded){E(t,`${$(e.ref)} is not a cluster (${n}).`),Sg(e)>0&&E(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}Rl(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(E(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);E(t,M([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,bg(e,i),...l?[_g(i)]:[]])]))}e.router==="starting"?E(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&E(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&E(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?E(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&E(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),E(t)}function xg(e){if(e.length===0){d("No jobs yet.");return}E(process.stdout,M([["ID","OPERATION","STATE","STEPS","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function $g(e){let t=process.stdout;E(t,`${$(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),E(t,M([[" STEP","KIND","STATE","PHASE"],...e.steps.map(n=>[` ${n.n+1}`,n.kind,n.state,typeof n.detail?.phase=="string"?n.detail.phase:""])])),e.plan&&Array.isArray(e.plan.steps)&&(E(t),we(e.plan,t))}oe();z();vt();import{spawn as Eg}from"node:child_process";import{createHash as Pg}from"node:crypto";import{chmodSync as Ag,renameSync as Qn,rmSync as Ol,writeFileSync as Tg}from"node:fs";import{dirname as Rg,join as Og}from"node:path";var Zn=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Ng="https://github.com/snoutdata/app/releases/download";function jg(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 Dg(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 Ig(){let e;try{e=await fetch(`${Zn}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new S("network",`could not reach ${Zn} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new S("server",`${Zn} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new S("server",`${Zn} did not say which version is newest`);return t.version}async function Nl(e){let t;try{t=await fetch(e)}catch(n){throw new S("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new S("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Cg(e,t){let n=Og(Rg(e),`.snoutdata-upgrade-${process.pid}`);Tg(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Ol(r,{force:!0}),Qn(e,r);try{Qn(n,e)}catch(o){throw Qn(r,e),o}}else Ag(n,493),Qn(n,e)}catch(r){Ol(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new S("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?Te:kt}`):r}}function Mg(){return new Promise((e,t)=>{let n=Eg(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new S("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${Te}`))),n.on("exit",r=>e(r??1))})}async function jl(e={}){let t=e.kind??ln(),n=await Ig(),r=_s(Z,n),o={installed:Z,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{d(r?`snoutdata ${Z} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${Z} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${Z}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new S("usage",`this snoutdata (${Z}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?Te:kt}`);case"npm":{d(`Upgrading snoutdata ${Z} to ${n} with npm\u2026`);let s=await Mg();if(s!==0)throw new S("failed",`npm exited ${s}. Run it yourself: ${Te}`);return g({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=jg(process.platform,process.arch);if(!s)throw new S("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${Te}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Ng}/cli-v${n}`,[l,i]=await Promise.all([Nl(`${a}/${s}`),Nl(`${a}/SHA256SUMS`)]),u=Dg(i.toString("utf8"),s);if(!u)throw new S("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=Pg("sha256").update(l).digest("hex");if(c!==u)throw new S("failed",`checksum mismatch for ${s}. Refusing to install.
921
+ `)}function Ho(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function we(e,t){E(t,`${v(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&E(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")}`,Ho(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),E(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Ho(e.estimated_bytes)}, ~${e.estimated_copy_seconds}s to copy, expected write pause ~${e.expected_pause_ms}ms (ceiling ${e.max_write_pause_ms}ms)`),E(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)E(t,` warning: ${n}`);e.assumptions&&E(t,w(` ${e.assumptions}`))}function Se(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function yg(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Se(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Se(t.at)}`:""}${t.to!==void 0?` --to ${Se(t.to)}`:""}`;case"range.move":return`${n} range move ${Se(`${t.keyspace}:${t.range}`)} --to ${Se(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Se(t.keyspace)} --value ${Se(t.value)}${t.node!==void 0?` --node ${Se(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Se(t.name)} --host ${t.host?Se(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Se(t.keyspace)}`:""}`;default:return null}}function wg(e,t){E(t,e.summary),e.advice.forEach((n,r)=>{E(t),E(t,`${v(`${r+1}. ${n.op}`)} (${n.metric})`),E(t,` ${n.reason}`);let o=yg(n.request);if(E(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;E(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Ho(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])E(t,` warning: ${a}`)}else n.needs.length>0?E(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&E(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(E(t),E(t,w(e.assumptions)))}function Al(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Ol(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}E(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 bg(e){let t=process.stdout;if(e.cloud&&(xg(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){E(t,"The home node has no Lepis catalog yet (schema lepis).");return}E(t,`${v("Cluster")} at epoch ${e.epoch}`),E(t),Ol(e);for(let s of e.keyspaces??[]){E(t),E(t,`${v(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),E(t,L([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Al(e,a.node)])]));for(let a of s.pins)E(t,` pinned: ${a.value} on ${Al(e,a.node)}`)}let n=e.tables??[];n.length>0&&(E(t),E(t,L([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(E(t),E(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&&E(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 _g(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 Sg(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function Tl(e){return e.severity.toUpperCase()}function Nl(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)E(t,`${v(Tl(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&E(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)E(t,`${v(Tl(a))} ${a.message}`),E(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&E(t),E(t,"Resolved in the last day:");for(let a of s)E(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function kg(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function vg(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 xg(e,t){let n=vg(e);if(!e.sharded){E(t,`${v(e.ref)} is not a cluster (${n}).`),kg(e)>0&&E(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}Nl(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(E(t,`${v(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);E(t,L([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,_g(e,i),...l?[Sg(i)]:[]])]))}e.router==="starting"?E(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&E(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&E(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?E(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&E(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),E(t)}function $g(e){if(e.length===0){d("No jobs yet.");return}E(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 Eg(e){let t=process.stdout;E(t,`${v(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),E(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)&&(E(t),we(e.plan,t))}oe();z();vt();import{spawn as Pg}from"node:child_process";import{createHash as Ag}from"node:crypto";import{chmodSync as Tg,renameSync as Qn,rmSync as jl,writeFileSync as Rg}from"node:fs";import{dirname as Og,join as Ng}from"node:path";var Zn=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",jg="https://github.com/snoutdata/app/releases/download";function Dg(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 Ig(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 Cg(){let e;try{e=await fetch(`${Zn}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new S("network",`could not reach ${Zn} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new S("server",`${Zn} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new S("server",`${Zn} did not say which version is newest`);return t.version}async function Dl(e){let t;try{t=await fetch(e)}catch(n){throw new S("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new S("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Mg(e,t){let n=Ng(Og(e),`.snoutdata-upgrade-${process.pid}`);Rg(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;jl(r,{force:!0}),Qn(e,r);try{Qn(n,e)}catch(o){throw Qn(r,e),o}}else Tg(n,493),Qn(n,e)}catch(r){jl(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new S("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?Te:kt}`):r}}function Lg(){return new Promise((e,t)=>{let n=Pg(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new S("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${Te}`))),n.on("exit",r=>e(r??1))})}async function Il(e={}){let t=e.kind??ln(),n=await Cg(),r=_s(Z,n),o={installed:Z,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{d(r?`snoutdata ${Z} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${Z} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${Z}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new S("usage",`this snoutdata (${Z}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?Te:kt}`);case"npm":{d(`Upgrading snoutdata ${Z} to ${n} with npm\u2026`);let s=await Lg();if(s!==0)throw new S("failed",`npm exited ${s}. Run it yourself: ${Te}`);return g({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=Dg(process.platform,process.arch);if(!s)throw new S("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${Te}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${jg}/cli-v${n}`,[l,i]=await Promise.all([Dl(`${a}/${s}`),Dl(`${a}/SHA256SUMS`)]),u=Ig(i.toString("utf8"),s);if(!u)throw new S("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=Ag("sha256").update(l).digest("hex");if(c!==u)throw new S("failed",`checksum mismatch for ${s}. Refusing to install.
920
922
  expected ${u}
921
- actual ${c}`);return Cg(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}hn();Ie();vt();var Dl=`snoutdata ${Z} \u2014 hosted Postgres, from a terminal or an agent
923
+ actual ${c}`);return Mg(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}hn();Ce();vt();var Cl=`snoutdata ${Z} \u2014 hosted Postgres, from a terminal or an agent
922
924
 
923
925
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
924
926
  --env writes DATABASE_URL into ./.env (and adds
@@ -986,6 +988,8 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var zt="_snoutdata_migrations",Zf=`crea
986
988
  snoutdata functions list [--ref R]
987
989
  snoutdata functions size <name> [--memory MB] [--concurrency N] [--reset]
988
990
  its memory and workers, within the plan
991
+ snoutdata functions logs [name] [--errors] [--tail N] [--follow]
992
+ what your functions printed, last 7 days
989
993
  snoutdata functions delete <name>
990
994
 
991
995
  snoutdata secrets set NAME=value [...] the environment functions run with
@@ -1060,11 +1064,11 @@ that re-signs Postgres traffic.
1060
1064
  Docs: https://docs.snoutdata.com/developers/cli
1061
1065
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
1062
1066
  Source: https://github.com/snoutdata/snout-cli
1063
- `,Lg=["yes","token"];async function Ug(e){let[t,n,...r]=e.command;if(I(e,"version")||t==="version")return g({version:Z},()=>process.stdout.write(`${Z}
1064
- `)),0;if(I(e,"help")&&t&&t!=="help"){let i=zo(t,n);if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,nr(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${m}
1067
+ `,Ug=["yes","token"];async function Fg(e){let[t,n,...r]=e.command;if(D(e,"version")||t==="version")return g({version:Z},()=>process.stdout.write(`${Z}
1068
+ `)),0;if(D(e,"help")&&t&&t!=="help"){let i=zo(t,n);if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,nr(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${m}
1065
1069
  ${u.summary}
1066
- `);let h=Math.max(0,...u.flags.map(v=>v.length));for(let v of u.flags)process.stdout.write(` ${v.padEnd(h)} ${nr(u.name,v)}
1070
+ `);let h=Math.max(0,...u.flags.map(x=>x.length));for(let x of u.flags)process.stdout.write(` ${x.padEnd(h)} ${nr(u.name,x)}
1067
1071
  `)}process.stdout.write(`
1068
1072
  Every command also takes --json, --quiet and --help.
1069
- `)}),0}let o=I(e,"help")||t==="help";if(o||!t)return g({version:Z,usage:Dl.trim(),commands:tr},()=>process.stdout.write(Dl)),o?0:Ae.usage;let s=()=>{let i=We(e,"timeout");if(i!==void 0){if(!(i>0))throw new f("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Zt({flag:x(e,"ref")});if(!i)throw new f("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Zt({flag:x(e,"ref")});return i?Ne(i):null};for(let i of Lg)if(e.flags[i]!==void 0&&t!=="cluster")throw new f(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Al(e,{timeoutMs:s()});case"login":{if(I(e,"device")){let m=await dn({name:sn(),platform:process.platform});g({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let h=await un(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ke({accessToken:h.token});let v=x(e,"email"),C=v?(await Ye()).email??void 0:void 0;return v&&wr(v,{accessToken:h.token,email:C}),g({ok:!0,name:h.name??null,email:C??null},()=>d(`Signed in${C?` as ${C}`:""}.`)),0}let i=x(e,"domain"),u=I(e,"sso")||i!==void 0;if(u&&x(e,"provider"))throw new f("--sso and --provider are two different doors: pass one or the other");let c=await gn({provider:x(e,"provider"),sso:u,domain:i,noBrowser:I(e,"no-browser")||!se(),timeoutMs:s(),email:x(e,"email")});return g({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{es();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Ye(),u=Qt()?.expectedEmail,c=!!(u&&i.email&&!br(u,i.email));return c&&V(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),g({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
1070
- `),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 ia(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new f("tokens create needs --name (what is it for, and where does it live)");return await aa({name:i,expiresInDays:We(e,"expires"),project:x(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new f("tokens revoke needs the token's id or its sdt_ prefix");return await ca(i),0}default:throw new f(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await ta(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new f("projects create needs --name");let u=x(e,"team");return await $r({teamId:u?await sa(u):void 0,name:i,region:x(e,"region"),wait:!I(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:I(e,"show-url")}),0}case"pause":case"resume":case"delete":return await na(n,x(e,"ref")??r[0]??a(),{wait:!I(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=x(e,"ref")??r[0]??a(),u=Ne(i);u?Us(u):await qa(i)}return 0;default:throw new f(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await zs(i,r[0],{dir:x(e,"dir"),verifyJwt:!I(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Vs(i),0;if((n==="delete"||n==="remove")&&r[0])return await Ys(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await fa(a(),u,{dir:x(e,"dir"),entrypoint:x(e,"entrypoint"),verifyJwt:!I(e,"no-verify-jwt")}),0}case void 0:case"list":return await ya(a()),0;case"size":{let u=r[0];if(!u)throw new f("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await ha(a(),u,{memoryMb:We(e,"memory"),concurrency:We(e,"concurrency"),reset:I(e,"reset")}),0}case"delete":case"remove":{let u=r[0];if(!u)throw new f("functions delete needs a name");return await wa(a(),u),0}default:throw new f(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await Qs(i,await Sa(r,{stdin:I(e,"stdin")})),0;if(n===void 0||n==="list")return Zs(i),0;if((n==="unset"||n==="remove")&&r[0])return await ea(i,r[0]),0}switch(n){case"set":return await ka(a(),r,{stdin:I(e,"stdin")}),0;case void 0:case"list":return await xa(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new f("secrets unset needs a name");return await va(a(),u),0}default:throw new f(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await la(a()),0;case"rotate":return await da(a(),{force:I(e,"force")}),0;default:throw new f(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await La(a()),0;if(n==="enable"||n==="disable")return await Ua(a(),Ma(r[0]),n==="enable"),0;throw new f(`unknown command: products ${n}. products [enable|disable ${En.join("|")}]`)}case"push":{if(n!=="credentials")throw new f("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await gi(a()),0;if(i==="set"){let u=to(r[1]),c=u==="apns"?await pi({p8:x(e,"p8"),keyId:x(e,"key-id"),teamId:x(e,"team-id"),topic:x(e,"topic"),environment:x(e,"environment")}):await fi(x(e,"file"));return await hi(a(),u,c),0}if(i==="remove"||i==="unset")return await yi(a(),to(r[1])),0;throw new f(`unknown command: push credentials ${i}`)}case"auth":return await ci(a(),n,r,{clientId:x(e,"client-id"),stdin:I(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 ri(a(),{channel:x(e,"channel"),watch:I(e,"watch")});if(n==="logs")return oi(a(),{channel:x(e,"channel"),since:x(e,"since")});throw new f(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Fa(a(),n,r[0]),0;case"teams":return await oa(),0;case"link":{if(I(e,"local")){let c;try{c=ss(n)}catch(h){throw new S("not-found",h instanceof Error?h.message:String(h))}let m=ot(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return g({ref:c.ref,local:c.folder,api:c.apiUrl,path:m},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${m}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=x(e,"ref")??n;if(!i)throw new f("link needs --ref");let u=ot(process.cwd(),{ref:i});return g({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Vr(a()),0;case"psql":return Ya(a(),r);case"reset-password":return await ra(a()),0;case"access":return await ei(a(),r,{level:x(e,"level")}),0;case"push":return ui(a(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return I(e,"window")?(await Ba(a()),0):x(e,"at")?(await Ha(a(),x(e,"at"),x(e,"name")),0):Za(a(),{file:x(e,"file"),force:!!e.flags.force});case"export":return Qa(a(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new f(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new f(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=I(e,"local")?await bl(await Yt()):null,u=i?i.dbUrl:x(e,"db-url");return Si(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 Ms(i)}return hl(await Yt(),{port:We(e,"port"),dir:x(e,"dir"),noMigrations:I(e,"no-migrations"),outOfOrder:I(e,"out-of-order")});case"stop":{let i=l();return i?Ls(i):yl(await Yt())}case"status":{let i=l();return i?Cs(i):wl(await Yt())}case"usage":return xl(a(),{days:We(e,"days"),history:!!e.flags.history});case"mcp":return kl({version:Z,allowDelete:!!e.flags["allow-delete"]});case"init":return Fg(e);case"upgrade":return jl({check:I(e,"check")});default:throw new f(`unknown command: ${t}`)}}async function Fg(e){let t=rs({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.`),I(e,"env")?await Il(o):await Vr(o),t.kind==="link"){let s=Ne(o),a=ot(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=x(e,"name")??Bg(process.cwd());d(`Creating a project called "${n}"\u2026`),await $r({name:n,region:x(e,"region"),wait:!0,link:process.cwd(),showUrl:!I(e,"env")});let r=Zt({cwd:process.cwd()});return r&&I(e,"env")&&await Il(r),0}async function Il(e){let{connection:t}=await Promise.resolve().then(()=>(Y(),xe)),n=await t(e),r=Xa(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 Bg(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Hg(e){if(e instanceof S)return Le(e.code,e.message,e.details),e.exitCode;if(e instanceof f)return Le("usage",e.message),Ae.usage;if(e instanceof Ue)return Le("not-signed-in",e.message),Ae["not-signed-in"];if(e instanceof ge){let r=ze(e.status,e.serverCode);return Le(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ae[r]}let t=cs(e);if(t)return Le("usage",t),Ae.usage;let n=nn(e);return Le(n,e instanceof Error?e.message:String(e)),Ae[n]}function qo(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function qg(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Gg(){$t(qg(process.argv.slice(2)));let e;try{e=Ko(process.argv.slice(2))}catch(t){Le("usage",String(t instanceof Error?t.message:t)),qo(Ae.usage);return}$t(e.json),vs(e.flags.quiet===!0),ls(rn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{qo(await Ug(e))}catch(t){qo(Hg(t))}}Gg();
1073
+ `)}),0}let o=D(e,"help")||t==="help";if(o||!t)return g({version:Z,usage:Cl.trim(),commands:tr},()=>process.stdout.write(Cl)),o?0:Ae.usage;let s=()=>{let i=Ie(e,"timeout");if(i!==void 0){if(!(i>0))throw new f("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Zt({flag:$(e,"ref")});if(!i)throw new f("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Zt({flag:$(e,"ref")});return i?Ne(i):null};for(let i of Ug)if(e.flags[i]!==void 0&&t!=="cluster")throw new f(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return Rl(e,{timeoutMs:s()});case"login":{if(D(e,"device")){let m=await dn({name:sn(),platform:process.platform});g({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let h=await un(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ke({accessToken:h.token});let x=$(e,"email"),M=x?(await Ye()).email??void 0:void 0;return x&&wr(x,{accessToken:h.token,email:M}),g({ok:!0,name:h.name??null,email:M??null},()=>d(`Signed in${M?` as ${M}`:""}.`)),0}let i=$(e,"domain"),u=D(e,"sso")||i!==void 0;if(u&&$(e,"provider"))throw new f("--sso and --provider are two different doors: pass one or the other");let c=await gn({provider:$(e,"provider"),sso:u,domain:i,noBrowser:D(e,"no-browser")||!se(),timeoutMs:s(),email:$(e,"email")});return g({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{es();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Ye(),u=Qt()?.expectedEmail,c=!!(u&&i.email&&!br(u,i.email));return c&&Y(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),g({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
1074
+ `),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 ia(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("tokens create needs --name (what is it for, and where does it live)");return await aa({name:i,expiresInDays:Ie(e,"expires"),project:$(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new f("tokens revoke needs the token's id or its sdt_ prefix");return await ca(i),0}default:throw new f(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await ta(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new f("projects create needs --name");let u=$(e,"team");return await $r({teamId:u?await sa(u):void 0,name:i,region:$(e,"region"),wait:!D(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:D(e,"show-url")}),0}case"pause":case"resume":case"delete":return await na(n,$(e,"ref")??r[0]??a(),{wait:!D(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=$(e,"ref")??r[0]??a(),u=Ne(i);u?Us(u):await Ja(i)}return 0;default:throw new f(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await zs(i,r[0],{dir:$(e,"dir"),verifyJwt:!D(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Vs(i),0;if((n==="delete"||n==="remove")&&r[0])return await Ys(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new f("functions deploy needs a name: snoutdata functions deploy <name>");return await ga(a(),u,{dir:$(e,"dir"),entrypoint:$(e,"entrypoint"),verifyJwt:!D(e,"no-verify-jwt")}),0}case void 0:case"list":return await wa(a()),0;case"size":{let u=r[0];if(!u)throw new f("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await ya(a(),u,{memoryMb:Ie(e,"memory"),concurrency:Ie(e,"concurrency"),reset:D(e,"reset")}),0}case"logs":return await ba(a(),r[0],{errorsOnly:D(e,"errors"),limit:Ie(e,"tail"),follow:D(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new f("functions delete needs a name");return await _a(a(),u),0}default:throw new f(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await Qs(i,await va(r,{stdin:D(e,"stdin")})),0;if(n===void 0||n==="list")return Zs(i),0;if((n==="unset"||n==="remove")&&r[0])return await ea(i,r[0]),0}switch(n){case"set":return await xa(a(),r,{stdin:D(e,"stdin")}),0;case void 0:case"list":return await Ea(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new f("secrets unset needs a name");return await $a(a(),u),0}default:throw new f(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await la(a()),0;case"rotate":return await da(a(),{force:D(e,"force")}),0;default:throw new f(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Fa(a()),0;if(n==="enable"||n==="disable")return await Ba(a(),Ua(r[0]),n==="enable"),0;throw new f(`unknown command: products ${n}. products [enable|disable ${En.join("|")}]`)}case"push":{if(n!=="credentials")throw new f("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await yi(a()),0;if(i==="set"){let u=to(r[1]),c=u==="apns"?await gi({p8:$(e,"p8"),keyId:$(e,"key-id"),teamId:$(e,"team-id"),topic:$(e,"topic"),environment:$(e,"environment")}):await hi($(e,"file"));return await wi(a(),u,c),0}if(i==="remove"||i==="unset")return await bi(a(),to(r[1])),0;throw new f(`unknown command: push credentials ${i}`)}case"auth":return await di(a(),n,r,{clientId:$(e,"client-id"),stdin:D(e,"stdin"),siteUrl:$(e,"site-url"),allow:$(e,"allow"),file:$(e,"file"),subject:$(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return si(a(),{channel:$(e,"channel"),watch:D(e,"watch")});if(n==="logs")return ai(a(),{channel:$(e,"channel"),since:$(e,"since")});throw new f(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Ha(a(),n,r[0]),0;case"teams":return await oa(),0;case"link":{if(D(e,"local")){let c;try{c=ss(n)}catch(h){throw new S("not-found",h instanceof Error?h.message:String(h))}let m=ot(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return g({ref:c.ref,local:c.folder,api:c.apiUrl,path:m},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${m}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=$(e,"ref")??n;if(!i)throw new f("link needs --ref");let u=ot(process.cwd(),{ref:i});return g({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Vr(a()),0;case"psql":return Qa(a(),r);case"reset-password":return await ra(a()),0;case"access":return await ni(a(),r,{level:$(e,"level")}),0;case"push":return pi(a(),{dir:$(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return D(e,"window")?(await qa(a()),0):$(e,"at")?(await Ga(a(),$(e,"at"),$(e,"name")),0):ti(a(),{file:$(e,"file"),force:!!e.flags.force});case"export":return ei(a(),{timeoutMs:s(),out:$(e,"out"),statusOnly:!!e.flags.status});default:throw new f(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new f(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=D(e,"local")?await Sl(await Yt()):null,u=i?i.dbUrl:$(e,"db-url");return vi(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:($(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:$(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Ms(i)}return wl(await Yt(),{port:Ie(e,"port"),dir:$(e,"dir"),noMigrations:D(e,"no-migrations"),outOfOrder:D(e,"out-of-order")});case"stop":{let i=l();return i?Ls(i):bl(await Yt())}case"status":{let i=l();return i?Cs(i):_l(await Yt())}case"usage":return El(a(),{days:Ie(e,"days"),history:!!e.flags.history});case"mcp":return xl({version:Z,allowDelete:!!e.flags["allow-delete"]});case"init":return Bg(e);case"upgrade":return Il({check:D(e,"check")});default:throw new f(`unknown command: ${t}`)}}async function Bg(e){let t=rs({flag:$(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),D(e,"env")?await Ml(o):await Vr(o),t.kind==="link"){let s=Ne(o),a=ot(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=$(e,"name")??Hg(process.cwd());d(`Creating a project called "${n}"\u2026`),await $r({name:n,region:$(e,"region"),wait:!0,link:process.cwd(),showUrl:!D(e,"env")});let r=Zt({cwd:process.cwd()});return r&&D(e,"env")&&await Ml(r),0}async function Ml(e){let{connection:t}=await Promise.resolve().then(()=>(X(),xe)),n=await t(e),r=Za(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 Hg(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function qg(e){if(e instanceof S)return Ue(e.code,e.message,e.details),e.exitCode;if(e instanceof f)return Ue("usage",e.message),Ae.usage;if(e instanceof Fe)return Ue("not-signed-in",e.message),Ae["not-signed-in"];if(e instanceof ge){let r=ze(e.status,e.serverCode);return Ue(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ae[r]}let t=cs(e);if(t)return Ue("usage",t),Ae.usage;let n=nn(e);return Ue(n,e instanceof Error?e.message:String(e)),Ae[n]}function qo(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Gg(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Jg(){$t(Gg(process.argv.slice(2)));let e;try{e=Ko(process.argv.slice(2))}catch(t){Ue("usage",String(t instanceof Error?t.message:t)),qo(Ae.usage);return}$t(e.json),vs(e.flags.quiet===!0),ls(rn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{qo(await Fg(e))}catch(t){qo(qg(t))}}Jg();