snoutdata 0.11.1 → 0.12.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 +55 -55
  2. package/package.json +1 -1
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  var $l=Object.defineProperty;var O=(e,t)=>()=>(e&&(t=e(e=0)),t);var Uo=(e,t)=>{for(var n in t)$l(e,n,{get:t[n],enumerable:!0})};function Fo(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]=Al(o);if(El.has(s)){let l=a??e[++r];if(l===void 0)throw new g(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new g(`--${s} does not take a value`);if(!Pl.has(s))throw new g(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Al(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 H(e,t){return e.flags[t]===!0}function Fe(e,t){let n=x(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new g(`--${t} must be a whole number, not "${n}"`);return r}var g,El,Pl,se=O(()=>{"use strict";g=class extends Error{},El=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"]);Pl=new Set(["json","window","help","version","force","no-verify-jwt","reset","stdin","quiet","yes","plan","status","no-wait","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as Tl,existsSync as Go,mkdirSync as qo,readFileSync as Jo,rmSync as Rl,writeFileSync as Ko}from"node:fs";import{homedir as qt}from"node:os";import{dirname as Yn,join as Wo,parse as Ol,resolve as Nl}from"node:path";function Xn(e=qt()){return Wo(e,".snoutdata","auth.json")}function Jt(e=qt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=Xn(e);if(!Go(n))return null;try{let r=JSON.parse(Jo(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ye(e,t=qt()){let n=Xn(t);qo(Yn(n),{recursive:!0}),Ko(n,`${JSON.stringify(e,null,2)}
3
3
  `,{mode:384}),Tl(n,384)}function zo(e=qt()){Rl(Xn(e),{force:!0})}function Vo(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Yo(e){return Wo(e,".snoutdata","project.json")}function Ve(e){let t=Nl(e),n=Ol(t).root;for(;;){let r=Yo(t);if(Go(r))try{let o=JSON.parse(Jo(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=Yn(t)}}function Ye(e,t){let n=Yo(e);return qo(Yn(n),{recursive:!0}),Ko(n,`${JSON.stringify(t,null,2)}
4
- `),n}function Kt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Ve(e.cwd??process.cwd())?.ref??null}function Xo(e){let t=e.environment??process.env,n=Ve(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 Re=O(()=>{"use strict"});import{existsSync as Zn,readFileSync as Zo}from"node:fs";import{homedir as Dl}from"node:os";import{isAbsolute as Il,join as Oe,resolve as Xe}from"node:path";function jl(e=process.env,t=process.platform,n=Dl()){let r;return t==="win32"?r=e.APPDATA??Oe(n,"AppData","Roaming"):t==="darwin"?r=Oe(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Oe(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Oe(r,o,"localProjects.json"))}function Wt(e=jl()){let t=[];for(let n of e)if(Zn(n))try{let r=JSON.parse(Zo(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 zt(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 mt(e){return Zn(Oe(e,"compose.yaml"))&&Zn(Oe(e,".env"))}function Ne(e,t=null){let n=Xe(e);if(!mt(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=zt(Zo(Oe(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Oe(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 Qo(e,t=process.cwd(),n=Wt()){if(!e){if(mt(t))return Ne(t);let[i]=n;if(n.length===1&&i)return Ne(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=Il(e)?e:Xe(t,e);if(mt(r)){let i=n.find(u=>Xe(u.folder)===Xe(r));return Ne(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 Ne(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 es(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 ts(e,t,n=Wt()){if(t&&mt(t))try{let r=n.find(s=>Xe(s.folder)===Xe(t)),o=Ne(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&mt(r.folder))return Ne(r.folder,r.name);return null}var pt=O(()=>{"use strict"});function D(e,t,n){throw new _(e,t,n)}function Be(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 ns(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 Vt(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(Cl.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 ke,_,Cl,ne=O(()=>{"use strict";ke={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},_=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return ke[this.code]}};Cl=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function Yt(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 Ml){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 Ze(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 rs(e){Qn=e}function le(){return(Qn??Yt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Qe(){return(Qn??Yt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Ml,Qn,He=O(()=>{"use strict";Ml=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];Qn=null});import{execFile as Ll}from"node:child_process";import{existsSync as Ul,readFileSync as as}from"node:fs";import{homedir as er,hostname as Fl}from"node:os";import{join as et}from"node:path";function Bl(e,t,n){if(t==="win32"){let r=n.APPDATA??et(er(),"AppData","Roaming");return et(r,e)}return t==="darwin"?et(er(),"Library","Application Support",e):et(n.XDG_CONFIG_HOME??et(er(),".config"),e)}function is(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>et(Bl(n,e,t),"mcp.json"))}function Hl(e=process.platform,t=process.env){return is(e,t)[0]}function Gl(e=Hl()){if(!Ul(e))return null;try{let t=JSON.parse(as(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function ql(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Kl(e,t){let n=`0100007F:${t.toString(16).toUpperCase().padStart(4,"0")}`,r=[];for(let o of e.split(`
4
+ `),n}function Kt(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Ve(e.cwd??process.cwd())?.ref??null}function Xo(e){let t=e.environment??process.env,n=Ve(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 Re=O(()=>{"use strict"});import{existsSync as Zn,readFileSync as Zo}from"node:fs";import{homedir as Dl}from"node:os";import{isAbsolute as jl,join as Oe,resolve as Xe}from"node:path";function Il(e=process.env,t=process.platform,n=Dl()){let r;return t==="win32"?r=e.APPDATA??Oe(n,"AppData","Roaming"):t==="darwin"?r=Oe(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Oe(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Oe(r,o,"localProjects.json"))}function Wt(e=Il()){let t=[];for(let n of e)if(Zn(n))try{let r=JSON.parse(Zo(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 zt(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 mt(e){return Zn(Oe(e,"compose.yaml"))&&Zn(Oe(e,".env"))}function Ne(e,t=null){let n=Xe(e);if(!mt(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=zt(Zo(Oe(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Oe(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 Qo(e,t=process.cwd(),n=Wt()){if(!e){if(mt(t))return Ne(t);let[i]=n;if(n.length===1&&i)return Ne(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:Xe(t,e);if(mt(r)){let i=n.find(u=>Xe(u.folder)===Xe(r));return Ne(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 Ne(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 es(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 ts(e,t,n=Wt()){if(t&&mt(t))try{let r=n.find(s=>Xe(s.folder)===Xe(t)),o=Ne(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&mt(r.folder))return Ne(r.folder,r.name);return null}var pt=O(()=>{"use strict"});function D(e,t,n){throw new _(e,t,n)}function Be(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 ns(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 Vt(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(Cl.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 ke,_,Cl,ne=O(()=>{"use strict";ke={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},_=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return ke[this.code]}};Cl=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function Yt(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 Ml){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 Ze(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 rs(e){Qn=e}function le(){return(Qn??Yt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Qe(){return(Qn??Yt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Ml,Qn,He=O(()=>{"use strict";Ml=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];Qn=null});import{execFile as Ll}from"node:child_process";import{existsSync as Ul,readFileSync as as}from"node:fs";import{homedir as er,hostname as Fl}from"node:os";import{join as et}from"node:path";function Bl(e,t,n){if(t==="win32"){let r=n.APPDATA??et(er(),"AppData","Roaming");return et(r,e)}return t==="darwin"?et(er(),"Library","Application Support",e):et(n.XDG_CONFIG_HOME??et(er(),".config"),e)}function is(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>et(Bl(n,e,t),"mcp.json"))}function Hl(e=process.platform,t=process.env){return is(e,t)[0]}function Gl(e=Hl()){if(!Ul(e))return null;try{let t=JSON.parse(as(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function ql(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Kl(e,t){let n=`0100007F:${t.toString(16).toUpperCase().padStart(4,"0")}`,r=[];for(let o of e.split(`
5
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 Wl(e,t){return e.split(`
6
- `).some(n=>n.trim()===`n127.0.0.1:${t}`)}function os(e,t){return new Promise(n=>{Ll(e,t,{timeout:5e3,windowsHide:!0},(r,o)=>{n(r&&!o?"":String(o))})})}async function cs(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=Kl(as("/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 os("/usr/sbin/lsof",["-nP","-a",`-iTCP@127.0.0.1:${e}`,"-sTCP:LISTEN","-u",String(n),"-Fn"]);return Wl(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 os("powershell.exe",["-NoProfile","-NonInteractive","-Command",n])).trim()==="mine"}}catch{return!1}return!1}async function ls(e,t){let n=tr.get(e);if(n!==void 0&&Date.now()-n<zl)return!0;let r=await t(e).catch(()=>!1);return r?tr.set(e,Date.now()):tr.delete(e),r}async function or(e,t,n,r=cs){if(!await ls(e.port,r))throw new nr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function Xt(e,t=cs){if(e!==void 0)return ss(e,t);let n="no-config";for(let r of is()){let o=Gl(r);if(!o)continue;let s=await ss(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 ss(e,t){let n=ql(e);if(n)return{available:!1,skip:n};let r=e;if(!await ls(r.port,t))return{available:!1,skip:"not-verified"};let o;try{let s=await or(r,"/cli/whoami",{signal:AbortSignal.timeout(Jl)},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 ds(e,t){let n=await or(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 Zt(){return Fl()}async function rr(e,t,n,r){let o=await or(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 us(e){return await rr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await rr(e,"tools/list",{},3e3)).tools??[]}async function ms(e,t,n){return rr(e,"tools/call",{name:t,arguments:n},12e4)}var Jl,zl,tr,nr,Qt=O(()=>{"use strict";Jl=1500;zl=1e4,tr=new Map;nr=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 Vl}from"node:path";function tn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Vl(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 Yl(e=tn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?ve:ft}}function sr(e={}){let t=e.kind??tn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?ve:`${ft} (or ${ve})`;return`snoutdata ${ee} is installed and ${r}. Run \`${Yl(t,n)}\`, or install again: ${o}`}function fs(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 ee,en,ft,ve,gt=O(()=>{"use strict";ee="0.11.1",en=`snoutdata-cli/${ee}`,ft="curl -fsSL https://snoutdata.com/install.sh | sh",ve="npm install -g snoutdata@latest"});async function gs(e,t){let n=await fetch(`${we}/functions/v1/${e}`,{method:"POST",headers:{apikey:xe,"Content-Type":"application/json","x-client-info":en},body:JSON.stringify(t)});on(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw sn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function nn(e={}){let{status:t,body:n}=await gs("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 rn(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 gs("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 ar=O(()=>{"use strict";V();ne();gt()});function yt(e){ht=e}function ys(e){hs=e}function ws(){return ht}function w(e){return bs?`\x1B[2m${e}\x1B[0m`:e}function v(e){return bs?`\x1B[1m${e}\x1B[0m`:e}function f(e,t){if(ht){process.stdout.write(`${JSON.stringify(e,null,2)}
6
+ `).some(n=>n.trim()===`n127.0.0.1:${t}`)}function os(e,t){return new Promise(n=>{Ll(e,t,{timeout:5e3,windowsHide:!0},(r,o)=>{n(r&&!o?"":String(o))})})}async function cs(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=Kl(as("/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 os("/usr/sbin/lsof",["-nP","-a",`-iTCP@127.0.0.1:${e}`,"-sTCP:LISTEN","-u",String(n),"-Fn"]);return Wl(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 os("powershell.exe",["-NoProfile","-NonInteractive","-Command",n])).trim()==="mine"}}catch{return!1}return!1}async function ls(e,t){let n=tr.get(e);if(n!==void 0&&Date.now()-n<zl)return!0;let r=await t(e).catch(()=>!1);return r?tr.set(e,Date.now()):tr.delete(e),r}async function or(e,t,n,r=cs){if(!await ls(e.port,r))throw new nr(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function Xt(e,t=cs){if(e!==void 0)return ss(e,t);let n="no-config";for(let r of is()){let o=Gl(r);if(!o)continue;let s=await ss(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 ss(e,t){let n=ql(e);if(n)return{available:!1,skip:n};let r=e;if(!await ls(r.port,t))return{available:!1,skip:"not-verified"};let o;try{let s=await or(r,"/cli/whoami",{signal:AbortSignal.timeout(Jl)},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 ds(e,t){let n=await or(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 Zt(){return Fl()}async function rr(e,t,n,r){let o=await or(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 us(e){return await rr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await rr(e,"tools/list",{},3e3)).tools??[]}async function ms(e,t,n){return rr(e,"tools/call",{name:t,arguments:n},12e4)}var Jl,zl,tr,nr,Qt=O(()=>{"use strict";Jl=1500;zl=1e4,tr=new Map;nr=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 Vl}from"node:path";function tn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Vl(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 Yl(e=tn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?ve:ft}}function sr(e={}){let t=e.kind??tn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?ve:`${ft} (or ${ve})`;return`snoutdata ${ee} is installed and ${r}. Run \`${Yl(t,n)}\`, or install again: ${o}`}function fs(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 ee,en,ft,ve,gt=O(()=>{"use strict";ee="0.12.0",en=`snoutdata-cli/${ee}`,ft="curl -fsSL https://snoutdata.com/install.sh | sh",ve="npm install -g snoutdata@latest"});async function gs(e,t){let n=await fetch(`${we}/functions/v1/${e}`,{method:"POST",headers:{apikey:xe,"Content-Type":"application/json","x-client-info":en},body:JSON.stringify(t)});on(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw sn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function nn(e={}){let{status:t,body:n}=await gs("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 rn(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 gs("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 ar=O(()=>{"use strict";V();ne();gt()});function yt(e){ht=e}function ys(e){hs=e}function ws(){return ht}function w(e){return bs?`\x1B[2m${e}\x1B[0m`:e}function v(e){return bs?`\x1B[1m${e}\x1B[0m`:e}function f(e,t){if(ht){process.stdout.write(`${JSON.stringify(e,null,2)}
7
7
  `);return}t()}function d(e){!ht&&!hs&&process.stderr.write(`${e}
8
8
  `)}function De(e,t,n){process.stderr.write(`${t}
9
9
  `),ht&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
10
  `)}function z(e){process.stderr.write(`${e}
11
- `)}function j(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(`
11
+ `)}function I(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
12
  `)}function $e(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 ht,hs,bs,W=O(()=>{"use strict";ht=!1,hs=!1;bs=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as ks,randomBytes as ir}from"node:crypto";import{createServer as Zl}from"node:http";import{createInterface as Ql}from"node:readline/promises";import{spawn as ed}from"node:child_process";function an(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Ss(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.":yd(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">
@@ -22,7 +22,7 @@ var $l=Object.defineProperty;var O=(e,t)=>()=>(e&&(t=e(e=0)),t);var Uo=(e,t)=>{f
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
24
  </body></html>`}function cr(){let e=an(ir(32)),t=an(ks("sha256").update(e).digest());return{verifier:e,challenge:t}}function td(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 nd(e){let{command:t,args:n,verbatim:r}=td(process.platform,e),o=ed(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function lr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function sd(){return"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"}function ad(e){let t=e??an(ir(32));return{raw:t,hashed:ks("sha256").update(t).digest("hex")}}function id(e){return`${rd}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:lr(),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 cd(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 dr(e){return new Promise((t,n)=>{let r="",o=Zl((a,l)=>{let i=cd(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"?Ss("ok"):Ss("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."),nd(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 vs(e,t){let n=await fetch(`${we}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:xe,"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 xs(await n.json())}async function ld(e,t,n){let r=await fetch(od,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:lr(),client_secret:sd(),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 dd(e){if(!lr())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=cr(),{raw:r,hashed:o}=ad(),s=an(ir(16)),{code:a,redirectUri:l}=await dr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>id({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await ld(a,t,l),u=await fetch(`${we}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:xe,"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 xs(await u.json())}function xs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ye(n),n}function _s(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 ud(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 md(e,t,n){let r=ud(t);return e===404||/no sso provider/i.test(r)?new _("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 _("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new _(Be(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function pd(e){let t=await fetch(`${we}/auth/v1/sso`,{method:"POST",headers:{apikey:xe,"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 md(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new _("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 fd(e){if(e!==void 0){let r=_s(e);if(!r)throw new g(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!le()){let r=Qe();throw new g(`login --sso needs --domain <your work email or company domain>${r?`, because ${Ze(r)}`:""}`)}let t=Ql({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new g("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=_s(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new g("no domain to sign in with: pass --domain <your work email or company domain>")}async function gd(e){let t=await fd(e.domain),{verifier:n,challenge:r}=cr();d(`Signing in with the identity provider for ${v(t)}.`);let{code:o}=await dr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>pd({domain:t,redirectTo:s,challenge:r})});return vs(o,n)}async function cn(e={}){return ur(e.email,await hd(e))}function ur(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ye(n),t.email&&!mr(t.email,e)&&z(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function mr(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function hd(e){if(e.sso)return gd({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return dd(e);let{verifier:n,challenge:r}=cr(),{code:o}=await dr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${we}/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 vs(o,n)}function yd(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var rd,od,ln=O(()=>{"use strict";V();se();Re();ne();He();W();rd="https://accounts.google.com/o/oauth2/v2/auth",od="https://oauth2.googleapis.com/token"});import{createInterface as wd}from"node:readline/promises";async function pr(e){let t=wd({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 bd(){let e=await Xt();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 pr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await ds(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 ye(r),d(`Signed in with "${n.name}".`),r}async function Sd(){if(!await pr("Sign in with a browser?"))return null;try{let e=await cn({});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 _d(){if(!await pr("Sign in by typing a code into a browser on another machine?"))return null;let e=await nn({name:Zt(),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 rn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ye(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function $s(){if(!le())return null;let e=await bd();if(e)return e;let t=await Sd();return t||_d()}var Es=O(()=>{"use strict";Re();Qt();ar();He();ln();W()});var be={};Uo(be,{ACCOUNTS_URL:()=>we,ANON_KEY:()=>xe,ApiError:()=>de,FUNCTIONS:()=>As,NotSignedIn:()=>Ie,call:()=>b,connection:()=>ae,exportStatus:()=>tt,isSettled:()=>Ts,listProjects:()=>ue,listTeams:()=>dn,localStack:()=>Ee,noticeFrom:()=>on,outdated:()=>sn,requestExport:()=>wt,waitForReady:()=>St,waitForSettled:()=>bt,whoami:()=>Ge});function kd(){let e=Qe();return e?`not signed in, and ${Ze(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 vd(){let e=Jt();if(e||(e=await $s()),!e)throw new Ie;if(!Vo(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${we}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:xe,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new Ie;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ye(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=Ee(r);if(u)throw new _("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 vd(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${As}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:xe,"Content-Type":"application/json","x-client-info":en},body:n==="GET"?void 0:JSON.stringify(t)});on(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new de(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 Ie(u);if(a.status===410)throw sn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new de(a.status,u,c,i)}return i}function on(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Ps||(Ps=!0,z(`${t} ${sr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function sn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new _("outdated",`${t.replace(/\s+$/,"")} ${sr({minimum:n})}`,{status:410,installed:ee,minimum:n??null})}async function Ge(){return b("cloud-whoami")}async function ue(){return b("cloud-project-list")}async function ae(e){let t=Ee(e);return t?es(t):b("cloud-project-connection",{ref:e})}function Ee(e){return ts(e,Ve(process.cwd())?.local??null)}async function dn(){return b("cloud-team-list")}async function wt(e){return b("cloud-project-export",{ref:e})}async function tt(e){return b("cloud-project-export",{ref:e},"GET")}async function bt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await ue(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ts(t,a))return s;if(Date.now()>=r)throw new de(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function Ts(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function St(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await ue(),o=r.find(s=>s.ref===e);if(!o)throw new de(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new de(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var we,xe,As,de,Ie,Ps,V=O(()=>{"use strict";Re();pt();ne();He();Es();W();gt();we=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",xe=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",As=`${we}/functions/v1`,de=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Ie=class extends Error{constructor(t){super(t??kd())}};Ps=!1});function Qr(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function yi(e,t,n){let r="";for(;r.length<n;)r+=Qr(e,t);return r}var Zr,hi,wi=O(()=>{"use strict";Zr="0123456789abcdefghjkmnpqrstvwxyz",hi="abcdefghjkmnpqrstvwxyz"});function _i(e){return Qr(e,bi)+yi(e,Zr,Si-1)}function tp(e){return ep.test(e)}function te(e){if(!tp(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function F(e,t=np){return te(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var bi,Si,ep,np,_e=O(()=>{"use strict";wi();bi=hi,Si=13,ep=new RegExp(`^[${bi}][${Zr}]{${Si-1}}$`);np="db.snoutdata.com"});var ki=O(()=>{"use strict";_e()});function xi(e,t){let n=vi(e,7),r=vi(t,24);return Math.max(2,Math.ceil(n*24/r))}function vi(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var eo=O(()=>{"use strict"});function to(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var no=O(()=>{"use strict"});var $i=O(()=>{"use strict"});var vw,Ei=O(()=>{"use strict";_e();vw=7*24});var $w,Pi=O(()=>{"use strict";$w=7*24*60*60*1e3});var rp,Ai=O(()=>{"use strict";Pi();rp=60*60*1e3});var at,Ow,Ti=O(()=>{"use strict";at="snout_storage_admin",Ow=`do $$ begin
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."),nd(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 vs(e,t){let n=await fetch(`${we}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:xe,"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 xs(await n.json())}async function ld(e,t,n){let r=await fetch(od,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:lr(),client_secret:sd(),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 dd(e){if(!lr())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=cr(),{raw:r,hashed:o}=ad(),s=an(ir(16)),{code:a,redirectUri:l}=await dr({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>id({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await ld(a,t,l),u=await fetch(`${we}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:xe,"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 xs(await u.json())}function xs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ye(n),n}function _s(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 ud(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 md(e,t,n){let r=ud(t);return e===404||/no sso provider/i.test(r)?new _("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 _("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new _(Be(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function pd(e){let t=await fetch(`${we}/auth/v1/sso`,{method:"POST",headers:{apikey:xe,"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 md(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new _("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 fd(e){if(e!==void 0){let r=_s(e);if(!r)throw new g(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!le()){let r=Qe();throw new g(`login --sso needs --domain <your work email or company domain>${r?`, because ${Ze(r)}`:""}`)}let t=Ql({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new g("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=_s(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new g("no domain to sign in with: pass --domain <your work email or company domain>")}async function gd(e){let t=await fd(e.domain),{verifier:n,challenge:r}=cr();d(`Signing in with the identity provider for ${v(t)}.`);let{code:o}=await dr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>pd({domain:t,redirectTo:s,challenge:r})});return vs(o,n)}async function cn(e={}){return ur(e.email,await hd(e))}function ur(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ye(n),t.email&&!mr(t.email,e)&&z(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function mr(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function hd(e){if(e.sso)return gd({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return dd(e);let{verifier:n,challenge:r}=cr(),{code:o}=await dr({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${we}/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 vs(o,n)}function yd(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var rd,od,ln=O(()=>{"use strict";V();se();Re();ne();He();W();rd="https://accounts.google.com/o/oauth2/v2/auth",od="https://oauth2.googleapis.com/token"});import{createInterface as wd}from"node:readline/promises";async function pr(e){let t=wd({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 bd(){let e=await Xt();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 pr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await ds(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 ye(r),d(`Signed in with "${n.name}".`),r}async function Sd(){if(!await pr("Sign in with a browser?"))return null;try{let e=await cn({});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 _d(){if(!await pr("Sign in by typing a code into a browser on another machine?"))return null;let e=await nn({name:Zt(),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 rn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ye(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function $s(){if(!le())return null;let e=await bd();if(e)return e;let t=await Sd();return t||_d()}var Es=O(()=>{"use strict";Re();Qt();ar();He();ln();W()});var be={};Uo(be,{ACCOUNTS_URL:()=>we,ANON_KEY:()=>xe,ApiError:()=>de,FUNCTIONS:()=>As,NotSignedIn:()=>je,call:()=>b,connection:()=>ae,exportStatus:()=>tt,isSettled:()=>Ts,listProjects:()=>ue,listTeams:()=>dn,localStack:()=>Ee,noticeFrom:()=>on,outdated:()=>sn,requestExport:()=>wt,waitForReady:()=>St,waitForSettled:()=>bt,whoami:()=>Ge});function kd(){let e=Qe();return e?`not signed in, and ${Ze(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 vd(){let e=Jt();if(e||(e=await $s()),!e)throw new je;if(!Vo(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${we}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:xe,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new je;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ye(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=Ee(r);if(u)throw new _("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 vd(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${As}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:xe,"Content-Type":"application/json","x-client-info":en},body:n==="GET"?void 0:JSON.stringify(t)});on(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new de(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 je(u);if(a.status===410)throw sn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new de(a.status,u,c,i)}return i}function on(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Ps||(Ps=!0,z(`${t} ${sr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function sn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new _("outdated",`${t.replace(/\s+$/,"")} ${sr({minimum:n})}`,{status:410,installed:ee,minimum:n??null})}async function Ge(){return b("cloud-whoami")}async function ue(){return b("cloud-project-list")}async function ae(e){let t=Ee(e);return t?es(t):b("cloud-project-connection",{ref:e})}function Ee(e){return ts(e,Ve(process.cwd())?.local??null)}async function dn(){return b("cloud-team-list")}async function wt(e){return b("cloud-project-export",{ref:e})}async function tt(e){return b("cloud-project-export",{ref:e},"GET")}async function bt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await ue(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ts(t,a))return s;if(Date.now()>=r)throw new de(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function Ts(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function St(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await ue(),o=r.find(s=>s.ref===e);if(!o)throw new de(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new de(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var we,xe,As,de,je,Ps,V=O(()=>{"use strict";Re();pt();ne();He();Es();W();gt();we=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",xe=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",As=`${we}/functions/v1`,de=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},je=class extends Error{constructor(t){super(t??kd())}};Ps=!1});function Qr(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function yi(e,t,n){let r="";for(;r.length<n;)r+=Qr(e,t);return r}var Zr,hi,wi=O(()=>{"use strict";Zr="0123456789abcdefghjkmnpqrstvwxyz",hi="abcdefghjkmnpqrstvwxyz"});function _i(e){return Qr(e,bi)+yi(e,Zr,Si-1)}function tp(e){return ep.test(e)}function te(e){if(!tp(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function F(e,t=np){return te(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var bi,Si,ep,np,_e=O(()=>{"use strict";wi();bi=hi,Si=13,ep=new RegExp(`^[${bi}][${Zr}]{${Si-1}}$`);np="db.snoutdata.com"});var ki=O(()=>{"use strict";_e()});function xi(e,t){let n=vi(e,7),r=vi(t,24);return Math.max(2,Math.ceil(n*24/r))}function vi(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var eo=O(()=>{"use strict"});function to(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var no=O(()=>{"use strict"});var $i=O(()=>{"use strict"});var vw,Ei=O(()=>{"use strict";_e();vw=7*24});var $w,Pi=O(()=>{"use strict";$w=7*24*60*60*1e3});var rp,Ai=O(()=>{"use strict";Pi();rp=60*60*1e3});var at,Ow,Ti=O(()=>{"use strict";at="snout_storage_admin",Ow=`do $$ begin
26
26
  if exists (select from pg_roles where rolname = 'supabase_storage_admin') then
27
27
  if exists (select from pg_roles where rolname = '${at}') then
28
28
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${at}';
@@ -32,7 +32,7 @@ ${e==="ok"?"":'<p style="color:#8f887e;margin:14px 0 0;font-size:14px">Run <code
32
32
  end if;
33
33
  alter role supabase_storage_admin rename to ${at};
34
34
  end if;
35
- end $$;`});var Ce,Dw,An,Ri,Iw,Oi=O(()=>{"use strict";Ce="snout_realtime_admin",Dw=`do $snoutpod$
35
+ end $$;`});var Ce,Dw,An,Ri,jw,Oi=O(()=>{"use strict";Ce="snout_realtime_admin",Dw=`do $snoutpod$
36
36
  begin
37
37
  if exists (select from pg_roles where rolname = 'supabase_admin') then
38
38
  if exists (select from pg_roles where rolname = '${Ce}') then
@@ -55,7 +55,7 @@ begin
55
55
  end if;
56
56
  end
57
57
  $snoutpod$;
58
- `,An="snoutdata_realtime",Ri="supabase_realtime",Iw=`do $snoutpod$
58
+ `,An="snoutdata_realtime",Ri="supabase_realtime",jw=`do $snoutpod$
59
59
  begin
60
60
  if exists (select 1 from pg_publication where pubname = '${An}') then
61
61
  return;
@@ -69,10 +69,10 @@ begin
69
69
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
70
70
  end
71
71
  $snoutpod$;
72
- `});var Cw,Mw,Ni=O(()=>{"use strict";Cw=2*1024*1024,Mw=64*1024});var Di,Ii,op,sp,ap,ji=O(()=>{"use strict";Di="/snoutfn",Ii="/_snoutpod/health",op="x-snoutdata-door",sp="SNOUT_FUNCTIONS_DOOR_SECRET",ap=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
72
+ `});var Cw,Mw,Ni=O(()=>{"use strict";Cw=2*1024*1024,Mw=64*1024});var Di,ji,op,sp,ap,Ii=O(()=>{"use strict";Di="/snoutfn",ji="/_snoutpod/health",op="x-snoutdata-door",sp="SNOUT_FUNCTIONS_DOOR_SECRET",ap=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
73
73
  // every time the agent starts, from packages/snoutpod/src/host/functionMain.ts.
74
74
  const MOUNT = ${JSON.stringify(Di)};
75
- const HEALTH = ${JSON.stringify(Ii)};
75
+ const HEALTH = ${JSON.stringify(ji)};
76
76
  const DOOR_HEADER = ${JSON.stringify(op)};
77
77
  const DOOR = new TextEncoder().encode(Deno.env.get(${JSON.stringify(sp)}) ?? '');
78
78
  if (DOOR.length === 0) {
@@ -235,7 +235,7 @@ Deno.serve(async (req) => {
235
235
  return refuse(500, 'This function could not be run.', detail);
236
236
  }
237
237
  });
238
- `});function ro(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Tn(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 cp(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 cp(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(ro("the subnet",n,Ci),ro("the sink",r,ip),!Li(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Li(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",`${ro("a denied prefix",s,Ci)},${r}`);return o.push(e.name),o}function Mi(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Li(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Mi(e)&s)>>>0===(Mi(n??"")&s)>>>0}function Ui(e){return e?.internal===!0}var Ci,ip,oo=O(()=>{"use strict";Ci=/^((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])$/,ip=/^((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 lp,Rn=O(()=>{"use strict";lp=7*24*60*60});var Fi=O(()=>{"use strict";_e();Ni();ji();oo();Rn()});var Bi=O(()=>{"use strict"});import{createHash as up}from"node:crypto";function On(e){return`${F(e).pod}-lepis-tls`}function pp(e){return up("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Ji(e,t){let n={...e},r=n[so],o=n[ao];delete n[so],delete n[ao];for(let[s,a,l]of[[so,r,t],[ao,o,pp(t)]])if(a!==void 0){if(!mp.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var It,so,ao,Hi,Gi,io,qi,mp,co=O(()=>{"use strict";_e();Bi();It="lepis",so="SNOUTPOD_ADMIN_PASSWORD_ENV",ao="SNOUTPOD_ADMIN_TOKEN_ENV",Hi=7433,Gi=1e3;io="/tls",qi=["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(`
238
+ `});function ro(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Tn(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 cp(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 cp(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(ro("the subnet",n,Ci),ro("the sink",r,ip),!Li(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Li(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",`${ro("a denied prefix",s,Ci)},${r}`);return o.push(e.name),o}function Mi(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Li(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Mi(e)&s)>>>0===(Mi(n??"")&s)>>>0}function Ui(e){return e?.internal===!0}var Ci,ip,oo=O(()=>{"use strict";Ci=/^((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])$/,ip=/^((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 lp,Rn=O(()=>{"use strict";lp=7*24*60*60});var Fi=O(()=>{"use strict";_e();Ni();Ii();oo();Rn()});var Bi=O(()=>{"use strict"});import{createHash as up}from"node:crypto";function On(e){return`${F(e).pod}-lepis-tls`}function pp(e){return up("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Ji(e,t){let n={...e},r=n[so],o=n[ao];delete n[so],delete n[ao];for(let[s,a,l]of[[so,r,t],[ao,o,pp(t)]])if(a!==void 0){if(!mp.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var jt,so,ao,Hi,Gi,io,qi,mp,co=O(()=>{"use strict";_e();Bi();jt="lepis",so="SNOUTPOD_ADMIN_PASSWORD_ENV",ao="SNOUTPOD_ADMIN_TOKEN_ENV",Hi=7433,Gi=1e3;io="/tls",qi=["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(`
239
239
  `),mp=/^[A-Z][A-Z0-9_]{0,63}$/});function Wi(e){let t=[];for(let n of e.split(`
240
240
  `)){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 Ki,lo=O(()=>{"use strict";Ki="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var ub,mb,zi=O(()=>{"use strict";_e();Rn();ub=6*60*60,mb=5*60});var Vi=O(()=>{"use strict"});var Yi=O(()=>{"use strict"});var Xi=O(()=>{"use strict";Yi()});function Qi(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"||!gp.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!fp.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 ec(e,t){return`${e}.nspname !~ '^pg_'
241
241
  and ${e}.nspname <> all (array[${Zi.map(n=>`'${n}'`).join(", ")}]::text[])
@@ -497,7 +497,7 @@ end
497
497
  $snoutpod$;
498
498
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
499
499
  `});var oc,lS,dS,uS,mS,pS,fS,sc=O(()=>{"use strict";ki();eo();no();$i();Ei();Ai();Ti();Oi();Fi();co();lo();zi();Vi();Xi();Rn();uo();oc=120,lS=2*6e4,dS=15*60*1e3,uS=60*60*1e3,mS=60*60*1e3,pS=5*60*1e3,fS=10*60*1e3});import{execFile as bp}from"node:child_process";function ac(){return(e,t,n={})=>new Promise((r,o)=>{let s=bp(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 U(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
500
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var jt=O(()=>{"use strict"});import{readdir as Sp,readFile as bS}from"node:fs/promises";async function ic(e,t,n={}){let r=n.binary??"podman",o=F(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??_p)(i)!=="empty":!1}async function _p(e){try{return(await Sp(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var cc=O(()=>{"use strict";_e();jt()});function lc(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 mc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function pc(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=${lc("the access key",e.key,vp)}`),n.push("--env",dc),e.token&&n.push("--env",uc)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${lc("the endpoint",e.endpoint,kp)}`),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 fc(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{[dc]:e.secret,...e.token?{[uc]:e.token}:{}}}var dc,uc,kp,vp,gc=O(()=>{"use strict";dc="SNOUTPOD_S3_SECRET",uc="SNOUTPOD_S3_TOKEN",kp=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,vp=/^[A-Za-z0-9/+=_.-]{1,256}$/});function In(e){return xp[e]??e}var hc,yc,Ae,xp,wc,bc=O(()=>{"use strict";hc="authenticator",yc=`do $snoutpod$
500
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var It=O(()=>{"use strict"});import{readdir as Sp,readFile as bS}from"node:fs/promises";async function ic(e,t,n={}){let r=n.binary??"podman",o=F(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??_p)(i)!=="empty":!1}async function _p(e){try{return(await Sp(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var cc=O(()=>{"use strict";_e();It()});function lc(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 mc(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function pc(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=${lc("the access key",e.key,vp)}`),n.push("--env",dc),e.token&&n.push("--env",uc)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${lc("the endpoint",e.endpoint,kp)}`),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 fc(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{[dc]:e.secret,...e.token?{[uc]:e.token}:{}}}var dc,uc,kp,vp,gc=O(()=>{"use strict";dc="SNOUTPOD_S3_SECRET",uc="SNOUTPOD_S3_TOKEN",kp=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,vp=/^[A-Za-z0-9/+=_.-]{1,256}$/});function jn(e){return xp[e]??e}var hc,yc,Ae,xp,wc,bc=O(()=>{"use strict";hc="authenticator",yc=`do $snoutpod$
501
501
  declare
502
502
  wanted text[];
503
503
  begin
@@ -574,48 +574,48 @@ BEGIN
574
574
  END
575
575
  $snoutpod$;
576
576
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
577
- `});function xc(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 vc,AS,TS,$c=O(()=>{"use strict";jt();fo();vc="SNOUTPOD_OAUTH_ISSUER",AS=256*1024,TS=5*6e4});import{createHash as yo,randomBytes as $p}from"node:crypto";import{access as Ep,constants as Pp}from"node:fs/promises";import{delimiter as Ap,join as Tp}from"node:path";function Rp(e,t){return yo("sha256").update(wo(e)).update(e.smtpSecretEnv&&t?yo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function wo(e){if(Array.isArray(e))return`[${e.map(wo).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)}:${wo(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function So(e){if(!Np.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Mt(e){if(!Dp.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Ip(e,t,n){return`postgres://${Mt(t)}:${n}@127.0.0.1:5432/${te(e)}`}function Ac(e,t,n,r){let o=Ji(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=In(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]=Ip(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),jp(t.image,o)}function jp(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 Tc(e,t){return`${F(e).pod}-${So(t)}`}function jn(e){return`snoutpod-host-${So(e)}`}function Cp(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return yo("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 Mp(e,t){let n=[],r=bo(t);for(let o of e??[])if(So(o.name),Cn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Cn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===It){let s=o.hostPort+Gi;Cn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Hi}`)}}return n}function bo(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 Cn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Oc(e){let t=[];for(let n of Object.keys(e)){if(!Lp.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Up(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Ic.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Cn(e.hostPort,`${e.name}: hostPort`),["--label",`${_o}=${e.hostPort}:${t}`])}function Fp(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||!Ic.test(r)?null:{port:n,path:r}}async function Hp(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Ep(o,Pp.X_OK));for(let o of n.split(Ap))if(o)try{let s=Tp(o,e);return await r(s),s}catch{}return null}function Gp(){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(`
578
- `)}function Jp(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Kp(e,t){for(let r of e??[]){let o=/^PG_MAJOR=(\d+)$/.exec(r.trim());if(o)return o[1]??null}let n=t.split("@")[0]??"";return/:(\d+)(?:[.-][^/:]*)?$/.exec(n)?.[1]??null}function ko(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Wp(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 zp(e,t,n,r){return{image:await Vp(e,t,r),data:await Yp(e,t,n)}}async function Vp(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 Kp(r,n)}async function Yp(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",F(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?ko(s.stdout):null}catch{return null}}function Xp(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:Nc;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Nc)}`),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 Zp(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Bp;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Hp(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Cc(e){let t=e.binary??"podman",n=e.exec,r=Zp(e),o=e.probe??jc;async function s(p){try{await n(t,["exec",...oe,"--interactive",F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:qp,stdin:Gp()})}catch{}}async function a(){let p=await U(n,t,["ps","--all","--filter",`label=${fe}`,"--format","json"]),y=p.trim()?JSON.parse(p):[],S=new Map,T=new Map;for(let E of y){let N=E.Labels?.[fe],I=E.Labels?.[ze];if(!N||!I)continue;let Z=S.get(N)??[];Z.push(I),S.set(N,Z);let Te=Number(E.Labels?.[ho]??0),A=Number.isInteger(Te)?Te:0;T.set(N,Math.max(T.get(N)??A,A))}let P=y.filter(E=>E.Labels?.[fe]&&!E.Labels?.[ze]),G=P.map(E=>tf(E.State)),X=await ef(P,Qp,(E,N)=>G[N]==="running"?Promise.resolve(!0):e.hasData(E.Labels?.[fe]??""));return P.map((E,N)=>{let I=E.Labels?.[fe]??"",Z=Number(E.Labels?.[go]);return{ref:I,status:G[N]??"unknown",generation:Number.isInteger(Z)?Z:null,hasData:X[N]??!1,services:S.get(I)??[],...T.has(I)?{servicesGeneration:T.get(I)}:{}}})}async function l(){let p=await U(n,t,["ps","--all","--filter",`label=${fe}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(S=>S.Labels?.[fe]&&!S.Labels?.[ze]).length}async function i(p){let y=await(e.majors??((S,T)=>zp(n,t,S,T)))(p.ref,p.image);return Wp(p.ref,y)}async function u(p){let y=F(p.ref),S=await i(p);if(S)throw new Error(S);let T=e.credentials,P=await mc(e.repository,T?()=>T.forProject(p.ref,p.bucket):null),G={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...fc(P)},X=[...await r(),"run","--detach","--name",y.pod,"--label",`${fe}=${p.ref}`,"--label",`${go}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...of(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?bo(e.podBind).flatMap(E=>["--publish",`${E}:${p.port}:5432`]):[],...Mp(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"]:[],...rf(p.oauth),...Xp(p.tuning).flatMap(E=>["--env",E]),"--env",`SNOUTPOD_RETENTION_FULL=${xi(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...pc(P),p.image,"snoutpod-postgres"];await U(n,t,X,{env:G})}async function c(p){let y=await U(n,t,["ps","--all","--filter",`label=${fe}=${te(p)}`,"--filter",`label=${ze}`,"--format","json"]),S=y.trim()?JSON.parse(y):[],T=[];for(let P of S){let G=P.Labels?.[ze];if(!G)continue;let X=Number(P.Labels?.[ho]??0);T.push({name:G,container:Tc(p,G),spec:P.Labels?.[Ec]??"",pod:P.Labels?.[Pc]??"",running:P.State==="running",generation:Number.isInteger(X)?X:0})}return T}async function m(p){let y=await n(t,["container","inspect","--format","{{.Id}}",F(p).pod]);return y.code===0?y.stdout.trim():""}async function h(p,y){let S=[];for(let E of y){let N=E.dbUriRole?In(E.dbUriRole):null;E.dbUriEnv&&N&&!S.includes(N)&&S.push(Mt(N))}let T=new Map;if(S.length===0)return T;let P={},G=[],X=S.includes(Ae)?[wc]:[];return S.includes(hc)&&X.push(yc),S.forEach((E,N)=>{let I=$p(24).toString("base64url"),Z=`SNOUTPOD_SERVICE_PASSWORD_${N}`;T.set(E,I),P[Z]=I,G.push("--env",Z),X.push(`\\getenv service_password_${N} ${Z}`),X.push(`\\set service_role_${N} ${E}`),X.push(`alter role :"service_role_${N}" with password :'service_password_${N}';`)}),X.push(""),await U(n,t,["exec",...oe,"--interactive",...G,F(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",F(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:P,stdin:X.join(`
579
- `)}),T}async function $(p){let y=On(p.ref);await U(n,t,["volume","create","--ignore",y]),await U(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${io}`,"--entrypoint","bash",p.image,"-c",qi])}async function B(p){let y=p.services??[],S=await c(p.ref),T;y.some(A=>A.smtpSecretEnv)&&(T=await e.smtpSecret?.().catch(()=>{}));let P=y.length>0?await m(p.ref):"",G=new Map(S.map(A=>[A.name,A])),X=new Map(y.map(A=>[A.name,Rp(A,T)])),E=new Set(y.filter(A=>{let he=G.get(A.name);return!he||!he.running||he.spec!==X.get(A.name)||P===""||he.pod!==P}).map(A=>A.name)),N=p.servicesGeneration??0;if(E.size===0&&y.length>0&&!S.some(A=>A.generation>=N))for(let A of y)E.add(A.name);let I=A=>A.dbUriEnv&&A.dbUriRole?In(A.dbUriRole):null;for(let A=!0;A;){A=!1;let he=new Set(y.filter(pe=>E.has(pe.name)).map(I));for(let pe of y){let Lo=I(pe);!E.has(pe.name)&&Lo!==null&&he.has(Lo)&&(E.add(pe.name),A=!0)}}for(let A of S)(E.has(A.name)||!X.has(A.name))&&await U(n,t,["rm","--force","--ignore",A.container]);let Z=y.filter(A=>E.has(A.name)),Te=await h(p,Z);Z.some(A=>A.name===It)&&await $(p);for(let A of Z){let he=Tc(p.ref,A.name),pe=[...await r(),"run","--detach","--name",he,"--label",`${fe}=${p.ref}`,"--label",`${go}=${p.generation}`,"--label",`${ze}=${A.name}`,"--label",`${ho}=${p.servicesGeneration??0}`,"--label",`${Ec}=${X.get(A.name)??""}`,"--label",`${Pc}=${P}`,"--network",`container:${F(p.ref).pod}`,"--userns",`container:${F(p.ref).pod}`,"--memory",`${A.memoryMb}m`,"--restart","no",...Up(A),...A.name===It?["--volume",`${On(p.ref)}:${io}:ro`]:[],...Oc(Ac(p,A,Te,T)),A.image];await U(n,t,pe,{env:Ac(p,A,Te,T)})}}async function K(p){let y=await n(t,["exec",F(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 S=y.stdout.trim();return S==="t"?!0:S==="f"?!1:null}return{list:a,inRecovery:K,preflight:i,create:u,createServices:B,async start(p){await U(n,t,["start",F(p).pod])},async startServices(p){let y=(await c(p)).map(S=>S.container);y.length>0&&await U(n,t,["start",...y])},async applyHostServices(p){let y=[],S=await U(n,t,["ps","--all","--filter",`label=${Ct}`,"--format","json"]),T=S.trim()?JSON.parse(S):[],P=new Map;for(let E of T){let N=E.Labels?.[Ct];N&&P.set(N,{spec:E.Labels?.[Rc]??"",state:E.State??""})}for(let E of p)try{await X(E)}catch(N){y.push({name:E.name,action:"failed",reason:N instanceof Error?N.message:String(N)})}async function G(E){let N=await r();if((await n(t,[...N,"image","exists",E])).code===0)return;let I=await n(t,[...N,"pull","--quiet",E]);if(I.code===0)return;let Z=Jp(E);if(!Z||!e.registryLogin)throw new Error(`${E} could not be pulled: ${(I.stderr||I.stdout).trim()}`);await e.registryLogin(Z),await U(n,t,[...N,"pull","--quiet",E])}async function X(E){let N=e.hostServiceLocal?.(E,p),I=N?{...E,...N.env&&Object.keys(N.env).length>0?{env:{...E.env,...N.env}}:{},...N.volumes?{volumes:N.volumes}:{},...N.network?{network:N.network}:{}}:E,Z=jn(I.name),Te=Cp(I),A=P.get(I.name);if(P.delete(I.name),A&&A.spec===Te){A.state!=="running"&&(await U(n,t,["start",Z]),y.push({name:I.name,action:"started",reason:A.state}));return}let he=A?"the spec changed":"it was not here";await G(I.image),A&&await U(n,t,["rm","--force","--ignore",Z]),await U(n,t,[...await r(),"run","--detach","--name",Z,"--label",`${Ct}=${I.name}`,"--label",`${Rc}=${Te}`,...I.healthPath?["--label",`${_o}=${I.hostPort}:${I.healthPath}`]:[],"--memory",`${I.memoryMb}m`,"--restart","no",...I.network?["--network",I.network]:e.network?["--network",e.network]:[],...(I.volumes??[]).flatMap(pe=>["--volume",pe]),...bo(e.podBind).flatMap(pe=>["--publish",`${pe}:${I.hostPort}:${I.port}`]),...I.adminPort&&I.adminHostPort?["--publish",`127.0.0.1:${I.adminHostPort}:${I.adminPort}`]:[],...Oc(I.env),I.image,...I.command??[]],{env:{...I.env}}),y.push({name:I.name,action:A?"recreated":"created",reason:he})}for(let E of P.keys())await U(n,t,["rm","--force","--ignore",jn(E)]),y.push({name:E,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(p){let y=await U(n,t,["ps","--all","--filter",`label=${Ct}`,"--format","json"]),S=(y.trim()?JSON.parse(y):[]).map(P=>P.Labels?.[Ct]??"").filter(P=>P!=="");if(S.length===0)return[];let T=Date.now()+p;for(;;){let P=[];for(let G of S)P.push(await Dc(n,t,G,jn(G),o));if(P.every(G=>G.ok)||Date.now()>=T)return P;await new Promise(G=>setTimeout(G,500))}},async serviceHealth(p,y){let S=await c(p);if(S.length===0)return[];let T=Date.now()+y;for(;;){let P=await Promise.all(S.map(G=>Dc(n,t,G.name,G.container,o)));if(P.every(G=>G.ok)||Date.now()>=T)return P;await new Promise(G=>setTimeout(G,500))}},async stop(p,y){for(let S of await c(p))await U(n,t,["stop","--time",String(Op),S.container]);await U(n,t,["stop","--time",String(y),F(p).pod])},async remove(p){for(let y of await c(p))await U(n,t,["rm","--force","--ignore",y.container]);await U(n,t,["rm","--force","--ignore","--volumes",F(p).pod])},async removeData(p){await U(n,t,["volume","rm","--force",F(p).pod]);let y=On(p);(await n(t,["volume","exists",y])).code===0&&await U(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(`
577
+ `});function xc(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 vc,AS,TS,$c=O(()=>{"use strict";It();fo();vc="SNOUTPOD_OAUTH_ISSUER",AS=256*1024,TS=5*6e4});import{createHash as yo,randomBytes as $p}from"node:crypto";import{access as Ep,constants as Pp}from"node:fs/promises";import{delimiter as Ap,join as Tp}from"node:path";function Rp(e,t){return yo("sha256").update(wo(e)).update(e.smtpSecretEnv&&t?yo("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function wo(e){if(Array.isArray(e))return`[${e.map(wo).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)}:${wo(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function So(e){if(!Np.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Mt(e){if(!Dp.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function jp(e,t,n){return`postgres://${Mt(t)}:${n}@127.0.0.1:5432/${te(e)}`}function Ac(e,t,n,r){let o=Ji(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=jn(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),Ip(t.image,o)}function Ip(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 Tc(e,t){return`${F(e).pod}-${So(t)}`}function In(e){return`snoutpod-host-${So(e)}`}function Cp(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return yo("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 Mp(e,t){let n=[],r=bo(t);for(let o of e??[])if(So(o.name),Cn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Cn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===jt){let s=o.hostPort+Gi;Cn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Hi}`)}}return n}function bo(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 Cn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Oc(e){let t=[];for(let n of Object.keys(e)){if(!Lp.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Up(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?[]:(Cn(e.hostPort,`${e.name}: hostPort`),["--label",`${_o}=${e.hostPort}:${t}`])}function Fp(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 Hp(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Ep(o,Pp.X_OK));for(let o of n.split(Ap))if(o)try{let s=Tp(o,e);return await r(s),s}catch{}return null}function Gp(){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(`
578
+ `)}function Jp(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Kp(e,t){for(let r of e??[]){let o=/^PG_MAJOR=(\d+)$/.exec(r.trim());if(o)return o[1]??null}let n=t.split("@")[0]??"";return/:(\d+)(?:[.-][^/:]*)?$/.exec(n)?.[1]??null}function ko(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Wp(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 zp(e,t,n,r){return{image:await Vp(e,t,r),data:await Yp(e,t,n)}}async function Vp(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 Kp(r,n)}async function Yp(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",F(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?ko(s.stdout):null}catch{return null}}function Xp(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:Nc;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Nc)}`),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 Zp(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Bp;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Hp(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Cc(e){let t=e.binary??"podman",n=e.exec,r=Zp(e),o=e.probe??Ic;async function s(p){try{await n(t,["exec",...oe,"--interactive",F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"--quiet","--file","-"],{timeoutMs:qp,stdin:Gp()})}catch{}}async function a(){let p=await U(n,t,["ps","--all","--filter",`label=${fe}`,"--format","json"]),y=p.trim()?JSON.parse(p):[],S=new Map,T=new Map;for(let E of y){let N=E.Labels?.[fe],j=E.Labels?.[ze];if(!N||!j)continue;let Z=S.get(N)??[];Z.push(j),S.set(N,Z);let Te=Number(E.Labels?.[ho]??0),A=Number.isInteger(Te)?Te:0;T.set(N,Math.max(T.get(N)??A,A))}let P=y.filter(E=>E.Labels?.[fe]&&!E.Labels?.[ze]),G=P.map(E=>tf(E.State)),X=await ef(P,Qp,(E,N)=>G[N]==="running"?Promise.resolve(!0):e.hasData(E.Labels?.[fe]??""));return P.map((E,N)=>{let j=E.Labels?.[fe]??"",Z=Number(E.Labels?.[go]);return{ref:j,status:G[N]??"unknown",generation:Number.isInteger(Z)?Z:null,hasData:X[N]??!1,services:S.get(j)??[],...T.has(j)?{servicesGeneration:T.get(j)}:{}}})}async function l(){let p=await U(n,t,["ps","--all","--filter",`label=${fe}`,"--format","json"]);return(p.trim()?JSON.parse(p):[]).filter(S=>S.Labels?.[fe]&&!S.Labels?.[ze]).length}async function i(p){let y=await(e.majors??((S,T)=>zp(n,t,S,T)))(p.ref,p.image);return Wp(p.ref,y)}async function u(p){let y=F(p.ref),S=await i(p);if(S)throw new Error(S);let T=e.credentials,P=await mc(e.repository,T?()=>T.forProject(p.ref,p.bucket):null),G={SNOUTPOD_OWNER_PASSWORD:p.ownerPassword,POSTGRES_PASSWORD:p.adminPassword,...fc(P)},X=[...await r(),"run","--detach","--name",y.pod,"--label",`${fe}=${p.ref}`,"--label",`${go}=${p.generation}`,"--userns=auto","--memory",`${p.memoryMb}m`,...of(p.cpus),"--restart","no",...e.network?["--network",e.network]:[],...p.port?bo(e.podBind).flatMap(E=>["--publish",`${E}:${p.port}:5432`]):[],...Mp(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"]:[],...rf(p.oauth),...Xp(p.tuning).flatMap(E=>["--env",E]),"--env",`SNOUTPOD_RETENTION_FULL=${xi(p.backup?.retentionDays,p.backup?.fullIntervalHours)}`,...pc(P),p.image,"snoutpod-postgres"];await U(n,t,X,{env:G})}async function c(p){let y=await U(n,t,["ps","--all","--filter",`label=${fe}=${te(p)}`,"--filter",`label=${ze}`,"--format","json"]),S=y.trim()?JSON.parse(y):[],T=[];for(let P of S){let G=P.Labels?.[ze];if(!G)continue;let X=Number(P.Labels?.[ho]??0);T.push({name:G,container:Tc(p,G),spec:P.Labels?.[Ec]??"",pod:P.Labels?.[Pc]??"",running:P.State==="running",generation:Number.isInteger(X)?X:0})}return T}async function m(p){let y=await n(t,["container","inspect","--format","{{.Id}}",F(p).pod]);return y.code===0?y.stdout.trim():""}async function h(p,y){let S=[];for(let E of y){let N=E.dbUriRole?jn(E.dbUriRole):null;E.dbUriEnv&&N&&!S.includes(N)&&S.push(Mt(N))}let T=new Map;if(S.length===0)return T;let P={},G=[],X=S.includes(Ae)?[wc]:[];return S.includes(hc)&&X.push(yc),S.forEach((E,N)=>{let j=$p(24).toString("base64url"),Z=`SNOUTPOD_SERVICE_PASSWORD_${N}`;T.set(E,j),P[Z]=j,G.push("--env",Z),X.push(`\\getenv service_password_${N} ${Z}`),X.push(`\\set service_role_${N} ${E}`),X.push(`alter role :"service_role_${N}" with password :'service_password_${N}';`)}),X.push(""),await U(n,t,["exec",...oe,"--interactive",...G,F(p.ref).pod,"psql","--username","snoutpod_admin","--dbname",F(p.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:P,stdin:X.join(`
579
+ `)}),T}async function $(p){let y=On(p.ref);await U(n,t,["volume","create","--ignore",y]),await U(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${io}`,"--entrypoint","bash",p.image,"-c",qi])}async function B(p){let y=p.services??[],S=await c(p.ref),T;y.some(A=>A.smtpSecretEnv)&&(T=await e.smtpSecret?.().catch(()=>{}));let P=y.length>0?await m(p.ref):"",G=new Map(S.map(A=>[A.name,A])),X=new Map(y.map(A=>[A.name,Rp(A,T)])),E=new Set(y.filter(A=>{let he=G.get(A.name);return!he||!he.running||he.spec!==X.get(A.name)||P===""||he.pod!==P}).map(A=>A.name)),N=p.servicesGeneration??0;if(E.size===0&&y.length>0&&!S.some(A=>A.generation>=N))for(let A of y)E.add(A.name);let j=A=>A.dbUriEnv&&A.dbUriRole?jn(A.dbUriRole):null;for(let A=!0;A;){A=!1;let he=new Set(y.filter(pe=>E.has(pe.name)).map(j));for(let pe of y){let Lo=j(pe);!E.has(pe.name)&&Lo!==null&&he.has(Lo)&&(E.add(pe.name),A=!0)}}for(let A of S)(E.has(A.name)||!X.has(A.name))&&await U(n,t,["rm","--force","--ignore",A.container]);let Z=y.filter(A=>E.has(A.name)),Te=await h(p,Z);Z.some(A=>A.name===jt)&&await $(p);for(let A of Z){let he=Tc(p.ref,A.name),pe=[...await r(),"run","--detach","--name",he,"--label",`${fe}=${p.ref}`,"--label",`${go}=${p.generation}`,"--label",`${ze}=${A.name}`,"--label",`${ho}=${p.servicesGeneration??0}`,"--label",`${Ec}=${X.get(A.name)??""}`,"--label",`${Pc}=${P}`,"--network",`container:${F(p.ref).pod}`,"--userns",`container:${F(p.ref).pod}`,"--memory",`${A.memoryMb}m`,"--restart","no",...Up(A),...A.name===jt?["--volume",`${On(p.ref)}:${io}:ro`]:[],...Oc(Ac(p,A,Te,T)),A.image];await U(n,t,pe,{env:Ac(p,A,Te,T)})}}async function K(p){let y=await n(t,["exec",F(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 S=y.stdout.trim();return S==="t"?!0:S==="f"?!1:null}return{list:a,inRecovery:K,preflight:i,create:u,createServices:B,async start(p){await U(n,t,["start",F(p).pod])},async startServices(p){let y=(await c(p)).map(S=>S.container);y.length>0&&await U(n,t,["start",...y])},async applyHostServices(p){let y=[],S=await U(n,t,["ps","--all","--filter",`label=${Ct}`,"--format","json"]),T=S.trim()?JSON.parse(S):[],P=new Map;for(let E of T){let N=E.Labels?.[Ct];N&&P.set(N,{spec:E.Labels?.[Rc]??"",state:E.State??""})}for(let E of p)try{await X(E)}catch(N){y.push({name:E.name,action:"failed",reason:N instanceof Error?N.message:String(N)})}async function G(E){let N=await r();if((await n(t,[...N,"image","exists",E])).code===0)return;let j=await n(t,[...N,"pull","--quiet",E]);if(j.code===0)return;let Z=Jp(E);if(!Z||!e.registryLogin)throw new Error(`${E} could not be pulled: ${(j.stderr||j.stdout).trim()}`);await e.registryLogin(Z),await U(n,t,[...N,"pull","--quiet",E])}async function X(E){let N=e.hostServiceLocal?.(E,p),j=N?{...E,...N.env&&Object.keys(N.env).length>0?{env:{...E.env,...N.env}}:{},...N.volumes?{volumes:N.volumes}:{},...N.network?{network:N.network}:{}}:E,Z=In(j.name),Te=Cp(j),A=P.get(j.name);if(P.delete(j.name),A&&A.spec===Te){A.state!=="running"&&(await U(n,t,["start",Z]),y.push({name:j.name,action:"started",reason:A.state}));return}let he=A?"the spec changed":"it was not here";await G(j.image),A&&await U(n,t,["rm","--force","--ignore",Z]),await U(n,t,[...await r(),"run","--detach","--name",Z,"--label",`${Ct}=${j.name}`,"--label",`${Rc}=${Te}`,...j.healthPath?["--label",`${_o}=${j.hostPort}:${j.healthPath}`]:[],"--memory",`${j.memoryMb}m`,"--restart","no",...j.network?["--network",j.network]:e.network?["--network",e.network]:[],...(j.volumes??[]).flatMap(pe=>["--volume",pe]),...bo(e.podBind).flatMap(pe=>["--publish",`${pe}:${j.hostPort}:${j.port}`]),...j.adminPort&&j.adminHostPort?["--publish",`127.0.0.1:${j.adminHostPort}:${j.adminPort}`]:[],...Oc(j.env),j.image,...j.command??[]],{env:{...j.env}}),y.push({name:j.name,action:A?"recreated":"created",reason:he})}for(let E of P.keys())await U(n,t,["rm","--force","--ignore",In(E)]),y.push({name:E,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(p){let y=await U(n,t,["ps","--all","--filter",`label=${Ct}`,"--format","json"]),S=(y.trim()?JSON.parse(y):[]).map(P=>P.Labels?.[Ct]??"").filter(P=>P!=="");if(S.length===0)return[];let T=Date.now()+p;for(;;){let P=[];for(let G of S)P.push(await Dc(n,t,G,In(G),o));if(P.every(G=>G.ok)||Date.now()>=T)return P;await new Promise(G=>setTimeout(G,500))}},async serviceHealth(p,y){let S=await c(p);if(S.length===0)return[];let T=Date.now()+y;for(;;){let P=await Promise.all(S.map(G=>Dc(n,t,G.name,G.container,o)));if(P.every(G=>G.ok)||Date.now()>=T)return P;await new Promise(G=>setTimeout(G,500))}},async stop(p,y){for(let S of await c(p))await U(n,t,["stop","--time",String(Op),S.container]);await U(n,t,["stop","--time",String(y),F(p).pod])},async remove(p){for(let y of await c(p))await U(n,t,["rm","--force","--ignore",y.container]);await U(n,t,["rm","--force","--ignore","--volumes",F(p).pod])},async removeData(p){await U(n,t,["volume","rm","--force",F(p).pod]);let y=On(p);(await n(t,["volume","exists",y])).code===0&&await U(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(`
580
580
  `);await U(n,t,["exec",...oe,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",F(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,S){let T=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Mt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
581
- `);await U(n,t,["exec",...oe,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",F(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:S},stdin:T})},async updateExtensions(p){let y=F(p),S=await U(n,t,["exec",...oe,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-tA","-F",po,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Sc});return _c(S)},async applyDbAccess(p,y){let S=F(p),T=await U(n,t,["exec",...oe,"--interactive","--env",Dn,S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Dn]:rc(p,y)},stdin:tc,timeoutMs:6e4});return nc(T)},async runProjectSql(p,y){let S=F(p);await U(n,t,["exec",...oe,"--interactive",S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(p,y,S){let T=jn(p),P=Mt(y),G=Mt(S),X=N=>["exec",T,"psql","--username",P,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",N];(await U(n,t,X(`select 1 from pg_database where datname = '${G}'`))).trim()!=="1"&&await U(n,t,X(`create database "${G}"`))},async replicationSlots(p){let y=await U(n,t,["exec",...oe,F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",Ki]);return Wi(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 U(n,t,["exec",...oe,F(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 U(n,t,["exec",...oe,"--interactive",F(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:nf(y,p.ref)})},async waitReady(p,y){let S=F(p).pod,T=Date.now()+y;for(;;){let P=await n(t,["exec",...oe,S,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(P.code===0&&P.stdout.trim()==="t")return s(p),!0;if(Date.now()>=T)return!1;await new Promise(G=>setTimeout(G,500))}},async waitStandby(p,y){let S=Date.now()+y;for(;;){if(await K(p)===!0)return!0;if(Date.now()>=S)return!1;await new Promise(T=>setTimeout(T,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",...oe,F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return y.code===0?to(y.stdout):null},async storageBytes(p){let y=await n(t,["exec",...oe,F(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?to(y.stdout):null},async setReadOnly(p,y){let S=F(p);await U(n,t,["exec",...oe,S.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${S.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(p){let y=await n(t,["exec",...oe,F(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[S="",T=""]=y.stdout.trim().split(`
582
- `)[0]?.split("|")??[],P=Number(T);return{lastArchivedAt:S.trim()||null,failures:Number.isInteger(P)&&P>=0?P:0}}}}async function Dc(e,t,n,r,o=jc){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=Fp(l?.[_o]);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 ef(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 tf(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function nf(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(`
583
- `)}function rf(e){if(!e?.issuer)return[];try{return["--env",`${vc}=${xc(e.issuer)}`]}catch{return[]}}function of(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var fe,go,ze,ho,Ec,Pc,Op,Np,Dp,Ct,Rc,Lp,Ic,_o,jc,Bp,oe,qp,Nc,Qp,fo=O(()=>{"use strict";_e();eo();lo();jt();gc();no();bc();kc();uo();$c();co();fe="snoutpod.ref",go="snoutpod.generation",ze="snoutpod.service",ho="snoutpod.services-generation",Ec="snoutpod.service-spec",Pc="snoutpod.service-pod";Op=10,Np=/^[a-z][a-z0-9-]{0,30}$/;Dp=/^[a-z][a-z0-9_]{0,62}$/;Ct="snoutpod.host-service",Rc="snoutpod.host-service-spec";Lp=/^[A-Za-z_][A-Za-z0-9_]*$/;Ic=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,_o="snoutpod.health";jc=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()})},Bp="crun";oe=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],qp=3e4;Nc=200;Qp=8});var Xc={};Uo(Xc,{DEFAULT_LOCAL_MEMORY_MB:()=>Bc,DEFAULT_LOCAL_PORT:()=>df,ENGINE:()=>J,ENGINE_NAME:()=>vo,IMAGE:()=>Eo,NETWORK:()=>Me,NEW_PROJECT_MAJOR:()=>$o,NO_BLACKHOLE_ROUTES:()=>Jc,PUBLIC_IMAGE:()=>ff,PUBLISHED_MAJORS:()=>ct,capacity:()=>qc,chooseLocalImage:()=>Ft,dropIfUnused:()=>Vc,ensureImage:()=>gf,ensureNetwork:()=>Sf,exec:()=>Q,hasData:()=>Fn,localImageFor:()=>Gc,localPsql:()=>vf,localRuntime:()=>bf,localSpec:()=>zc,localStatus:()=>Po,localUri:()=>Wc,networkPolicyInPlace:()=>Kc,newLocalPassword:()=>mf,newLocalRef:()=>uf,podLog:()=>Yc,podName:()=>lt,podmanReady:()=>yf,removeLocal:()=>Ef,staleImage:()=>xo,startLocal:()=>kf,stopLocal:()=>$f,volumePgVersion:()=>Mn});import{randomBytes as Uc}from"node:crypto";import{existsSync as Mc}from"node:fs";import{platform as Un}from"node:os";import{delimiter as sf,join as af}from"node:path";function cf(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Un()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(sf))if(t&&e.some(n=>Mc(af(t,n))))return"podman";return Mc("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Ft(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Ut($o);let n=Number(ko(e));if(!ct.includes(n)){let r=e.trim()||"(empty)",o=ct.map(s=>Ut(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ct.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Ut(n)}function Ut(e){return{name:`${lf}:${e}`,published:`${Fc}:${e}`,major:e}}function uf(){return _i(e=>Uc(e))}function mf(){return Uc(18).toString("base64url")}async function gf(e,t=Q,n=Ft(null)){let r=n.published;if(!r)return null;if((await t(J,["image","exists",n.name])).code===0)return await hf(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(J,["pull",r],{timeoutMs:Hc}).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(J,["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 hf(e,t,n,r){let o=await t(J,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
581
+ `);await U(n,t,["exec",...oe,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",F(p).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:S},stdin:T})},async updateExtensions(p){let y=F(p),S=await U(n,t,["exec",...oe,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",y.database,"--quiet","-tA","-F",po,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:Sc});return _c(S)},async applyDbAccess(p,y){let S=F(p),T=await U(n,t,["exec",...oe,"--interactive","--env",Dn,S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Dn]:rc(p,y)},stdin:tc,timeoutMs:6e4});return nc(T)},async runProjectSql(p,y){let S=F(p);await U(n,t,["exec",...oe,"--interactive",S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(p,y,S){let T=In(p),P=Mt(y),G=Mt(S),X=N=>["exec",T,"psql","--username",P,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",N];(await U(n,t,X(`select 1 from pg_database where datname = '${G}'`))).trim()!=="1"&&await U(n,t,X(`create database "${G}"`))},async replicationSlots(p){let y=await U(n,t,["exec",...oe,F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tA","-F","|","-c",Ki]);return Wi(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 U(n,t,["exec",...oe,F(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 U(n,t,["exec",...oe,"--interactive",F(p.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:nf(y,p.ref)})},async waitReady(p,y){let S=F(p).pod,T=Date.now()+y;for(;;){let P=await n(t,["exec",...oe,S,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(P.code===0&&P.stdout.trim()==="t")return s(p),!0;if(Date.now()>=T)return!1;await new Promise(G=>setTimeout(G,500))}},async waitStandby(p,y){let S=Date.now()+y;for(;;){if(await K(p)===!0)return!0;if(Date.now()>=S)return!1;await new Promise(T=>setTimeout(T,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",...oe,F(p).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",p,"-tAc","select pg_database_size(current_database())"]);return y.code===0?to(y.stdout):null},async storageBytes(p){let y=await n(t,["exec",...oe,F(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?to(y.stdout):null},async setReadOnly(p,y){let S=F(p);await U(n,t,["exec",...oe,S.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${S.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(p){let y=await n(t,["exec",...oe,F(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[S="",T=""]=y.stdout.trim().split(`
582
+ `)[0]?.split("|")??[],P=Number(T);return{lastArchivedAt:S.trim()||null,failures:Number.isInteger(P)&&P>=0?P:0}}}}async function Dc(e,t,n,r,o=Ic){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=Fp(l?.[_o]);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 ef(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 tf(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function nf(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(`
583
+ `)}function rf(e){if(!e?.issuer)return[];try{return["--env",`${vc}=${xc(e.issuer)}`]}catch{return[]}}function of(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var fe,go,ze,ho,Ec,Pc,Op,Np,Dp,Ct,Rc,Lp,jc,_o,Ic,Bp,oe,qp,Nc,Qp,fo=O(()=>{"use strict";_e();eo();lo();It();gc();no();bc();kc();uo();$c();co();fe="snoutpod.ref",go="snoutpod.generation",ze="snoutpod.service",ho="snoutpod.services-generation",Ec="snoutpod.service-spec",Pc="snoutpod.service-pod";Op=10,Np=/^[a-z][a-z0-9-]{0,30}$/;Dp=/^[a-z][a-z0-9_]{0,62}$/;Ct="snoutpod.host-service",Rc="snoutpod.host-service-spec";Lp=/^[A-Za-z_][A-Za-z0-9_]*$/;jc=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,_o="snoutpod.health";Ic=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()})},Bp="crun";oe=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],qp=3e4;Nc=200;Qp=8});var Xc={};Uo(Xc,{DEFAULT_LOCAL_MEMORY_MB:()=>Bc,DEFAULT_LOCAL_PORT:()=>df,ENGINE:()=>J,ENGINE_NAME:()=>vo,IMAGE:()=>Eo,NETWORK:()=>Me,NEW_PROJECT_MAJOR:()=>$o,NO_BLACKHOLE_ROUTES:()=>Jc,PUBLIC_IMAGE:()=>ff,PUBLISHED_MAJORS:()=>ct,capacity:()=>qc,chooseLocalImage:()=>Ft,dropIfUnused:()=>Vc,ensureImage:()=>gf,ensureNetwork:()=>Sf,exec:()=>Q,hasData:()=>Fn,localImageFor:()=>Gc,localPsql:()=>vf,localRuntime:()=>bf,localSpec:()=>zc,localStatus:()=>Po,localUri:()=>Wc,networkPolicyInPlace:()=>Kc,newLocalPassword:()=>mf,newLocalRef:()=>uf,podLog:()=>Yc,podName:()=>lt,podmanReady:()=>yf,removeLocal:()=>Ef,staleImage:()=>xo,startLocal:()=>kf,stopLocal:()=>$f,volumePgVersion:()=>Mn});import{randomBytes as Uc}from"node:crypto";import{existsSync as Mc}from"node:fs";import{platform as Un}from"node:os";import{delimiter as sf,join as af}from"node:path";function cf(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Un()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(sf))if(t&&e.some(n=>Mc(af(t,n))))return"podman";return Mc("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Ft(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Ut($o);let n=Number(ko(e));if(!ct.includes(n)){let r=e.trim()||"(empty)",o=ct.map(s=>Ut(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ct.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Ut(n)}function Ut(e){return{name:`${lf}:${e}`,published:`${Fc}:${e}`,major:e}}function uf(){return _i(e=>Uc(e))}function mf(){return Uc(18).toString("base64url")}async function gf(e,t=Q,n=Ft(null)){let r=n.published;if(!r)return null;if((await t(J,["image","exists",n.name])).code===0)return await hf(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(J,["pull",r],{timeoutMs:Hc}).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(J,["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 hf(e,t,n,r){let o=await t(J,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
584
584
  `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(J,["pull",r],{timeoutMs:Hc}).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(`
585
585
  `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(J,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(J,["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 yf(e=pf,t=Eo){if(vo!=="podman")return`SNOUTPOD_ENGINE=${vo}: 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(J,["--version"])}catch{return`${J} 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`${J} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await Q(J,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await Q(J,["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 wf(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(J,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
586
586
  `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Fn(e){let t=Un()==="darwin"||Un()==="win32";return ic(Q,e,{binary:J,look:t?wf:void 0})}async function Mn(e,t=Q,n=Un()){let r=F(e).pod;if((await t(J,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of ct){let l=Ut(a).name;if((await t(J,["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 (${ct.map(a=>Ut(a).published).join(" or ")}).`);o=await t(J,["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(J,["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(J,["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 Gc(e,t=Mn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Ft(null));te(e);let n=t===Mn?Ln.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Ft(o)}catch(s){throw new Error(`${lt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Mn&&(Ln.set(e,r),r.catch(()=>Ln.delete(e))),r}async function qc(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function bf(e){return Cc({exec:Q,binary:J,network:Me,hasData:Fn,capacity:qc,repository:e})}function lt(e){return F(e).pod}async function Sf(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await _f()||await Q(J,Tn({name:Me}));return}let t=Tn({name:Me,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Kc())return;await e?.(),await Q(J,["network","rm","--force",Me]).catch(()=>{});let o=await Q(J,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Jc}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Me} with egress blocked${n?`: ${n}`:""}`)}async function _f(){return(await Q(J,["network","inspect",Me,"--format","json"])).code===0}async function Kc(){let e=await Q(J,["network","inspect",Me,"--format","json"]);if(e.code!==0)return!1;try{return Ui(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Wc(e){let t=F(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function zc(e,t={}){return te(e.ref),{ref:e.ref,generation:1,image:t.image??Eo,memoryMb:t.memoryMb??Bc,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 Lt(e,t){e.onProgress?.(t)}async function kf(e,t,n={}){let r=n.image??(await Gc(t.ref,n.pgVersion)).name,o=zc(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Fn,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??xo)(t.ref,r))?(Lt(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??Vc)(i)):l.status==="stopped"?(Lt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Lt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??xo)(t.ref,r)&&Lt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Lt(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 xf(t.ref,s,n.podLog??Yc));return await e.applyOwnerPassword(o),await e.createServices(o),Po(e,t,n)}function Lc(e){return e.trim().replace(/^sha256:/,"")}async function xo(e,t){let[n,r]=await Promise.all([Q(J,["container","inspect","--format","{{.Image}}",lt(e)]).catch(()=>null),Q(J,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Lc(n.stdout),s=Lc(r.stdout);return o&&s&&o!==s?o:null}async function Vc(e){let t=await Q(J,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await Q(J,["rmi",e]).catch(()=>{})}async function vf(e,t){let n=F(e.ref);return Q(J,["exec","--interactive","-e","PGPASSWORD",lt(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 Yc(e){let t=await Q(J,["logs","--tail","20",lt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function xf(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${lt(e)} never became ready in ${t}ms
587
587
  --- pod log ---
588
- ${r}`}async function $f(e,t,n={}){te(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,oc),Po(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Ef(e,t,n){te(t),await e.remove(t),n&&(await e.removeData(t),Ln.delete(t))}async function Po(e,t,n={}){te(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Fn;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?Wc(t):null}}var vo,J,$o,ct,lf,Fc,Eo,Me,df,Bc,Q,pf,ff,Hc,Ln,Jc,Zc=O(()=>{"use strict";sc();jt();cc();oo();fo();_e();vo=process.env.SNOUTPOD_ENGINE??"podman";J=cf(),$o=18,ct=[17,18],lf="snoutpod/postgres",Fc="ghcr.io/snoutdata/snoutpod-postgres";Eo=Ft(null).name,Me=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",df=54322,Bc=512,Q=ac();pf="cd packages/snoutpod && npm run image:build",ff=`${Fc}:${$o}`;Hc=20*60*1e3;Ln=new Map;Jc="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});se();var ce=["--admin","--token","--project"],Wn=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],ut=[...ce,"--plan","--no-wait","--timeout",...Wn],ie=[...ut,"--yes"],zn=[{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:"shards status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...ce]},{name:"shards ops",summary:"The operations the router offers, and its default settings",flags:[...ce]},{name:"shards nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...ce]},{name:"shards enable",summary:"SnoutData Cloud: turn sharding on for a project. 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:"shards nodes attach",summary:"SnoutData Cloud: hand a ready node pod (nodes add made it) to the cluster, as a standby until a range moves onto it",flags:[...ut]},{name:"shards nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node pod, which uses one of the plan project slots",flags:[...ut,"--host","--port","--dbname","--sslmode","--peer-host"]},{name:"shards nodes drain",summary:"Move every range off a node",flags:[...ie,"--to"]},{name:"shards nodes remove",summary:"Detach a drained node",flags:[...ie]},{name:"shards keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...ut,"--key-type","--ranges","--seed","--nodes"]},{name:"shards table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ie,"--column","--keyspace"]},{name:"shards table reference",summary:"Copy a table to every node",flags:[...ie]},{name:"shards table global",summary:"Bring a table back to the home node alone",flags:[...ie]},{name:"shards range split",summary:"One range becomes two, and one half moves",flags:[...ie,"--keyspace","--range","--at","--to"]},{name:"shards range merge",summary:"Two adjacent ranges become one",flags:[...ie]},{name:"shards range move",summary:"Give a range to another node",flags:[...ie,"--keyspace","--range","--to"]},{name:"shards tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ie,"--keyspace","--value","--node"]},{name:"shards rebalance",summary:"Even out the ranges across the nodes",flags:[...ie,"--keyspace"]},{name:"shards scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ie,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"shards verify",summary:"Row counts and a checksum per range, source against target",flags:[...ut,"--keyspace"]},{name:"shards cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ie,"--node"]},{name:"shards plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...ce,...Wn]},{name:"shards jobs",summary:"The latest jobs",flags:[...ce]},{name:"shards jobs show",summary:"One job, its plan and its steps",flags:[...ce]},{name:"shards jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...ce,"--timeout"]},{name:"shards jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...ce,"--yes"]},{name:"shards jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...ce,"--no-wait","--timeout"]},{name:"shards settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...ce,...Wn,"--set"]},{name:"shards 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:[...ce,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],Bo={"--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","shards jobs cancel --yes":"Cancel without asking","shards 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","shards nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The shard 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 shard key column","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","shards range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","shards cleanup --node":"Only this node. Default: every node","--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","shards 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 Vn(e,t){return Bo[`${e} ${t}`]??Bo[t]??""}function Ho(e,t){let n=zn.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}V();ne();W();import{spawn as xd}from"node:child_process";function $d(e){return`snout-${e}`}function nt(e,t,n){return new Promise((r,o)=>{let s=xd("docker",["compose","-p",$d(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 _("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 _("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Ed(e){let t=await nt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new _("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 Rs(e){let t=await Ed(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return f({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(`${j([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
589
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Os(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await nt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new _("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return f({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 Ns(e){let t=await nt(e,["stop"],!0);if(t.code!==0)throw new _("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return f({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Ds(e){f({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${j([["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}`]])}
590
- `)})}pt();import{cpSync as Cs,existsSync as kt,readdirSync as Id,readFileSync as Ms,rmSync as Ls,statSync as jd,writeFileSync as Us}from"node:fs";import{join as me,resolve as un}from"node:path";var Pd=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Ad(e){return Pd.test(e)}function qe(e){if(!Ad(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 Td=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Rd(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!==".."&&Td.test(n))}var fr=2*1024*1024,Is=200;function js(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>Is)throw new Error(`a function may hold ${Is} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Rd(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=Od(o);if(s.length>fr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${fr/1024} KB. A function is source code; put large files in Storage.`);return o}function Od(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 Nd=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Dd=["SNOUTDATA_","SNOUT_"];function _t(e){if(!Nd.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Dd.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 Rh=64*1024;ne();se();W();function Fs(e){let t=me(e.folder,"functions");return kt(t)?Id(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&jd(me(t,n)).isDirectory()).sort():[]}async function mn(e){d(w(" docker compose run --rm functions-deploy"));let t=await nt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new _("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Bs(e,t,n){let r=me(e.folder,".env"),o=Ms(r,"utf8"),s=(zt(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 Us(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
588
+ ${r}`}async function $f(e,t,n={}){te(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,oc),Po(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Ef(e,t,n){te(t),await e.remove(t),n&&(await e.removeData(t),Ln.delete(t))}async function Po(e,t,n={}){te(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Fn;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?Wc(t):null}}var vo,J,$o,ct,lf,Fc,Eo,Me,df,Bc,Q,pf,ff,Hc,Ln,Jc,Zc=O(()=>{"use strict";sc();It();cc();oo();fo();_e();vo=process.env.SNOUTPOD_ENGINE??"podman";J=cf(),$o=18,ct=[17,18],lf="snoutpod/postgres",Fc="ghcr.io/snoutdata/snoutpod-postgres";Eo=Ft(null).name,Me=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",df=54322,Bc=512,Q=ac();pf="cd packages/snoutpod && npm run image:build",ff=`${Fc}:${$o}`;Hc=20*60*1e3;Ln=new Map;Jc="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});se();var ce=["--admin","--token","--project"],Wn=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],ut=[...ce,"--plan","--no-wait","--timeout",...Wn],ie=[...ut,"--yes"],zn=[{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:[...ce]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...ce]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...ce]},{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 nodes attach",summary:"SnoutData Cloud: hand a ready node pod (nodes add made it) to the cluster, as a standby until a range moves onto it",flags:[...ut]},{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 pod, which uses one of the plan project slots",flags:[...ut,"--host","--port","--dbname","--sslmode","--peer-host"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...ie,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ie]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...ut,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ie,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ie]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ie]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ie,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ie]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ie,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ie,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ie,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ie,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...ut,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ie,"--node"]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...ce,...Wn]},{name:"cluster jobs",summary:"The latest jobs",flags:[...ce]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...ce]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...ce,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...ce,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...ce,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...ce,...Wn,"--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:[...ce,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],Bo={"--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","--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","--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 Vn(e,t){return Bo[`${e} ${t}`]??Bo[t]??""}function Ho(e,t){let n=zn.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}V();ne();W();import{spawn as xd}from"node:child_process";function $d(e){return`snout-${e}`}function nt(e,t,n){return new Promise((r,o)=>{let s=xd("docker",["compose","-p",$d(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 _("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 _("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Ed(e){let t=await nt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new _("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 Rs(e){let t=await Ed(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return f({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(`${I([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
589
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Os(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await nt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new _("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return f({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 Ns(e){let t=await nt(e,["stop"],!0);if(t.code!==0)throw new _("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return f({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Ds(e){f({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${I([["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}`]])}
590
+ `)})}pt();import{cpSync as Cs,existsSync as kt,readdirSync as jd,readFileSync as Ms,rmSync as Ls,statSync as Id,writeFileSync as Us}from"node:fs";import{join as me,resolve as un}from"node:path";var Pd=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Ad(e){return Pd.test(e)}function qe(e){if(!Ad(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 Td=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Rd(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!==".."&&Td.test(n))}var fr=2*1024*1024,js=200;function Is(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>js)throw new Error(`a function may hold ${js} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Rd(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=Od(o);if(s.length>fr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${fr/1024} KB. A function is source code; put large files in Storage.`);return o}function Od(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 Nd=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Dd=["SNOUTDATA_","SNOUT_"];function _t(e){if(!Nd.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Dd.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 Rh=64*1024;ne();se();W();function Fs(e){let t=me(e.folder,"functions");return kt(t)?jd(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Id(me(t,n)).isDirectory()).sort():[]}async function mn(e){d(w(" docker compose run --rm functions-deploy"));let t=await nt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new _("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Bs(e,t,n){let r=me(e.folder,".env"),o=Ms(r,"utf8"),s=(zt(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 Us(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
591
591
  `)}${l}
592
- `),!0}async function gr(e,t,n){qe(t);let r=un(n.dir??me(process.cwd(),"functions",t));if(!kt(me(r,"index.ts")))throw new g(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=me(e.folder,"functions",t);if(un(o)!==r){Ls(o,{recursive:!0,force:!0}),Cs(r,o,{recursive:!0});let a=me(r,"..","_shared");kt(a)&&un(a)!==un(me(e.folder,"functions","_shared"))&&Cs(a,me(e.folder,"functions","_shared"),{recursive:!0})}let s=Bs(e,t,!n.verifyJwt);if(await mn(e),s){let a=await nt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new _("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 Hs(e,t,n){let r=await gr(e,t,n);f(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 pn(e){return{ref:e.ref,local:e.folder,functions:Fs(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function Gs(e){let t=Fs(e);f(pn(e),()=>{if(t.length===0){d(`No functions in ${me(e.folder,"functions")}.`);return}process.stdout.write(`${j([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
592
+ `),!0}async function gr(e,t,n){qe(t);let r=un(n.dir??me(process.cwd(),"functions",t));if(!kt(me(r,"index.ts")))throw new g(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=me(e.folder,"functions",t);if(un(o)!==r){Ls(o,{recursive:!0,force:!0}),Cs(r,o,{recursive:!0});let a=me(r,"..","_shared");kt(a)&&un(a)!==un(me(e.folder,"functions","_shared"))&&Cs(a,me(e.folder,"functions","_shared"),{recursive:!0})}let s=Bs(e,t,!n.verifyJwt);if(await mn(e),s){let a=await nt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new _("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 Hs(e,t,n){let r=await gr(e,t,n);f(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 pn(e){return{ref:e.ref,local:e.folder,functions:Fs(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function Gs(e){let t=Fs(e);f(pn(e),()=>{if(t.length===0){d(`No functions in ${me(e.folder,"functions")}.`);return}process.stdout.write(`${I([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
593
593
  `)})}async function hr(e,t){qe(t);let n=me(e.folder,"functions",t);if(!kt(n))throw new _("not-found",`this local project has no function called ${t}`);return Ls(n,{recursive:!0,force:!0}),Bs(e,t,!1),await mn(e),{ref:e.ref,name:t,removed:!0}}async function qs(e,t){f(await hr(e,t),()=>d(`${t} is gone.`))}function fn(e){return me(e.folder,"functions",".env")}function yr(e){let t=fn(e);return kt(t)?zt(Ms(t,"utf8")):new Map}function Js(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Us(fn(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
594
594
  `)+(t.size?`
595
595
  `:""))}async function Ks(e,t){for(let r of t){let o=_t(r.name);if(o)throw new g(o)}let n=yr(e);for(let r of t)n.set(r.name,r.value);Js(e,n),await mn(e),f({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${v(r.name)} set in ${fn(e)}`)})}function gn(e){return{ref:e.ref,local:e.folder,secrets:[...yr(e).keys()].sort().map(t=>({name:t}))}}function Ws(e){let t=gn(e),n=t.secrets.map(r=>r.name);f(t,()=>{d(n.length?n.join(`
596
- `):`No function secrets in ${fn(e)}.`)})}async function zs(e,t){let n=yr(e);if(!n.delete(t))throw new _("not-found",`${t} is not set for this local project`);Js(e,n),await mn(e),f({ref:e.ref,unset:t},()=>d(`${t} unset.`))}ne();He();Re();ar();Qt();W();ln();V();V();ne();Re();W();async function Vs(){let{projects:e,allowance:t}=await ue();f({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${j(Cd(e))}
596
+ `):`No function secrets in ${fn(e)}.`)})}async function zs(e,t){let n=yr(e);if(!n.delete(t))throw new _("not-found",`${t} is not set for this local project`);Js(e,n),await mn(e),f({ref:e.ref,unset:t},()=>d(`${t} unset.`))}ne();He();Re();ar();Qt();W();ln();V();V();ne();Re();W();async function Vs(){let{projects:e,allowance:t}=await ue();f({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${I(Cd(e))}
597
597
  `)})}function Cd(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):"-"]:[],$e(n.lastConnectionAt)])]}async function wr(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 St(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&Ye(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;f({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}
598
598
  `),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 Ys(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){f({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await bt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new _("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});f({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Xs(e){let t=await b("cloud-project-reset-password",{ref:e});f(t,()=>{process.stdout.write(`${t.password}
599
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Zs(){let{teams:e}=await dn();f({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${j([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
599
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Zs(){let{teams:e}=await dn();f({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${I([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
600
600
  `)})}async function Qs(e){let{teams:t}=await dn(),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}V();W();async function ea(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});f(t,()=>{process.stdout.write(`${t.token}
601
- `),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 ta(){let{tokens:e}=await b("cloud-token-list");f({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${j([["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",$e(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
601
+ `),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 ta(){let{tokens:e}=await b("cloud-token-list");f({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${I([["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",$e(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
602
602
  `)})}async function na(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);f(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}V();se();W();async function ra(e){let t=await ae(e);f({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(`${sa(t.anonKey,t.serviceRoleKey)}
603
603
  `),Ud(t.keysIssuedAt)})}async function oa(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 g("keys rotate needs --force, because this cannot be undone");let n=await b("cloud-project-rotate-jwt",{ref:e});f(n,()=>{process.stdout.write(`${sa(n.anonKey,n.serviceRoleKey)}
604
- `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function sa(e,t){return j([["KEY","VALUE"],["anon",e],["service_role",t]])}function Ud(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)}`))}V();se();W();import{readdir as Bd,readFile as Hd,stat as Gd}from"node:fs/promises";import{join as Sr,relative as qd,sep as Jd}from"node:path";var aa=["functions"];async function br(e){try{return(await Gd(e)).isDirectory()}catch{return!1}}async function Kd(e,t){if(t){if(!await br(t))throw new g(`${t} is not a directory`);return t}for(let n of aa){let r=Sr(n,e);if(await br(r))return r}throw new g(`no folder found for ${e} at ${aa[0]}/${e}; use --dir to say where it is.`)}async function ia(e){let t=[];async function n(r){for(let o of await Bd(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Sr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Hd(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new g(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:qd(e,s).split(Jd).join("/"),text:l})}}return await n(e),t}async function Wd(e,t,n){let r=await ia(t);if(r.length===0)throw new g(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new g(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Sr(t,"..","_shared");if(!await br(s))return{entrypoint:o,files:r};let a=(await ia(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 _r(e,t,n){qe(t);let r=await Kd(t,n.dir),{entrypoint:o,files:s}=await Wd(t,r,n.entrypoint),a=js({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 ca(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await _r(e,t,n),l=r.functions.find(i=>i.name===t);f(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 kr(e){return b("cloud-project-functions",{ref:e})}function vr(e,t){return qe(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function xr(e,t,n){if(qe(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new g("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 zd(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 la(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 da(e,t,n){let r=await xr(e,t,n),o=r.functions.find(s=>s.name===t);f(r,()=>{let s=o?zd(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=la(r.limit);a&&d(w(` ${a}`))})}async function ua(e){let t=await kr(e);f(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${j([["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])])}
605
- `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=la(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}async function ma(e,t){let n=await vr(e,t);f(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}V();se();W();function pa(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function fa(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function ga(e,t){if(e.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await fa()}]}return e.map(n=>{let r=pa(n);if(!r)throw new g(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function ha(e,t,n){if(t.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await fa()})}else for(let s of t){let a=pa(s);if(!a)throw new g(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=_t(s.name);if(a)throw new g(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});f(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 ya(e,t){let n=_t(t);if(n)throw new g(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});f(r,()=>{d(`${t} is gone.`)})}async function wa(e){let t=await b("cloud-project-secrets",{ref:e});f(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${j([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,$e(n.updatedAt)])])}
604
+ `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function sa(e,t){return I([["KEY","VALUE"],["anon",e],["service_role",t]])}function Ud(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)}`))}V();se();W();import{readdir as Bd,readFile as Hd,stat as Gd}from"node:fs/promises";import{join as Sr,relative as qd,sep as Jd}from"node:path";var aa=["functions"];async function br(e){try{return(await Gd(e)).isDirectory()}catch{return!1}}async function Kd(e,t){if(t){if(!await br(t))throw new g(`${t} is not a directory`);return t}for(let n of aa){let r=Sr(n,e);if(await br(r))return r}throw new g(`no folder found for ${e} at ${aa[0]}/${e}; use --dir to say where it is.`)}async function ia(e){let t=[];async function n(r){for(let o of await Bd(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Sr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Hd(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new g(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:qd(e,s).split(Jd).join("/"),text:l})}}return await n(e),t}async function Wd(e,t,n){let r=await ia(t);if(r.length===0)throw new g(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new g(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Sr(t,"..","_shared");if(!await br(s))return{entrypoint:o,files:r};let a=(await ia(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 _r(e,t,n){qe(t);let r=await Kd(t,n.dir),{entrypoint:o,files:s}=await Wd(t,r,n.entrypoint),a=Is({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 ca(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await _r(e,t,n),l=r.functions.find(i=>i.name===t);f(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 kr(e){return b("cloud-project-functions",{ref:e})}function vr(e,t){return qe(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function xr(e,t,n){if(qe(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new g("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 zd(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 la(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 da(e,t,n){let r=await xr(e,t,n),o=r.functions.find(s=>s.name===t);f(r,()=>{let s=o?zd(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=la(r.limit);a&&d(w(` ${a}`))})}async function ua(e){let t=await kr(e);f(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${I([["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])])}
605
+ `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=la(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}async function ma(e,t){let n=await vr(e,t);f(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}V();se();W();function pa(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function fa(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function ga(e,t){if(e.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await fa()}]}return e.map(n=>{let r=pa(n);if(!r)throw new g(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function ha(e,t,n){if(t.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await fa()})}else for(let s of t){let a=pa(s);if(!a)throw new g(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=_t(s.name);if(a)throw new g(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});f(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 ya(e,t){let n=_t(t);if(n)throw new g(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});f(r,()=>{d(`${t} is gone.`)})}async function wa(e){let t=await b("cloud-project-secrets",{ref:e});f(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${I([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,$e(n.updatedAt)])])}
606
606
  `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Rt}from"node:child_process";import{appendFileSync as Fa,existsSync as kn,readFileSync as Ba,statSync as Cu,unlinkSync as Mu,writeFileSync as ot}from"node:fs";import{dirname as Lu,join as vn}from"node:path";import{tmpdir as Uu}from"node:os";import{open as Ha}from"node:fs/promises";function ba(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 Sa(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 _a(e){let t=e.split(`
607
607
  `).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 $r={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Xd=/^(snoutpod_admin|snout_[a-z]+_admin)$/,hn=new Set(["auth","storage","push","realtime","snout_realtime","_realtime","graphql","graphql_public","extensions","net","cron","pgbouncer","vault","pgsodium"]),Zd=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Qd={auth:"auth",storage:"storage",push:"push"},eu=new Set(["push.settings"]),tu=["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 nu(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=tu.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 ru(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 ka(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
608
- `)){let u=nu(i);if(!u)continue;let{type:c,schema:m,name:h,owner:$}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let K=/^(schema_migrations|migrations)$/.test(h)&&hn.has(m);if(/realtime/.test(m)||K||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 p=Qd[m];if(p){p==="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(p,[...n.get(p)??[],u]);continue}if(hn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?hn.has(h):hn.has(m))||Zd.test(h)||Xd.test($)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=ru(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"&&eu.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:ou(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function ou(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}V();se();W();V();ne();var Pr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],xa=["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 xt(e){return e==="done"||e==="failed"||e==="cancelled"}function $t(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new _("usage","shards takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new vt(lu(e.project))}let n=e.admin??va(t.LEPIS_ADMIN_URL);if(!n)throw new _("usage","shards needs a cluster: --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??va(t.LEPIS_ADMIN_TOKEN);if(!r)throw new _("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new Er(su(n),r)}function va(e){return e===void 0||e===""?void 0:e}function su(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new _("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new _("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new _("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!au(n.hostname))throw new _("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 au(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var iu=6e4,Er=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(iu)})}catch(i){let u=Vt(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new _(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 _("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw $a(s.status,l);return l}},cu={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function $a(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?cu[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":Be(e));return s==="not-signed-in"?new _(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new _(s,r,o)}function lu(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new _("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var du={"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"},uu="cloud-project-shards",vt=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:Ar(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}:{}})}#n(t){let n=du[t.op];if(!n)throw new _("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 _("usage","in SnoutData Cloud a node is a pod the project makes: nodes add takes no name or --host. Attach it once it is ready: shards nodes attach <ref>");if(t.op==="scale"&&(r.add!==void 0||r.remove!==void 0))throw new _("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(uu,{ref:this.ref,...t})}catch(n){throw mu(n)}}};function Ar(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"";return`New node pod${t.length===1?"":"s"} ${t.join(", ")}, restored from the home node's backup. Attach each once it is ready: snoutdata shards nodes attach <ref>.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`:e.unchanged===!0?`The project already has ${String(e.nodes)} nodes. Nothing changed.`:e.sharded===!0?e.changed===!1?"Sharding is already on.":String(e.note??"Sharding is on."):"Done."}function mu(e){if(!(e instanceof de))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return $a(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":Be(e.status);return new _(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Pt(e){if(Array.isArray(e))return`[${e.map(Pt).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)}:${Pt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Or=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}
608
+ `)){let u=nu(i);if(!u)continue;let{type:c,schema:m,name:h,owner:$}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let K=/^(schema_migrations|migrations)$/.test(h)&&hn.has(m);if(/realtime/.test(m)||K||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 p=Qd[m];if(p){p==="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(p,[...n.get(p)??[],u]);continue}if(hn.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?hn.has(h):hn.has(m))||Zd.test(h)||Xd.test($)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=ru(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"&&eu.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:ou(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function ou(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}V();se();W();V();ne();var Pr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],xa=["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 xt(e){return e==="done"||e==="failed"||e==="cancelled"}function $t(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new _("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new vt(lu(e.project))}let n=e.admin??va(t.LEPIS_ADMIN_URL);if(!n)throw new _("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??va(t.LEPIS_ADMIN_TOKEN);if(!r)throw new _("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new Er(su(n),r)}function va(e){return e===void 0||e===""?void 0:e}function su(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new _("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new _("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new _("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!au(n.hostname))throw new _("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 au(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var iu=6e4,Er=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(iu)})}catch(i){let u=Vt(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new _(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 _("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw $a(s.status,l);return l}},cu={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function $a(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?cu[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":Be(e));return s==="not-signed-in"?new _(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new _(s,r,o)}function lu(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new _("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var du={"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"},uu="cloud-project-shards",vt=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:Ar(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}:{}})}#n(t){let n=du[t.op];if(!n)throw new _("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 _("usage","in SnoutData Cloud a node is a pod the project makes: nodes add takes no name or --host. Attach it once it is ready: cluster nodes attach <ref>");if(t.op==="scale"&&(r.add!==void 0||r.remove!==void 0))throw new _("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(uu,{ref:this.ref,...t})}catch(n){throw mu(n)}}};function Ar(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"";return`New node pod${t.length===1?"":"s"} ${t.join(", ")}, restored from the home node's backup. Attach each once it is ready: snoutdata cluster nodes attach <ref>.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`: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."):"Done."}function mu(e){if(!(e instanceof de))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return $a(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":Be(e.status);return new _(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Pt(e){if(Array.isArray(e))return`[${e.map(Pt).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)}:${Pt(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var Or=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}
609
609
  ${Pt(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
610
610
  ${Pt(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
611
- ${Pt(t)}`)}},pu=new Or,Nr=["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"],fu="2024-11-05";function L(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var C={type:"string"},Ea={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. shards_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:[...Nr]}},required:["op"],additionalProperties:!0},yn={type:"integer",description:"The job id shards_run returned, or one from shards_jobs."},je={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)."},Tt=[{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:L({})},{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:L({})},{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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({})},{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:L({})},{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:L({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:L({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:L({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:L({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:L({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:L({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:L({ref:C},["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:L({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:L({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:L({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:L({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:L({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:L({ref:C,hostname:C},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:L({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:L({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:L({ref:C,at:C,name:C},["ref","at"])},{name:"shards_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 sharded, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:L({project:je})},{name:"shards_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 shards_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:L({project:je,sampleMs:{type:"integer"}})},{name:"shards_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 shards_run run it.",inputSchema:L({project:je,operation:Ea},["operation"])},{name:"shards_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 (shards_plan, shards_advice, or shards_run without confirm) in the last 15 minutes. Returns the job id; follow it with shards_watch_job.",inputSchema:L({project:je,operation:Ea,confirm:{type:"boolean"}},["operation"])},{name:"shards_jobs",description:"The cluster's latest jobs, or with id one job with each step's state and phase. Costs nothing.",inputSchema:L({project:je,id:yn})},{name:"shards_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; shards_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:L({project:je,id:yn,timeoutSeconds:{type:"integer"}},["id"])},{name:"shards_cancel_job",description:"Stop a running job. A move cancelled before its cutover rolls back; one cancelled after it finishes its cleanup.",inputSchema:L({project:je,id:yn},["id"])},{name:"shards_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 shards_watch_job.",inputSchema:L({project:je,id:yn},["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:L({ref:C},["ref"])}],gu=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","shards_status","shards_advice","shards_plan","shards_jobs","shards_watch_job"]),hu=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","shards_run"]),Pa=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Dr(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Dr)};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 Ir(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 yu(e){return{...e,annotations:gu.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:hu.has(e.name)}}}function wu(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function At(e,t){return{jsonrpc:"2.0",id:e,result:t}}function wn(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 At(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function bu(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Et(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 Tr(e){return{id:e.id,op:e.op,state:e.state,finished:xt(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:`shards_resume_job {"id": ${e.id}} runs it again from the failed step.`}:{}}}function Aa(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>"}. shards_advice returns them ready-made.`;let r=n;if(typeof r.op!="string"||!Nr.includes(r.op))return`${t}: operation.op must be one of ${Nr.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 shards_status or shards_advice wrote it.`}return r}function Rr(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 Su(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var _u="The shards 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 ku(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,_u,!0);s=r.shards(a)}catch(u){return k(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??pu,i=a??"";try{switch(t){case"shards_status":return k(e,await s.status());case"shards_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:Et(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with shards_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 shards_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"shards_plan":{let u=Aa(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:Et(c),next:"Nothing has run. To run it, show the user this plan, then call shards_run with the same operation and confirm: true."})}case"shards_run":{let u=Aa(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:Et(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 shards_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call shards_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:Et(c.plan),next:`Follow it with shards_watch_job {"id": ${c.job}}.`})}case"shards_jobs":{if(n.id===void 0)return k(e,{jobs:await s.jobs()});let u=Rr(n);if(u===null)return k(e,"shards_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return k(e,{...Tr(c),plan:c.plan&&Array.isArray(c.plan.steps)?Et(c.plan):null})}case"shards_watch_job":{let u=Rr(n);if(u===null)return k(e,"shards_watch_job needs id, the job number shards_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(xt(h.state))return k(e,Tr(h),h.state!=="done");if(Date.now()>=m)return k(e,{...Tr(h),note:`Still ${h.state} after ${c}s. It carries on; call shards_watch_job again to keep waiting.`});await new Promise($=>setTimeout($,o.shardsPollMs??1e3))}}case"shards_cancel_job":case"shards_resume_job":{let u=Rr(n);if(u===null)return k(e,`${t} needs id, a job number.`,!0);let c=t==="shards_cancel_job"?await s.cancel(u):await s.resume(u);return k(e,{...c,next:`Follow it with shards_watch_job {"id": ${u}}.`})}default:return wn(e,-32602,`unknown tool: ${t}`)}}catch(u){return k(e,Su(u),!0)}}function Y(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ta(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 At(r,{protocolVersion:fu,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return At(r,{});case"tools/list":return At(r,{tools:wu([...Tt.map(yu),...n.borrowed??[]])});case"tools/call":return vu(r,s,t,n);default:return wn(r,-32601,`unknown method: ${o}`)}}async function vu(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Tt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return wn(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("shards_")&&Tt.some(c=>c.name===o))return ku(e,o,s,n,r);let i=Y(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&&!Pa.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: ${Pa.join(", ")}.`,!0);try{switch(o){case"whoami":return k(e,await n.whoami());case"list_projects":return k(e,Dr(await n.listProjects()));case"create_project":{let c=Y(s,"name");return c?k(e,await n.createProject({name:c,region:Y(s,"region")??void 0,teamId:Y(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,Dr(await n.getProject(i)));case"list_products":return k(e,await n.getProducts(i));case"set_product":{let c=Y(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=Y(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?Ir(i,c.restore):null;return k(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=Y(s,"at");return c?k(e,await n.restoreTo(i,c,Y(s,"name")??void 0)):k(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=Y(s,"ref"),m=Y(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:Y(s,"dir")??void 0,entrypoint:Y(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=Y(s,"ref");return c?k(e,await n.listFunctions(c)):k(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=Y(s,"ref"),m=Y(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,$=typeof s.concurrency=="number"?s.concurrency:void 0,B=s.reset===!0;return!B&&h===void 0&&$===void 0?k(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):k(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:$,reset:B}))}case"delete_function":{let c=Y(s,"ref"),m=Y(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=Y(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=Y(s,"name");if(!c)return k(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=Y(s,"expires"),h=m?bu(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,Y(s,"project")??void 0))}case"revoke_token":{let c=Y(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:Y(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?At(e,await n.callBorrowed(o,s)):k(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):wn(e,-32602,`unknown tool: ${o}`)}}catch(c){return k(e,c instanceof Error?c.message:String(c),!0)}}function Ra(e){let t=e.split(`
612
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var bn=["auth","storage","data-api","push"];function Je(e){return{error:e instanceof Error?e.message:String(e)}}async function rt(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}),Je),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}),Je),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Je),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Je)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:xu}}var xu={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Oa(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!bn.includes(t))throw new g(`say which product: ${bn.join(", ")}`);return t}async function jr(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 Na(e){let t=await rt(e);f(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(`${j([["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")])}
613
- `);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 Da(e,t,n){let r=await jr(e,t,n);f(r,()=>d(r.note))}async function Sn(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Cr(e,t,n){if(!n)throw new g(`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 $u(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(`${j(n)}
614
- `)}async function Ia(e,t,n){if(t===void 0||t==="list"){let o=await Sn(e);f(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${j([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":v("no"),s.certificate?.state??""])])}
615
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new g(`unknown command: domains ${t}`);let r=await Cr(e,t,n??"");f(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||$u(r.domain,e)),r.next&&d(r.next)})}async function Mr(e){return b("cloud-project-restore",{ref:e},"GET")}async function Lr(e,t,n){if(Number.isNaN(Date.parse(t)))throw new g(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function ja(e){let t=await Mr(e);f(t,()=>{let n=t.restore,r=Ir(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 Ca(e,t,n){let r=await Lr(e,t,n);f(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 Ur(e){let{projects:t}=await ue(),n=t.find(l=>l.ref===e);if(!n)throw new g(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([rt(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Je),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Je),Sn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Je)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ma(e){let t=await Ur(e);f(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(`${j([["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)]])}
616
- `)})}V();ne();He();V();W();import{existsSync as Eu,mkdirSync as Pu,readFileSync as Au,renameSync as Tu,writeFileSync as Ru}from"node:fs";import{homedir as Fr}from"node:os";import{dirname as Ou,join as Nu}from"node:path";import{rootCertificates as Du}from"node:tls";var Iu="SNOUTDATA_DB_SSLMODE";function ju(e=Fr()){let t=Nu(e,".snoutdata","ca-roots.pem"),n=`${Du.join(`
611
+ ${Pt(t)}`)}},pu=new Or,Nr=["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"],fu="2024-11-05";function L(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var C={type:"string"},Ea={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:[...Nr]}},required:["op"],additionalProperties:!0},yn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},Ie={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)."},Tt=[{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:L({})},{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:L({})},{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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({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:L({})},{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:L({})},{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:L({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:L({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:L({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:L({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:L({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:L({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:L({ref:C},["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:L({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:L({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:L({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:L({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:L({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:L({ref:C,hostname:C},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:L({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:L({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:L({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:L({project:Ie})},{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:L({project:Ie,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:L({project:Ie,operation:Ea},["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:L({project:Ie,operation:Ea,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:L({project:Ie,id:yn})},{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:L({project:Ie,id:yn,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:L({project:Ie,id:yn},["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:L({project:Ie,id:yn},["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:L({ref:C},["ref"])}],gu=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"]),hu=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Pa=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Dr(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Dr)};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 jr(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 yu(e){return{...e,annotations:gu.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:hu.has(e.name)}}}function wu(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function At(e,t){return{jsonrpc:"2.0",id:e,result:t}}function wn(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 At(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function bu(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Et(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 Tr(e){return{id:e.id,op:e.op,state:e.state,finished:xt(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 Aa(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"||!Nr.includes(r.op))return`${t}: operation.op must be one of ${Nr.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 Rr(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 Su(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var _u="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 ku(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,_u,!0);s=r.shards(a)}catch(u){return k(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??pu,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:Et(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=Aa(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:Et(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=Aa(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:Et(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:Et(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=Rr(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,{...Tr(c),plan:c.plan&&Array.isArray(c.plan.steps)?Et(c.plan):null})}case"cluster_watch_job":{let u=Rr(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(xt(h.state))return k(e,Tr(h),h.state!=="done");if(Date.now()>=m)return k(e,{...Tr(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise($=>setTimeout($,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=Rr(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 wn(e,-32602,`unknown tool: ${t}`)}}catch(u){return k(e,Su(u),!0)}}function Y(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ta(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 At(r,{protocolVersion:fu,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return At(r,{});case"tools/list":return At(r,{tools:wu([...Tt.map(yu),...n.borrowed??[]])});case"tools/call":return vu(r,s,t,n);default:return wn(r,-32601,`unknown method: ${o}`)}}async function vu(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Tt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return wn(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_")&&Tt.some(c=>c.name===o))return ku(e,o,s,n,r);let i=Y(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&&!Pa.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: ${Pa.join(", ")}.`,!0);try{switch(o){case"whoami":return k(e,await n.whoami());case"list_projects":return k(e,Dr(await n.listProjects()));case"create_project":{let c=Y(s,"name");return c?k(e,await n.createProject({name:c,region:Y(s,"region")??void 0,teamId:Y(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,Dr(await n.getProject(i)));case"list_products":return k(e,await n.getProducts(i));case"set_product":{let c=Y(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=Y(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?jr(i,c.restore):null;return k(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=Y(s,"at");return c?k(e,await n.restoreTo(i,c,Y(s,"name")??void 0)):k(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=Y(s,"ref"),m=Y(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:Y(s,"dir")??void 0,entrypoint:Y(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=Y(s,"ref");return c?k(e,await n.listFunctions(c)):k(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=Y(s,"ref"),m=Y(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,$=typeof s.concurrency=="number"?s.concurrency:void 0,B=s.reset===!0;return!B&&h===void 0&&$===void 0?k(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):k(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:$,reset:B}))}case"delete_function":{let c=Y(s,"ref"),m=Y(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=Y(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=Y(s,"name");if(!c)return k(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=Y(s,"expires"),h=m?bu(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,Y(s,"project")??void 0))}case"revoke_token":{let c=Y(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:Y(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?At(e,await n.callBorrowed(o,s)):k(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):wn(e,-32602,`unknown tool: ${o}`)}}catch(c){return k(e,c instanceof Error?c.message:String(c),!0)}}function Ra(e){let t=e.split(`
612
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var bn=["auth","storage","data-api","push"];function Je(e){return{error:e instanceof Error?e.message:String(e)}}async function rt(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}),Je),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}),Je),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Je),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Je)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:xu}}var xu={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Oa(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!bn.includes(t))throw new g(`say which product: ${bn.join(", ")}`);return t}async function Ir(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 Na(e){let t=await rt(e);f(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(`${I([["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")])}
613
+ `);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 Da(e,t,n){let r=await Ir(e,t,n);f(r,()=>d(r.note))}async function Sn(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Cr(e,t,n){if(!n)throw new g(`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 $u(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(`${I(n)}
614
+ `)}async function ja(e,t,n){if(t===void 0||t==="list"){let o=await Sn(e);f(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${I([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":v("no"),s.certificate?.state??""])])}
615
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new g(`unknown command: domains ${t}`);let r=await Cr(e,t,n??"");f(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||$u(r.domain,e)),r.next&&d(r.next)})}async function Mr(e){return b("cloud-project-restore",{ref:e},"GET")}async function Lr(e,t,n){if(Number.isNaN(Date.parse(t)))throw new g(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Ia(e){let t=await Mr(e);f(t,()=>{let n=t.restore,r=jr(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 Ca(e,t,n){let r=await Lr(e,t,n);f(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 Ur(e){let{projects:t}=await ue(),n=t.find(l=>l.ref===e);if(!n)throw new g(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([rt(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Je),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Je),Sn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Je)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ma(e){let t=await Ur(e);f(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(`${I([["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)]])}
616
+ `)})}V();ne();He();V();W();import{existsSync as Eu,mkdirSync as Pu,readFileSync as Au,renameSync as Tu,writeFileSync as Ru}from"node:fs";import{homedir as Fr}from"node:os";import{dirname as Ou,join as Nu}from"node:path";import{rootCertificates as Du}from"node:tls";var ju="SNOUTDATA_DB_SSLMODE";function Iu(e=Fr()){let t=Nu(e,".snoutdata","ca-roots.pem"),n=`${Du.join(`
617
617
  `)}
618
- `,r=null;try{r=Eu(t)?Au(t,"utf8"):null}catch{r=null}if(r!==n){Pu(Ou(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Ru(o,n,{mode:420}),Tu(o,t)}return t}function Br(e,t=process.env,n=Fr()){if(e==="disable")return{mode:"disable"};let r=t[Iu]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||ju(n)}}function Ke(e,t=process.env,n=Fr()){let{mode:r,rootCert:o}=Br(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Ga(e){try{let{projects:t}=await ue(),n=t.find(r=>r.ref===e);n?.readOnly?z(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&z(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Gr(e){await Ga(e);let t=await ae(e);f(t,()=>{process.stdout.write(`${t.uri}
618
+ `,r=null;try{r=Eu(t)?Au(t,"utf8"):null}catch{r=null}if(r!==n){Pu(Ou(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Ru(o,n,{mode:420}),Tu(o,t)}return t}function Br(e,t=process.env,n=Fr()){if(e==="disable")return{mode:"disable"};let r=t[ju]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Iu(n)}}function Ke(e,t=process.env,n=Fr()){let{mode:r,rootCert:o}=Br(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Ga(e){try{let{projects:t}=await ue(),n=t.find(r=>r.ref===e);n?.readOnly?z(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&z(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Gr(e){await Ga(e);let t=await ae(e);f(t,()=>{process.stdout.write(`${t.uri}
619
619
  `)})}async function qa(e,t){if(t.length===0&&!le()){let o=Qe();D("usage",`db psql with no statements opens an interactive session, and ${o?Ze(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Ga(e);let n=await ae(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Rt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...Ke(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new _("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new _("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Ja(e,t,n="DATABASE_URL"){let r=vn(e,".env"),o=`${n}=${t}`,s=Fu(e);if(!kn(r))return ot(r,`${o}
620
620
  `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Cu(r).mode&63&&z(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Ba(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(ot(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Fa(r,a.endsWith(`
621
621
  `)?`${o}
@@ -638,15 +638,15 @@ ${i.map(h=>`delete from ${h};`).join(`
638
638
  `)}
639
639
  `,"utf8");let c=await _n(e,"pg_restore",t,["--data-only",`--role=${$r[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}ot(`${r}.rest`,`${n.rest.join(`
640
640
  `)}
641
- `,"utf8");let a=await _n(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{Mu(`${r}${a}`)}catch{}}}function _n(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=Rt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ke(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{z(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}))})}V();se();W();function zu(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new g(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new g("db access list takes no argument");if(t!==void 0)throw new g("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new g("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new g(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new g("db access revoke needs an email address or a role name");if(t!==void 0)throw new g("--level is for db access grant");return{action:"revoke",who:r};default:throw new g(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function za(e,t,n){let r=zu(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});f(s,()=>Vu(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});f(s,()=>Yu(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});f(o,()=>Zu(o))}function Vu(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(`${j([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?v(n.email):n.email,n.level,n.role])])}
641
+ `,"utf8");let a=await _n(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{Mu(`${r}${a}`)}catch{}}}function _n(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=Rt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...Ke(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{z(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}))})}V();se();W();function zu(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new g(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new g("db access list takes no argument");if(t!==void 0)throw new g("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new g("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new g(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new g("db access revoke needs an email address or a role name");if(t!==void 0)throw new g("--level is for db access grant");return{action:"revoke",who:r};default:throw new g(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function za(e,t,n){let r=zu(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});f(s,()=>Vu(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});f(s,()=>Yu(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});f(o,()=>Zu(o))}function Vu(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(`${I([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?v(n.email):n.email,n.level,n.role])])}
642
642
  `);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 Yu(e){e.connection&&process.stdout.write(`${e.connection.keywords}
643
- `),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 Xu(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Zu(e){let t=Xu(e);for(let n of t.slice(0,-1))d(n);z(t[t.length-1])}V();se();W();var Qu=6e4;async function qr(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function em(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new g(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function tm(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Va(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 nm={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 Ya(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[tm(e.at),nm[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function rm(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function om(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,Va(Date.now()-a.joined_at),Va(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>Qu?" (silent: likely gone)":"")]);d(j([["SOCKET","PRESENCE KEY","JOINED","LAST HEARD"],...s]).split(`
643
+ `),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 Xu(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Zu(e){let t=Xu(e);for(let n of t.slice(0,-1))d(n);z(t[t.length-1])}V();se();W();var Qu=6e4;async function qr(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function em(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new g(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function tm(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Va(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 nm={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 Ya(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[tm(e.at),nm[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function rm(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function om(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,Va(Date.now()-a.joined_at),Va(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>Qu?" (silent: likely gone)":"")]);d(I([["SOCKET","PRESENCE KEY","JOINED","LAST HEARD"],...s]).split(`
644
644
  `).map(a=>` ${a}`).join(`
645
645
  `))}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} ${rm(a)}`)}}}async function Xa(e,t={}){if(t.watch&&ws())throw new g("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await qr(e,{channel:t.channel});if(f(n.inspect,()=>om(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 qr(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Ya(a)}
646
646
  `);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"}
647
647
  `))}}}async function Za(e,t={}){let n=t.since?em(t.since):0,r=await qr(e,{channel:t.channel,since:n});return f({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(`${Ya(o)}
648
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}V();se();W();import{readFile as am}from"node:fs/promises";var im="api.snoutdata.com";function ti(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}.${im}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function cm(e){return ti(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function xn(e,t){return ti(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Jr(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(`${j([["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"]])}
649
- `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function lm(){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 ni(e,t,n,r){if(t===void 0||t==="show"){let o=await cm(e);f(o,()=>Jr(o));return}if(t==="google"){if(n[0]==="off"){let a=await xn(e,{googleEnabled:!1});f(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new g('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await lm();if(!a)throw new g("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await xn(e,o);f(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Jr(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new g('auth anonymous takes "on" or "off"');let s=await xn(e,{anonymousEnabled:o==="on"});f(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 g("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await xn(e,o);f(s,()=>Jr(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});f(o,()=>ei(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Qa.includes(o))throw new g(`auth template needs a kind: ${Qa.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new g(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await am(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});f(a,()=>{ei(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new g(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Qa=["confirmation","recovery","magic_link","invite","email_change"];function ei(e){process.stdout.write(`${j([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?v("yours"):"ours",t.subject])])}
648
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}V();se();W();import{readFile as am}from"node:fs/promises";var im="api.snoutdata.com";function ti(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}.${im}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function cm(e){return ti(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function xn(e,t){return ti(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Jr(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(`${I([["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"]])}
649
+ `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function lm(){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 ni(e,t,n,r){if(t===void 0||t==="show"){let o=await cm(e);f(o,()=>Jr(o));return}if(t==="google"){if(n[0]==="off"){let a=await xn(e,{googleEnabled:!1});f(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new g('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await lm();if(!a)throw new g("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await xn(e,o);f(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):Jr(s),d(w(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new g('auth anonymous takes "on" or "off"');let s=await xn(e,{anonymousEnabled:o==="on"});f(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 g("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await xn(e,o);f(s,()=>Jr(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});f(o,()=>ei(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Qa.includes(o))throw new g(`auth template needs a kind: ${Qa.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new g(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await am(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});f(a,()=>{ei(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new g(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Qa=["confirmation","recovery","magic_link","invite","email_change"];function ei(e){process.stdout.write(`${I([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?v("yours"):"ours",t.subject])])}
650
650
  `)}V();W();ne();import{spawn as um}from"node:child_process";import{createHash as mm}from"node:crypto";import{existsSync as pm,readdirSync as fm,readFileSync as gm,statSync as hm}from"node:fs";import{isAbsolute as ym,join as Kr,relative as wm,resolve as bm}from"node:path";function dm(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function $n(e,t,n={}){let r=dm(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 Ot(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function oi(e){let t=wm(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Nt="_snoutdata_migrations",Sm=`create table if not exists ${Nt} (
651
651
  name text primary key,
652
652
  checksum text not null,
@@ -661,8 +661,8 @@ ${B}
661
661
  commit;
662
662
  `:`${$.sql}
663
663
  ${B}
664
- `,y=await Wr(l,p);if(y.code!==0)return K&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:$.name,error:y.err.trim(),notes:o};h.push($.name),s(` applied ${$.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}V();se();W();import{readFile as ai}from"node:fs/promises";function Vr(e){if(e==="apns"||e==="fcm")return e;throw new g("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function vm(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function ii(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new g("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new g("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await ai(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function ci(e){if(!e)throw new g("push credentials set fcm needs --file service-account.json");return{service_account:await ai(e,"utf8")}}async function xm(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 $m(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function li(e){let t=await vm(e);f(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${j([["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}`:""]])}
665
- `),(!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 di(e,t,n){let r=await xm(e,t,n);f(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 ui(e,t){let n=await $m(e,t);f(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}V();ne();W();import{spawn as Um}from"node:child_process";import{writeFileSync as Fm}from"node:fs";import{isAbsolute as Bm,resolve as Hm}from"node:path";var Pm=" ";function R(e){return Pm.repeat(e)}function re(e){return JSON.stringify(e)}function Se(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:re(e)}function We(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Am(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 Tm={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"},Rm={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Om(e,t){let n=Rm[e]?.[t];return n||(Tm[t]??"unknown")}function En(e,t){let{base:n,array:r}=Am(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Om(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Nm(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Yr(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${R(n)}${Se(t.name)}?: never`;let s=En(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${R(n)}${Se(t.name)}${r?"?":""}: ${a}`}function Dm(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 Im(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: ${re(o.constraintName)}`),n.push(`${R(t+2)}columns: [${o.columns.map(re).join(", ")}]`),n.push(`${R(t+2)}isOneToOne: ${Dm(e,o)?"true":"false"}`),n.push(`${R(t+2)}referencedRelation: ${re(o.referencedTable)}`),n.push(`${R(t+2)}referencedColumns: [${o.referencedColumns.map(re).join(", ")}]`),n.push(`${R(t+1)}},`);return n.push(`${R(t)}]`),n}function mi(e,t,n){let r=We(t.columns),o=[];o.push(`${R(n)}${Se(t.name)}: {`),o.push(`${R(n+1)}Row: {`);for(let s of r)o.push(Yr(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(Yr(e,s,n+2,Nm(s),!0));o.push(`${R(n+1)}}`),o.push(`${R(n+1)}Update: {`);for(let s of r)o.push(Yr(e,s,n+2,!0,!0));o.push(`${R(n+1)}}`)}return o.push(...Im(t,n+1)),o.push(`${R(n)}}`),o}function Dt(e,t){return[`${R(t)}${e}: {`,`${R(t+1)}[_ in never]: never`,`${R(t)}}`]}function jm(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}[]`:`${En(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??En(e,n)}function Cm(e,t,n){let r=[],o=We((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)}${Se(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)}${Se(s.name)}: ${En(e,s.dataType)}`);r.push(`${R(n+1)}}`)}return r.push(`${R(n+1)}Returns: ${jm(e,t)}`),r.push(`${R(n)}}`),r}function st(e,t){return e.schema&&e.schema.length>0?e.schema:t}function pi(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[${re(h)}][${re("Enums")}][${re(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=st(m,n),$=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${re(h)}][${re($)}][${re(m.name)}][${re("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(st(m,n));for(let m of e.routines??[])i.add(st(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=We(e.tables.filter(p=>st(p,n)===m&&p.kind!=="view")),$=We(e.tables.filter(p=>st(p,n)===m&&p.kind==="view")),B=We((e.routines??[]).filter(p=>st(p,n)===m)),K=We(o.filter(p=>(p.schema&&p.schema.length>0?p.schema:n)===m));if(c.push(`${R(1)}${Se(m)}: {`),h.length===0)c.push(...Dt("Tables",2));else{c.push(`${R(2)}Tables: {`);for(let p of h)c.push(...mi(l,p,3));c.push(`${R(2)}}`)}if($.length===0)c.push(...Dt("Views",2));else{c.push(`${R(2)}Views: {`);for(let p of $)c.push(...mi(l,p,3));c.push(`${R(2)}}`)}if(B.length===0)c.push(...Dt("Functions",2));else{c.push(`${R(2)}Functions: {`);for(let p of B)c.push(...Cm(l,p,3));c.push(`${R(2)}}`)}if(K.length===0)c.push(...Dt("Enums",2));else{c.push(`${R(2)}Enums: {`);for(let p of K)c.push(`${R(3)}${Se(p.name)}: ${p.values.map(re).join(" | ")}`);c.push(`${R(2)}}`)}c.push(...Dt("CompositeTypes",2)),c.push(`${R(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Lm(n))),t.constants!==!1&&(c.push(""),c.push(Mm(u,o,n))),`${c.join(`
664
+ `,y=await Wr(l,p);if(y.code!==0)return K&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:$.name,error:y.err.trim(),notes:o};h.push($.name),s(` applied ${$.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}V();se();W();import{readFile as ai}from"node:fs/promises";function Vr(e){if(e==="apns"||e==="fcm")return e;throw new g("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function vm(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function ii(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new g("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new g("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await ai(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function ci(e){if(!e)throw new g("push credentials set fcm needs --file service-account.json");return{service_account:await ai(e,"utf8")}}async function xm(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 $m(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function li(e){let t=await vm(e);f(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${I([["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}`:""]])}
665
+ `),(!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 di(e,t,n){let r=await xm(e,t,n);f(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 ui(e,t){let n=await $m(e,t);f(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}V();ne();W();import{spawn as Um}from"node:child_process";import{writeFileSync as Fm}from"node:fs";import{isAbsolute as Bm,resolve as Hm}from"node:path";var Pm=" ";function R(e){return Pm.repeat(e)}function re(e){return JSON.stringify(e)}function Se(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:re(e)}function We(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Am(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 Tm={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"},Rm={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Om(e,t){let n=Rm[e]?.[t];return n||(Tm[t]??"unknown")}function En(e,t){let{base:n,array:r}=Am(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Om(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Nm(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Yr(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${R(n)}${Se(t.name)}?: never`;let s=En(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${R(n)}${Se(t.name)}${r?"?":""}: ${a}`}function Dm(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: ${re(o.constraintName)}`),n.push(`${R(t+2)}columns: [${o.columns.map(re).join(", ")}]`),n.push(`${R(t+2)}isOneToOne: ${Dm(e,o)?"true":"false"}`),n.push(`${R(t+2)}referencedRelation: ${re(o.referencedTable)}`),n.push(`${R(t+2)}referencedColumns: [${o.referencedColumns.map(re).join(", ")}]`),n.push(`${R(t+1)}},`);return n.push(`${R(t)}]`),n}function mi(e,t,n){let r=We(t.columns),o=[];o.push(`${R(n)}${Se(t.name)}: {`),o.push(`${R(n+1)}Row: {`);for(let s of r)o.push(Yr(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(Yr(e,s,n+2,Nm(s),!0));o.push(`${R(n+1)}}`),o.push(`${R(n+1)}Update: {`);for(let s of r)o.push(Yr(e,s,n+2,!0,!0));o.push(`${R(n+1)}}`)}return o.push(...jm(t,n+1)),o.push(`${R(n)}}`),o}function Dt(e,t){return[`${R(t)}${e}: {`,`${R(t+1)}[_ in never]: never`,`${R(t)}}`]}function Im(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}[]`:`${En(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??En(e,n)}function Cm(e,t,n){let r=[],o=We((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)}${Se(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)}${Se(s.name)}: ${En(e,s.dataType)}`);r.push(`${R(n+1)}}`)}return r.push(`${R(n+1)}Returns: ${Im(e,t)}`),r.push(`${R(n)}}`),r}function st(e,t){return e.schema&&e.schema.length>0?e.schema:t}function pi(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[${re(h)}][${re("Enums")}][${re(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=st(m,n),$=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${re(h)}][${re($)}][${re(m.name)}][${re("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(st(m,n));for(let m of e.routines??[])i.add(st(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=We(e.tables.filter(p=>st(p,n)===m&&p.kind!=="view")),$=We(e.tables.filter(p=>st(p,n)===m&&p.kind==="view")),B=We((e.routines??[]).filter(p=>st(p,n)===m)),K=We(o.filter(p=>(p.schema&&p.schema.length>0?p.schema:n)===m));if(c.push(`${R(1)}${Se(m)}: {`),h.length===0)c.push(...Dt("Tables",2));else{c.push(`${R(2)}Tables: {`);for(let p of h)c.push(...mi(l,p,3));c.push(`${R(2)}}`)}if($.length===0)c.push(...Dt("Views",2));else{c.push(`${R(2)}Views: {`);for(let p of $)c.push(...mi(l,p,3));c.push(`${R(2)}}`)}if(B.length===0)c.push(...Dt("Functions",2));else{c.push(`${R(2)}Functions: {`);for(let p of B)c.push(...Cm(l,p,3));c.push(`${R(2)}}`)}if(K.length===0)c.push(...Dt("Enums",2));else{c.push(`${R(2)}Enums: {`);for(let p of K)c.push(`${R(3)}${Se(p.name)}: ${p.values.map(re).join(" | ")}`);c.push(`${R(2)}}`)}c.push(...Dt("CompositeTypes",2)),c.push(`${R(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(Lm(n))),t.constants!==!1&&(c.push(""),c.push(Mm(u,o,n))),`${c.join(`
666
666
  `)}
667
667
  `}function Mm(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=We(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${R(1)}${Se(o)}: {`),r.push(`${R(2)}Enums: {},`),r.push(`${R(1)}},`);continue}r.push(`${R(1)}${Se(o)}: {`),r.push(`${R(2)}Enums: {`);for(let a of s)r.push(`${R(3)}${Se(a.name)}: [${a.values.map(re).join(", ")}],`);r.push(`${R(2)}},`),r.push(`${R(1)}},`)}return r.push("} as const"),r.join(`
668
668
  `)}function Lm(e){return`type DatabaseWithoutInternals = Database
@@ -889,7 +889,7 @@ select json_build_object(
889
889
  where n.nspname = 'lepis' and c.relname = 'relation'
890
890
  )::text`,zm=`select coalesce(json_agg(json_build_object(
891
891
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
892
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function Vm(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:Ym(t)}function Ym(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 Xm(e,t){let n=await e(Wm);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(zm);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 Zm(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Pn(e.dbUrl):Gm(await ae(Qm(e))),r=!1,o=async B=>{if(e.sql)return e.sql(B);if(r&&e.ref)return fi(e.ref,B);let K=await Xr(n,B);return K.code===127&&!e.dbUrl&&e.ref?(r=!0,fi(e.ref,B)):K},s=qm.replace("$SCHEMAS$",Jm(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=Vm(l);i||D("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await Xm(o,t),{schema:c,enums:m}=Km(i,n.database,Date.now()),h=pi(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),$=null;return e.out&&($=Bm(e.out)?e.out:Hm(process.cwd(),e.out),Fm($,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(B=>B.kind!=="view").length,views:c.tables.filter(B=>B.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:$,distribution:u,types:h}}async function fi(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 Qm(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 gi(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 Zm(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}.`),f(r,()=>{r.path||process.stdout.write(r.types)}),0}ne();import{createHash as Pf}from"node:crypto";import{existsSync as Ao,mkdirSync as Af,readFileSync as To,readdirSync as Tf,statSync as Rf,writeFileSync as Of}from"node:fs";import{dirname as Nf,isAbsolute as nl,join as Ht,resolve as rl}from"node:path";import{pathToFileURL as Df}from"node:url";W();function If(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:nl(e)||e.startsWith("./")||e.startsWith("../")?Df(rl(e)).href:e}function jf(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(If(e)):Promise.resolve().then(()=>(Zc(),Xc))}function Cf(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 Qc="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 el(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Gt(){let e;try{e=await jf()}catch(t){D("tool-missing",Cf(t))}return{ready:async t=>{let n=await el(e,t);return typeof n=="string"?await e.podmanReady(Qc)??n:e.podmanReady(Qc,n.name)},ensureImage:async(t,n)=>{let r=await el(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 ol(e){return Ht(e,".snoutdata","local.json")}function Bn(e){let t=ol(e);if(!Ao(t))return null;try{let n=JSON.parse(To(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 Mf(e,t){let n=ol(e);return Af(Nf(n),{recursive:!0}),Of(n,`${JSON.stringify(t,null,2)}
892
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function Vm(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:Ym(t)}function Ym(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 Xm(e,t){let n=await e(Wm);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(zm);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 Zm(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Pn(e.dbUrl):Gm(await ae(Qm(e))),r=!1,o=async B=>{if(e.sql)return e.sql(B);if(r&&e.ref)return fi(e.ref,B);let K=await Xr(n,B);return K.code===127&&!e.dbUrl&&e.ref?(r=!0,fi(e.ref,B)):K},s=qm.replace("$SCHEMAS$",Jm(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=Vm(l);i||D("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await Xm(o,t),{schema:c,enums:m}=Km(i,n.database,Date.now()),h=pi(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),$=null;return e.out&&($=Bm(e.out)?e.out:Hm(process.cwd(),e.out),Fm($,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(B=>B.kind!=="view").length,views:c.tables.filter(B=>B.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:$,distribution:u,types:h}}async function fi(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 Qm(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 gi(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 Zm(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}.`),f(r,()=>{r.path||process.stdout.write(r.types)}),0}ne();import{createHash as Pf}from"node:crypto";import{existsSync as Ao,mkdirSync as Af,readFileSync as To,readdirSync as Tf,statSync as Rf,writeFileSync as Of}from"node:fs";import{dirname as Nf,isAbsolute as nl,join as Ht,resolve as rl}from"node:path";import{pathToFileURL as Df}from"node:url";W();function jf(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:nl(e)||e.startsWith("./")||e.startsWith("../")?Df(rl(e)).href:e}function If(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(jf(e)):Promise.resolve().then(()=>(Zc(),Xc))}function Cf(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 Qc="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 el(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function Gt(){let e;try{e=await If()}catch(t){D("tool-missing",Cf(t))}return{ready:async t=>{let n=await el(e,t);return typeof n=="string"?await e.podmanReady(Qc)??n:e.podmanReady(Qc,n.name)},ensureImage:async(t,n)=>{let r=await el(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 ol(e){return Ht(e,".snoutdata","local.json")}function Bn(e){let t=ol(e);if(!Ao(t))return null;try{let n=JSON.parse(To(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 Mf(e,t){let n=ol(e);return Af(Nf(n),{recursive:!0}),Of(n,`${JSON.stringify(t,null,2)}
893
893
  `,{encoding:"utf8",mode:384}),n}var Lf=54322;async function sl(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 al(e,t,n,r=Xr){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 Uf(e,t={}){let n=t.cwd??process.cwd(),r=Bn(n);await sl(e,r?.ref??null);let o=r??e.mint(t.port??Lf),s=r?null:Mf(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=al(e,o,Pn(l)),u=await Ff(i,n,t),c=await Bf(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function Ff(e,t,n){let r=rl(n.dir===void 0?Ht(t,"migrations"):nl(n.dir)?n.dir:Ht(t,n.dir));if(!Ao(r)||!Rf(r).isDirectory())return[];let o=Tf(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=To(Ht(r,m),"utf8");return{name:m,sql:h,checksum:Pf("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Hf);s.code!==0&&D(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${Bt}`);let a=await e(`select name || E'\\t' || checksum from ${Bt};`);a.code!==0&&D("failed",a.err.trim()||`could not read ${Bt}`);let l=a.out.split(`
894
894
  `).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=$n(o,l,{outOfOrder:n.outOfOrder});i.ok||D("conflict",`Refused. This folder and the local database do not agree:
895
895
  ${i.problems.map(m=>` ${m.reason}`).join(`
@@ -907,15 +907,15 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Bt="_snoutdata_migrations",Hf=`crea
907
907
  applied_at timestamptz not null default now()
908
908
  );`;function tl(e){return`'${e.replace(/'/g,"''")}'`}function Gf(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 il(e,t={}){let n=await Uf(e,t);return f(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}
909
909
  `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function cl(e,t={}){let n=t.cwd??process.cwd(),r=Bn(n);r||D("not-found","there is no local database for this folder. `snoutdata start` makes one."),await sl(e,r.ref);let o=await e.stop(r.ref);return f(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function ll(e,t={}){let n=t.cwd??process.cwd(),r=Bn(n);if(!r)return f({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 f({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return f({...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")} ${Gf(s.databaseBytes)}`),s.services.length>0&&d(` ${v("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
910
- `)}),0}async function dl(e,t=process.cwd()){let n=Bn(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:al(e,n,Pn(r))}}V();W();Qt();pt();Re();import{resolve as ul}from"node:path";function qf(){let e=n=>Ee(n),t;return{shards:n=>{if(n!==void 0)return $t({project:n});if(!process.env.LEPIS_ADMIN_URL)throw new Error("The shards 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??=$t({}),t},whoami:()=>Ge(),listProjects:async()=>({...await ue(),local:Jf()}),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 St(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=>tt(n),requestExport:n=>wt(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?gr(s,r,o):_r(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?pn(r):kr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?hr(o,r):vr(n,r)},sizeFunction:(n,r,o)=>xr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?gn(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:pn(r).functions.map(o=>o.name),secretNames:gn(r).secrets.map(o=>o.name)}:Ur(n)},getProducts:n=>rt(n),setProduct:(n,r,o)=>jr(n,r,o),listDomains:n=>Sn(n),domainAction:(n,r,o)=>Cr(n,r,o),restoreWindow:n=>Mr(n),restoreTo:(n,r,o)=>Lr(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await bt(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=>ae(n),pushMigrations:(n,r)=>zr(n,{dir:r.dir,dryRun:r.dryRun})}}function Jf(){let e=Wt().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Ve(process.cwd())?.local;if(t&&!e.some(n=>ul(n.folder)===ul(t)))try{let n=Ne(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var ml="app_";async function Kf(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Xt();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await us(t)).map(o=>({name:`${ml}${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 pl(e){yt(!0);let{tools:t,config:n}=await Kf(),r=qf();n&&(r.callBorrowed=(i,u)=>ms(n,i.slice(ml.length),u));let o={...e,borrowed:t};z(t.length>0?`snoutdata mcp: ${Tt.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 Ge();z(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(z(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return z("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;z(`snoutdata mcp: not signed in, so only the shards tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}z(`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=Ra(s+u);s=c.rest;for(let m of c.messages)l(Wf(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 Wf(e,t,n){let r;try{r=JSON.parse(e)}catch{z(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ta(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
910
+ `)}),0}async function dl(e,t=process.cwd()){let n=Bn(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:al(e,n,Pn(r))}}V();W();Qt();pt();Re();import{resolve as ul}from"node:path";function qf(){let e=n=>Ee(n),t;return{shards:n=>{if(n!==void 0)return $t({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??=$t({}),t},whoami:()=>Ge(),listProjects:async()=>({...await ue(),local:Jf()}),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 St(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=>tt(n),requestExport:n=>wt(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?gr(s,r,o):_r(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?pn(r):kr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?hr(o,r):vr(n,r)},sizeFunction:(n,r,o)=>xr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?gn(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:pn(r).functions.map(o=>o.name),secretNames:gn(r).secrets.map(o=>o.name)}:Ur(n)},getProducts:n=>rt(n),setProduct:(n,r,o)=>Ir(n,r,o),listDomains:n=>Sn(n),domainAction:(n,r,o)=>Cr(n,r,o),restoreWindow:n=>Mr(n),restoreTo:(n,r,o)=>Lr(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await bt(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=>ae(n),pushMigrations:(n,r)=>zr(n,{dir:r.dir,dryRun:r.dryRun})}}function Jf(){let e=Wt().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Ve(process.cwd())?.local;if(t&&!e.some(n=>ul(n.folder)===ul(t)))try{let n=Ne(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var ml="app_";async function Kf(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Xt();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await us(t)).map(o=>({name:`${ml}${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 pl(e){yt(!0);let{tools:t,config:n}=await Kf(),r=qf();n&&(r.callBorrowed=(i,u)=>ms(n,i.slice(ml.length),u));let o={...e,borrowed:t};z(t.length>0?`snoutdata mcp: ${Tt.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 Ge();z(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(z(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return z("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;z(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}z(`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=Ra(s+u);s=c.rest;for(let m of c.messages)l(Wf(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 Wf(e,t,n){let r;try{r=JSON.parse(e)}catch{z(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ta(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
911
911
  `)}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)}})}
912
- `)}}pt();V();W();function zf(e){return e>=1?"over":e>=.8?"near":"ok"}function fl(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:zf(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Ro(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 gl(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=fl(n.days,n.limit);return f({...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=`${Pe(r.storage.usedBytes)} of ${Pe(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(`${Pe(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"}: ${Ro(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 ${Pe(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(j([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":Pe(o.dbBytes),o.repoBytes===null?"-":Pe(o.repoBytes),Ro(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}se();ne();He();W();import{createInterface as Vf}from"node:readline/promises";var Yf=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup"]),Oo={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"},Xf="nodes add|drain|remove, keyspace create, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup";function hl(e,t,n=!1){let[r,o,...s]=e,a=c=>x(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new g(m);return c},i,u=`${r??""}${o!==void 0&&Zf(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new g("in SnoutData Cloud a node is a pod the project makes: shards nodes add --project <ref>, with no name or --host. Attach it once it is ready: shards nodes attach <node-ref>");return{body:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new g("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with shards 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 shards status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new g("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Ue(t,"nodes","nodes");if(!c.nodes)throw new g("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(Oo))Object.assign(i,Ue(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: shards 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"),...Ue(t,"port","port"),...Le(t,"dbname","dbname"),...Le(t,"sslmode","sslmode"),...Le(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: shards nodes drain <id|name> [--to a,b]"),...a("to")?{to:qn(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: shards nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: shards keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the shard key's type (bigint, int, text, uuid, ...)"),...Ue(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Qf(a("seed"))}:{},...a("nodes")?{nodes:qn(a("nodes"))}:{}};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: shards table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the shard 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: shards table ${o} <schema.table>`)};break;case"range split":{let c=Hn(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:No(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=Hn(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new g("range merge needs two adjacent ranges: shards range merge <keyspace:lo> <keyspace:lo>");let c=Hn(s[0],t),m=Hn(s[1],t);if(c.keyspace!==m.keyspace)throw new g(`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: shards 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",...Le(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?qn(a("add")).map(h=>eg(h,t)):[],m=Ue(t,"remove","remove");if(c.length===0&&!m.remove)throw new g("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",...Le(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Le(t,"node","node")};break;default:throw new g(`unknown operation: shards ${e.join(" ")}. One of ${Xf}`)}for(let[c,m]of Object.entries(Oo)){let h=Ue(t,m,c);Object.assign(i,h)}return{body:i,destructive:Yf.has(i.op)}}function Zf(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function qn(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Le(e,t,n){let r=x(e,t);return r===void 0?{}:{[n]:r}}function Ue(e,t,n){let r=x(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new g(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function No(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new g(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function Qf(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new g(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Hn(e,t){let n=x(t,"keyspace"),r=x(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new g(`a range is keyspace:lo, as shards status prints it, not "${e}"`);return{keyspace:a,lo:No(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new g("name the range as keyspace:lo (as shards status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:No(o,"--range")}}function eg(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new g(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Le(t,"dbname","dbname"),...Le(t,"sslmode","sslmode")}}function Gn(e,t){if(e===void 0||!/^\d+$/.test(e))throw new g(`jobs ${t} needs a job id: shards jobs ${t} <id>`);return Number(e)}async function wl(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??$t({project:a,admin:x(e,"admin"),token:x(e,"token")});switch(r){case"enable":{if(!l)throw new g("shards enable is for a SnoutData Cloud project: shards enable --project <ref>. A standalone router is sharded from the moment it runs");let c=i();if(!(c instanceof vt))throw new g("shards enable needs --project <ref>");if(H(e,"plan")){let h=await c.enable(!0);return f(h,()=>dt(h,process.stdout)),0}if(!H(e,"yes")){if(!t.confirm&&!le())throw new g("turning sharding on restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(dt(await c.enable(!0),process.stderr),!await(t.confirm??Co)(`Turn sharding on for ${c.target}?`))throw new _("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return f(m,()=>d(Ar(m))),0}case void 0:case"status":{let c=await i().status();return f(c,()=>ig(c)),0}case"ops":{let c=await i().ops();return f(c,()=>{process.stdout.write(`${c.ops.join(`
912
+ `)}}pt();V();W();function zf(e){return e>=1?"over":e>=.8?"near":"ok"}function fl(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:zf(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Ro(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 gl(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=fl(n.days,n.limit);return f({...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=`${Pe(r.storage.usedBytes)} of ${Pe(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(`${Pe(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"}: ${Ro(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 ${Pe(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(I([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":Pe(o.dbBytes),o.repoBytes===null?"-":Pe(o.repoBytes),Ro(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}se();ne();He();W();import{createInterface as Vf}from"node:readline/promises";var Yf=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup"]),Oo={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"},Xf="nodes add|drain|remove, keyspace create, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup";function hl(e,t,n=!1){let[r,o,...s]=e,a=c=>x(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new g(m);return c},i,u=`${r??""}${o!==void 0&&Zf(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new g("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. Attach it once it is ready: cluster nodes attach <node-ref>");return{body:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new g("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new g("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Ue(t,"nodes","nodes");if(!c.nodes)throw new g("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(Oo))Object.assign(i,Ue(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"),...Ue(t,"port","port"),...Le(t,"dbname","dbname"),...Le(t,"sslmode","sslmode"),...Le(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:qn(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, ...)"),...Ue(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Qf(a("seed"))}:{},...a("nodes")?{nodes:qn(a("nodes"))}:{}};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=Hn(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:No(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=Hn(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new g("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=Hn(s[0],t),m=Hn(s[1],t);if(c.keyspace!==m.keyspace)throw new g(`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",...Le(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?qn(a("add")).map(h=>eg(h,t)):[],m=Ue(t,"remove","remove");if(c.length===0&&!m.remove)throw new g("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",...Le(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Le(t,"node","node")};break;default:throw new g(`unknown operation: cluster ${e.join(" ")}. One of ${Xf}`)}for(let[c,m]of Object.entries(Oo)){let h=Ue(t,m,c);Object.assign(i,h)}return{body:i,destructive:Yf.has(i.op)}}function Zf(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function qn(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Le(e,t,n){let r=x(e,t);return r===void 0?{}:{[n]:r}}function Ue(e,t,n){let r=x(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new g(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function No(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new g(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function Qf(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new g(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function Hn(e,t){let n=x(t,"keyspace"),r=x(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new g(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:No(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new g("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:No(o,"--range")}}function eg(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new g(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Le(t,"dbname","dbname"),...Le(t,"sslmode","sslmode")}}function Gn(e,t){if(e===void 0||!/^\d+$/.test(e))throw new g(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function wl(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??$t({project:a,admin:x(e,"admin"),token:x(e,"token")});switch(r){case"enable":{if(!l)throw new g("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof vt))throw new g("cluster enable needs --project <ref>");if(H(e,"plan")){let h=await c.enable(!0);return f(h,()=>dt(h,process.stdout)),0}if(!H(e,"yes")){if(!t.confirm&&!le())throw new g("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(dt(await c.enable(!0),process.stderr),!await(t.confirm??Co)(`Make a cluster of ${c.target}?`))throw new _("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return f(m,()=>d(Ar(m))),0}case void 0:case"status":{let c=await i().status();return f(c,()=>ig(c)),0}case"ops":{let c=await i().ops();return f(c,()=>{process.stdout.write(`${c.ops.join(`
913
913
  `)}
914
914
  `),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 rg(e,i);case"advise":case"advice":{let c=Ue(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return f(m,()=>ag(m,process.stdout)),0}case"jobs":return og(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return f({nodes:c.nodes??[]},()=>bl(c)),0}break;case"plan":{let c=hl(n.slice(1),e,l),m=await i().plan(c.body);return f(m,()=>dt(m,process.stdout)),0}}let u=hl(n,e,l);if(H(e,"plan")){let c=await i().plan(u.body);return f(c,()=>dt(c,process.stdout)),0}return tg(u,e,i,t)}async function tg(e,t,n,r){let o=H(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!le())throw new g(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(dt(u,process.stderr),!await(r.confirm??Co)(`Run ${s} on ${a.target}?`))throw new _("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return f(l.answer,()=>d(l.done)),0;if(H(t,"no-wait"))return f({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
915
- `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata shards jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await Do(a,l.job,r);return Io(i)}async function Co(e){let t=Vf({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 ng(e){let t=[...Pr,...xa],n={};for(let r of qn(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new g(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new g(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function rg(e,t){let n={};for(let s of Pr)Object.assign(n,Ue(e,Oo[s],s));let r=x(e,"set");if(r!==void 0&&Object.assign(n,ng(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return f({settings:a},()=>process.stdout.write(`${j(Object.entries(a).map(([l,i])=>[l,String(i)]))}
916
- `)),0}let o=await t().settings(n);return f(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function og(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return f({jobs:s},()=>mg(s)),0}case"show":{let s=await r.job(Gn(t,"show"));return f(s,()=>pg(s)),0}case"watch":{let s=await Do(r,Gn(t,"watch"),o);return Io(s)}case"cancel":{let s=Gn(t,"cancel");if(!H(n,"yes")){if(!o.confirm&&!le())throw new g(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??Co)(`Cancel job ${s} on ${r.target}?`))throw new _("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return f(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Gn(t,"resume"),a=await r.resume(s);return H(n,"no-wait")?(f(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata shards jobs watch ${s}`)),0):Io(await Do(r,s,o))}default:throw new g(`unknown command: shards jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Do(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(xt(a.state))return a;if(Date.now()>=o)throw new _("timeout",`job ${t} is still ${a.state}; it carries on without this command. Follow it again: snoutdata shards jobs watch ${t}`,{job:a});await new Promise(l=>setTimeout(l,r))}}function Io(e){if(e.state==="done")return f(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
917
- `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata shards jobs resume ${e.id}`:"was cancelled";throw new _("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function M(e,t=""){e.write(`${t}
918
- `)}function jo(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 dt(e,t){M(t,`${v(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&M(t,j([[" 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")}`,jo(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),M(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${jo(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)`),M(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)M(t,` warning: ${n}`);e.assumptions&&M(t,w(` ${e.assumptions}`))}function ge(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function sg(e){let t=e,n="snoutdata shards";switch(e.op){case"range.split":return`${n} range split ${ge(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${ge(t.at)}`:""}${t.to!==void 0?` --to ${ge(t.to)}`:""}`;case"range.move":return`${n} range move ${ge(`${t.keyspace}:${t.range}`)} --to ${ge(t.to)}`;case"tenant.pin":return`${n} tenant pin ${ge(t.keyspace)} --value ${ge(t.value)}${t.node!==void 0?` --node ${ge(t.node)}`:""}`;case"node.add":return`${n} nodes add ${ge(t.name)} --host ${t.host?ge(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${ge(t.keyspace)}`:""}`;default:return null}}function ag(e,t){M(t,e.summary),e.advice.forEach((n,r)=>{M(t),M(t,`${v(`${r+1}. ${n.op}`)} (${n.metric})`),M(t,` ${n.reason}`);let o=sg(n.request);if(M(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;M(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${jo(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])M(t,` warning: ${a}`)}else n.needs.length>0?M(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&M(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(M(t),M(t,w(e.assumptions)))}function yl(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function bl(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}M(process.stdout,j([["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 ig(e){let t=process.stdout;if(e.cloud&&(ug(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){M(t,"The home node has no Lepis catalog yet (schema lepis).");return}M(t,`${v("Cluster")} at epoch ${e.epoch}`),M(t),bl(e);for(let s of e.keyspaces??[]){M(t),M(t,`${v(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),M(t,j([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,yl(e,a.node)])]));for(let a of s.pins)M(t,` pinned: ${a.value} on ${yl(e,a.node)}`)}let n=e.tables??[];n.length>0&&(M(t),M(t,j([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(M(t),M(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&&M(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 cg(e,t){switch(t.next){case"attach":return`ready: snoutdata shards nodes attach ${t.ref} --project ${e.ref}`;case"delete-pod":return`let go by the cluster: snoutdata shards nodes remove ${t.ref} --project ${e.ref} deletes its pod`;case"wait":return`being made (${t.state})`;default:return t.node?`node ${t.node.id}, ${t.node.state}`:t.state}}function lg(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function dg(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 ug(e,t){let n=dg(e);if(!e.sharded){M(t,`${v(e.ref)} is not sharded (${n}).`),lg(e)>0&&M(t,`Turn it on: snoutdata shards enable --project ${e.ref} (the database restarts once).`);return}let r=1+e.pods.filter(o=>o.desiredState!=="deleted").length;M(t,`${v(e.ref)}: ${r} node${r===1?"":"s"}, ${n}`),e.pods.length>0&&M(t,j([[" POD","STATE","NEXT"],...e.pods.map(o=>[` ${o.ref}`,o.state,cg(e,o)])])),e.routerError&&M(t,`The router did not answer: ${e.routerError}`),M(t)}function mg(e){if(e.length===0){d("No jobs yet.");return}M(process.stdout,j([["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 pg(e){let t=process.stdout;M(t,`${v(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),M(t,j([[" 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)&&(M(t),dt(e.plan,t))}ne();W();gt();import{spawn as fg}from"node:child_process";import{createHash as gg}from"node:crypto";import{chmodSync as hg,renameSync as Jn,rmSync as Sl,writeFileSync as yg}from"node:fs";import{dirname as wg,join as bg}from"node:path";var Kn=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Sg="https://github.com/snoutdata/app/releases/download";function _g(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 kg(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 vg(){let e;try{e=await fetch(`${Kn}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new _("network",`could not reach ${Kn} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new _("server",`${Kn} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new _("server",`${Kn} did not say which version is newest`);return t.version}async function _l(e){let t;try{t=await fetch(e)}catch(n){throw new _("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new _("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function xg(e,t){let n=bg(wg(e),`.snoutdata-upgrade-${process.pid}`);yg(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Sl(r,{force:!0}),Jn(e,r);try{Jn(n,e)}catch(o){throw Jn(r,e),o}}else hg(n,493),Jn(n,e)}catch(r){Sl(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new _("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?ve:ft}`):r}}function $g(){return new Promise((e,t)=>{let n=fg(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new _("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${ve}`))),n.on("exit",r=>e(r??1))})}async function kl(e={}){let t=e.kind??tn(),n=await vg(),r=fs(ee,n),o={installed:ee,latest:n,upToDate:!r,install:t};if(!r||e.check)return f({...o,upgraded:!1},()=>{d(r?`snoutdata ${ee} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ee} is the newest version.`)}),0;switch(t){case"npx":return f({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ee}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new _("usage",`this snoutdata (${ee}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?ve:ft}`);case"npm":{d(`Upgrading snoutdata ${ee} to ${n} with npm\u2026`);let s=await $g();if(s!==0)throw new _("failed",`npm exited ${s}. Run it yourself: ${ve}`);return f({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=_g(process.platform,process.arch);if(!s)throw new _("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${ve}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Sg}/cli-v${n}`,[l,i]=await Promise.all([_l(`${a}/${s}`),_l(`${a}/SHA256SUMS`)]),u=kg(i.toString("utf8"),s);if(!u)throw new _("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=gg("sha256").update(l).digest("hex");if(c!==u)throw new _("failed",`checksum mismatch for ${s}. Refusing to install.
915
+ `),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 Do(a,l.job,r);return jo(i)}async function Co(e){let t=Vf({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 ng(e){let t=[...Pr,...xa],n={};for(let r of qn(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new g(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new g(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function rg(e,t){let n={};for(let s of Pr)Object.assign(n,Ue(e,Oo[s],s));let r=x(e,"set");if(r!==void 0&&Object.assign(n,ng(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return f({settings:a},()=>process.stdout.write(`${I(Object.entries(a).map(([l,i])=>[l,String(i)]))}
916
+ `)),0}let o=await t().settings(n);return f(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function og(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return f({jobs:s},()=>mg(s)),0}case"show":{let s=await r.job(Gn(t,"show"));return f(s,()=>pg(s)),0}case"watch":{let s=await Do(r,Gn(t,"watch"),o);return jo(s)}case"cancel":{let s=Gn(t,"cancel");if(!H(n,"yes")){if(!o.confirm&&!le())throw new g(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??Co)(`Cancel job ${s} on ${r.target}?`))throw new _("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return f(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=Gn(t,"resume"),a=await r.resume(s);return H(n,"no-wait")?(f(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):jo(await Do(r,s,o))}default:throw new g(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function Do(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(xt(a.state))return a;if(Date.now()>=o)throw new _("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 jo(e){if(e.state==="done")return f(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
917
+ `)),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 _("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function M(e,t=""){e.write(`${t}
918
+ `)}function Io(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 dt(e,t){M(t,`${v(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&M(t,I([[" 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")}`,Io(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),M(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${Io(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)`),M(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)M(t,` warning: ${n}`);e.assumptions&&M(t,w(` ${e.assumptions}`))}function ge(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function sg(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${ge(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${ge(t.at)}`:""}${t.to!==void 0?` --to ${ge(t.to)}`:""}`;case"range.move":return`${n} range move ${ge(`${t.keyspace}:${t.range}`)} --to ${ge(t.to)}`;case"tenant.pin":return`${n} tenant pin ${ge(t.keyspace)} --value ${ge(t.value)}${t.node!==void 0?` --node ${ge(t.node)}`:""}`;case"node.add":return`${n} nodes add ${ge(t.name)} --host ${t.host?ge(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${ge(t.keyspace)}`:""}`;default:return null}}function ag(e,t){M(t,e.summary),e.advice.forEach((n,r)=>{M(t),M(t,`${v(`${r+1}. ${n.op}`)} (${n.metric})`),M(t,` ${n.reason}`);let o=sg(n.request);if(M(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;M(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${Io(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])M(t,` warning: ${a}`)}else n.needs.length>0?M(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&M(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(M(t),M(t,w(e.assumptions)))}function yl(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function bl(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}M(process.stdout,I([["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 ig(e){let t=process.stdout;if(e.cloud&&(ug(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){M(t,"The home node has no Lepis catalog yet (schema lepis).");return}M(t,`${v("Cluster")} at epoch ${e.epoch}`),M(t),bl(e);for(let s of e.keyspaces??[]){M(t),M(t,`${v(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),M(t,I([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,yl(e,a.node)])]));for(let a of s.pins)M(t,` pinned: ${a.value} on ${yl(e,a.node)}`)}let n=e.tables??[];n.length>0&&(M(t),M(t,I([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(M(t),M(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&&M(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 cg(e,t){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`being made (${t.state})`;default:return t.node?`node ${t.node.id}, ${t.node.state}`:t.state}}function lg(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function dg(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 ug(e,t){let n=dg(e);if(!e.sharded){M(t,`${v(e.ref)} is not a cluster (${n}).`),lg(e)>0&&M(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}let r=1+e.pods.filter(o=>o.desiredState!=="deleted").length;M(t,`${v(e.ref)}: ${r} node${r===1?"":"s"}, ${n}`),e.pods.length>0&&M(t,I([[" POD","STATE","NEXT"],...e.pods.map(o=>[` ${o.ref}`,o.state,cg(e,o)])])),e.routerError&&M(t,`The router did not answer: ${e.routerError}`),M(t)}function mg(e){if(e.length===0){d("No jobs yet.");return}M(process.stdout,I([["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 pg(e){let t=process.stdout;M(t,`${v(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),M(t,I([[" 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)&&(M(t),dt(e.plan,t))}ne();W();gt();import{spawn as fg}from"node:child_process";import{createHash as gg}from"node:crypto";import{chmodSync as hg,renameSync as Jn,rmSync as Sl,writeFileSync as yg}from"node:fs";import{dirname as wg,join as bg}from"node:path";var Kn=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Sg="https://github.com/snoutdata/app/releases/download";function _g(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 kg(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 vg(){let e;try{e=await fetch(`${Kn}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new _("network",`could not reach ${Kn} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new _("server",`${Kn} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new _("server",`${Kn} did not say which version is newest`);return t.version}async function _l(e){let t;try{t=await fetch(e)}catch(n){throw new _("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new _("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function xg(e,t){let n=bg(wg(e),`.snoutdata-upgrade-${process.pid}`);yg(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Sl(r,{force:!0}),Jn(e,r);try{Jn(n,e)}catch(o){throw Jn(r,e),o}}else hg(n,493),Jn(n,e)}catch(r){Sl(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new _("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?ve:ft}`):r}}function $g(){return new Promise((e,t)=>{let n=fg(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new _("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${ve}`))),n.on("exit",r=>e(r??1))})}async function kl(e={}){let t=e.kind??tn(),n=await vg(),r=fs(ee,n),o={installed:ee,latest:n,upToDate:!r,install:t};if(!r||e.check)return f({...o,upgraded:!1},()=>{d(r?`snoutdata ${ee} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ee} is the newest version.`)}),0;switch(t){case"npx":return f({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ee}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new _("usage",`this snoutdata (${ee}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?ve:ft}`);case"npm":{d(`Upgrading snoutdata ${ee} to ${n} with npm\u2026`);let s=await $g();if(s!==0)throw new _("failed",`npm exited ${s}. Run it yourself: ${ve}`);return f({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=_g(process.platform,process.arch);if(!s)throw new _("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${ve}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Sg}/cli-v${n}`,[l,i]=await Promise.all([_l(`${a}/${s}`),_l(`${a}/SHA256SUMS`)]),u=kg(i.toString("utf8"),s);if(!u)throw new _("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=gg("sha256").update(l).digest("hex");if(c!==u)throw new _("failed",`checksum mismatch for ${s}. Refusing to install.
919
919
  expected ${u}
920
920
  actual ${c}`);return xg(process.execPath,l),f({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}ln();Re();gt();var vl=`snoutdata ${ee} \u2014 hosted Postgres, from a terminal or an agent
921
921
 
@@ -1010,21 +1010,21 @@ ${o.err.trim()}`),d(" ran seed.sql"),!0}var Bt="_snoutdata_migrations",Hf=`crea
1010
1010
  snoutdata db restore --window [--ref R] how far back a point-in-time restore can go
1011
1011
  snoutdata db restore --at TIME [--name N] that moment, into a NEW project beside this one
1012
1012
 
1013
- snoutdata shards --admin URL [status] a Lepis cluster (Postgres over several nodes), from its
1013
+ snoutdata cluster --admin URL [status] a Lepis cluster (Postgres over several nodes), from its
1014
1014
  router's admin API; the token from LEPIS_ADMIN_TOKEN
1015
- snoutdata shards --project REF ... the same, for a SnoutData Cloud project, as you; there
1015
+ snoutdata cluster --project REF ... the same, for a SnoutData Cloud project, as you; there
1016
1016
  enable turns it on, nodes add makes a node pod, nodes
1017
1017
  attach REF hands a ready one over, scale --nodes N
1018
- snoutdata shards nodes [add|drain|remove] | keyspace create | table distribute|reference|global
1018
+ snoutdata cluster nodes [add|drain|remove] | keyspace create | table distribute|reference|global
1019
1019
  range split|merge|move | tenant pin | rebalance | scale | verify | cleanup
1020
1020
  each with --plan for the dry run (size, copy time,
1021
1021
  expected pause); one that moves or deletes data asks,
1022
1022
  or takes --yes. Waits for its job unless --no-wait
1023
- snoutdata shards plan <operation> the same dry run
1024
- snoutdata shards jobs [show|watch|cancel|resume <id>] the durable job log
1025
- snoutdata shards settings [--max-write-pause-ms N ...] [--set advice_min_bytes=N,...]
1023
+ snoutdata cluster plan <operation> the same dry run
1024
+ snoutdata cluster jobs [show|watch|cancel|resume <id>] the durable job log
1025
+ snoutdata cluster settings [--max-write-pause-ms N ...] [--set advice_min_bytes=N,...]
1026
1026
  the cluster's cutover and advisor settings
1027
- snoutdata shards advise [--sample-ms N] what to split, move or add, each with its reason, its
1027
+ snoutdata cluster advise [--sample-ms N] what to split, move or add, each with its reason, its
1028
1028
  plan and the command that runs it; runs nothing
1029
1029
 
1030
1030
  snoutdata mcp [--allow-delete] serve these operations to an agent, over stdio
@@ -1061,5 +1061,5 @@ Source: https://github.com/snoutdata/snout-cli
1061
1061
  `);let h=Math.max(0,...u.flags.map($=>$.length));for(let $ of u.flags)process.stdout.write(` ${$.padEnd(h)} ${Vn(u.name,$)}
1062
1062
  `)}process.stdout.write(`
1063
1063
  Every command also takes --json, --quiet and --help.
1064
- `)}),0}let o=H(e,"help")||t==="help";if(o||!t)return f({version:ee,usage:vl.trim(),commands:zn},()=>process.stdout.write(vl)),o?0:ke.usage;let s=()=>{let i=Fe(e,"timeout");if(i!==void 0){if(!(i>0))throw new g("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Kt({flag:x(e,"ref")});if(!i)throw new g("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Kt({flag:x(e,"ref")});return i?Ee(i):null};for(let i of Eg)if(e.flags[i]!==void 0&&t!=="shards")throw new g(`--${i} is only for snoutdata shards`);switch(t){case"shards":return wl(e,{timeoutMs:s()});case"login":{if(H(e,"device")){let m=await nn({name:Zt(),platform:process.platform});f({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 rn(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ye({accessToken:h.token});let $=x(e,"email"),B=$?(await Ge()).email??void 0:void 0;return $&&ur($,{accessToken:h.token,email:B}),f({ok:!0,name:h.name??null,email:B??null},()=>d(`Signed in${B?` as ${B}`:""}.`)),0}let i=x(e,"domain"),u=H(e,"sso")||i!==void 0;if(u&&x(e,"provider"))throw new g("--sso and --provider are two different doors: pass one or the other");let c=await cn({provider:x(e,"provider"),sso:u,domain:i,noBrowser:H(e,"no-browser")||!le(),timeoutMs:s(),email:x(e,"email")});return f({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{zo();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return f({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 Ge(),u=Jt()?.expectedEmail,c=!!(u&&i.email&&!mr(u,i.email));return c&&z(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),f({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
1065
- `),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 ta(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("tokens create needs --name (what is it for, and where does it live)");return await ea({name:i,expiresInDays:Fe(e,"expires"),project:x(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new g("tokens revoke needs the token's id or its sdt_ prefix");return await na(i),0}default:throw new g(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Vs(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("projects create needs --name");let u=x(e,"team");return await wr({teamId:u?await Qs(u):void 0,name:i,region:x(e,"region"),wait:!H(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:H(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Ys(n,x(e,"ref")??r[0]??a(),{wait:!H(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=x(e,"ref")??r[0]??a(),u=Ee(i);u?Ds(u):await Ma(i)}return 0;default:throw new g(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await Hs(i,r[0],{dir:x(e,"dir"),verifyJwt:!H(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Gs(i),0;if((n==="delete"||n==="remove")&&r[0])return await qs(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await ca(a(),u,{dir:x(e,"dir"),entrypoint:x(e,"entrypoint"),verifyJwt:!H(e,"no-verify-jwt")}),0}case void 0:case"list":return await ua(a()),0;case"size":{let u=r[0];if(!u)throw new g("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await da(a(),u,{memoryMb:Fe(e,"memory"),concurrency:Fe(e,"concurrency"),reset:H(e,"reset")}),0}case"delete":case"remove":{let u=r[0];if(!u)throw new g("functions delete needs a name");return await ma(a(),u),0}default:throw new g(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await Ks(i,await ga(r,{stdin:H(e,"stdin")})),0;if(n===void 0||n==="list")return Ws(i),0;if((n==="unset"||n==="remove")&&r[0])return await zs(i,r[0]),0}switch(n){case"set":return await ha(a(),r,{stdin:H(e,"stdin")}),0;case void 0:case"list":return await wa(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new g("secrets unset needs a name");return await ya(a(),u),0}default:throw new g(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await ra(a()),0;case"rotate":return await oa(a(),{force:H(e,"force")}),0;default:throw new g(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Na(a()),0;if(n==="enable"||n==="disable")return await Da(a(),Oa(r[0]),n==="enable"),0;throw new g(`unknown command: products ${n}. products [enable|disable ${bn.join("|")}]`)}case"push":{if(n!=="credentials")throw new g("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await li(a()),0;if(i==="set"){let u=Vr(r[1]),c=u==="apns"?await ii({p8:x(e,"p8"),keyId:x(e,"key-id"),teamId:x(e,"team-id"),topic:x(e,"topic"),environment:x(e,"environment")}):await ci(x(e,"file"));return await di(a(),u,c),0}if(i==="remove"||i==="unset")return await ui(a(),Vr(r[1])),0;throw new g(`unknown command: push credentials ${i}`)}case"auth":return await ni(a(),n,r,{clientId:x(e,"client-id"),stdin:H(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 Xa(a(),{channel:x(e,"channel"),watch:H(e,"watch")});if(n==="logs")return Za(a(),{channel:x(e,"channel"),since:x(e,"since")});throw new g(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Ia(a(),n,r[0]),0;case"teams":return await Zs(),0;case"link":{if(H(e,"local")){let c;try{c=Qo(n)}catch(h){throw new _("not-found",h instanceof Error?h.message:String(h))}let m=Ye(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return f({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 g("link needs --ref");let u=Ye(process.cwd(),{ref:i});return f({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Gr(a()),0;case"psql":return qa(a(),r);case"reset-password":return await Xs(a()),0;case"access":return await za(a(),r,{level:x(e,"level")}),0;case"push":return si(a(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return H(e,"window")?(await ja(a()),0):x(e,"at")?(await Ca(a(),x(e,"at"),x(e,"name")),0):Wa(a(),{file:x(e,"file"),force:!!e.flags.force});case"export":return Ka(a(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new g(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new g(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=H(e,"local")?await dl(await Gt()):null,u=i?i.dbUrl:x(e,"db-url");return gi(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 Os(i)}return il(await Gt(),{port:Fe(e,"port"),dir:x(e,"dir"),noMigrations:H(e,"no-migrations"),outOfOrder:H(e,"out-of-order")});case"stop":{let i=l();return i?Ns(i):cl(await Gt())}case"status":{let i=l();return i?Rs(i):ll(await Gt())}case"usage":return gl(a(),{days:Fe(e,"days"),history:!!e.flags.history});case"mcp":return pl({version:ee,allowDelete:!!e.flags["allow-delete"]});case"init":return Ag(e);case"upgrade":return kl({check:H(e,"check")});default:throw new g(`unknown command: ${t}`)}}async function Ag(e){let t=Xo({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.`),H(e,"env")?await xl(o):await Gr(o),t.kind==="link"){let s=Ee(o),a=Ye(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")??Tg(process.cwd());d(`Creating a project called "${n}"\u2026`),await wr({name:n,region:x(e,"region"),wait:!0,link:process.cwd(),showUrl:!H(e,"env")});let r=Kt({cwd:process.cwd()});return r&&H(e,"env")&&await xl(r),0}async function xl(e){let{connection:t}=await Promise.resolve().then(()=>(V(),be)),n=await t(e),r=Ja(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 Tg(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Rg(e){if(e instanceof _)return De(e.code,e.message,e.details),e.exitCode;if(e instanceof g)return De("usage",e.message),ke.usage;if(e instanceof Ie)return De("not-signed-in",e.message),ke["not-signed-in"];if(e instanceof de){let r=Be(e.status,e.serverCode);return De(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),ke[r]}let t=ns(e);if(t)return De("usage",t),ke.usage;let n=Vt(e);return De(n,e instanceof Error?e.message:String(e)),ke[n]}function Mo(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Og(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Ng(){yt(Og(process.argv.slice(2)));let e;try{e=Fo(process.argv.slice(2))}catch(t){De("usage",String(t instanceof Error?t.message:t)),Mo(ke.usage);return}yt(e.json),ys(e.flags.quiet===!0),rs(Yt({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{Mo(await Pg(e))}catch(t){Mo(Rg(t))}}Ng();
1064
+ `)}),0}let o=H(e,"help")||t==="help";if(o||!t)return f({version:ee,usage:vl.trim(),commands:zn},()=>process.stdout.write(vl)),o?0:ke.usage;let s=()=>{let i=Fe(e,"timeout");if(i!==void 0){if(!(i>0))throw new g("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Kt({flag:x(e,"ref")});if(!i)throw new g("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Kt({flag:x(e,"ref")});return i?Ee(i):null};for(let i of Eg)if(e.flags[i]!==void 0&&t!=="cluster")throw new g(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return wl(e,{timeoutMs:s()});case"login":{if(H(e,"device")){let m=await nn({name:Zt(),platform:process.platform});f({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 rn(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});ye({accessToken:h.token});let $=x(e,"email"),B=$?(await Ge()).email??void 0:void 0;return $&&ur($,{accessToken:h.token,email:B}),f({ok:!0,name:h.name??null,email:B??null},()=>d(`Signed in${B?` as ${B}`:""}.`)),0}let i=x(e,"domain"),u=H(e,"sso")||i!==void 0;if(u&&x(e,"provider"))throw new g("--sso and --provider are two different doors: pass one or the other");let c=await cn({provider:x(e,"provider"),sso:u,domain:i,noBrowser:H(e,"no-browser")||!le(),timeoutMs:s(),email:x(e,"email")});return f({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{zo();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return f({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 Ge(),u=Jt()?.expectedEmail,c=!!(u&&i.email&&!mr(u,i.email));return c&&z(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),f({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
1065
+ `),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 ta(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("tokens create needs --name (what is it for, and where does it live)");return await ea({name:i,expiresInDays:Fe(e,"expires"),project:x(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new g("tokens revoke needs the token's id or its sdt_ prefix");return await na(i),0}default:throw new g(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Vs(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("projects create needs --name");let u=x(e,"team");return await wr({teamId:u?await Qs(u):void 0,name:i,region:x(e,"region"),wait:!H(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:H(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Ys(n,x(e,"ref")??r[0]??a(),{wait:!H(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=x(e,"ref")??r[0]??a(),u=Ee(i);u?Ds(u):await Ma(i)}return 0;default:throw new g(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await Hs(i,r[0],{dir:x(e,"dir"),verifyJwt:!H(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Gs(i),0;if((n==="delete"||n==="remove")&&r[0])return await qs(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await ca(a(),u,{dir:x(e,"dir"),entrypoint:x(e,"entrypoint"),verifyJwt:!H(e,"no-verify-jwt")}),0}case void 0:case"list":return await ua(a()),0;case"size":{let u=r[0];if(!u)throw new g("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await da(a(),u,{memoryMb:Fe(e,"memory"),concurrency:Fe(e,"concurrency"),reset:H(e,"reset")}),0}case"delete":case"remove":{let u=r[0];if(!u)throw new g("functions delete needs a name");return await ma(a(),u),0}default:throw new g(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await Ks(i,await ga(r,{stdin:H(e,"stdin")})),0;if(n===void 0||n==="list")return Ws(i),0;if((n==="unset"||n==="remove")&&r[0])return await zs(i,r[0]),0}switch(n){case"set":return await ha(a(),r,{stdin:H(e,"stdin")}),0;case void 0:case"list":return await wa(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new g("secrets unset needs a name");return await ya(a(),u),0}default:throw new g(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await ra(a()),0;case"rotate":return await oa(a(),{force:H(e,"force")}),0;default:throw new g(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Na(a()),0;if(n==="enable"||n==="disable")return await Da(a(),Oa(r[0]),n==="enable"),0;throw new g(`unknown command: products ${n}. products [enable|disable ${bn.join("|")}]`)}case"push":{if(n!=="credentials")throw new g("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await li(a()),0;if(i==="set"){let u=Vr(r[1]),c=u==="apns"?await ii({p8:x(e,"p8"),keyId:x(e,"key-id"),teamId:x(e,"team-id"),topic:x(e,"topic"),environment:x(e,"environment")}):await ci(x(e,"file"));return await di(a(),u,c),0}if(i==="remove"||i==="unset")return await ui(a(),Vr(r[1])),0;throw new g(`unknown command: push credentials ${i}`)}case"auth":return await ni(a(),n,r,{clientId:x(e,"client-id"),stdin:H(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 Xa(a(),{channel:x(e,"channel"),watch:H(e,"watch")});if(n==="logs")return Za(a(),{channel:x(e,"channel"),since:x(e,"since")});throw new g(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await ja(a(),n,r[0]),0;case"teams":return await Zs(),0;case"link":{if(H(e,"local")){let c;try{c=Qo(n)}catch(h){throw new _("not-found",h instanceof Error?h.message:String(h))}let m=Ye(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return f({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 g("link needs --ref");let u=Ye(process.cwd(),{ref:i});return f({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Gr(a()),0;case"psql":return qa(a(),r);case"reset-password":return await Xs(a()),0;case"access":return await za(a(),r,{level:x(e,"level")}),0;case"push":return si(a(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return H(e,"window")?(await Ia(a()),0):x(e,"at")?(await Ca(a(),x(e,"at"),x(e,"name")),0):Wa(a(),{file:x(e,"file"),force:!!e.flags.force});case"export":return Ka(a(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new g(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new g(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=H(e,"local")?await dl(await Gt()):null,u=i?i.dbUrl:x(e,"db-url");return gi(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 Os(i)}return il(await Gt(),{port:Fe(e,"port"),dir:x(e,"dir"),noMigrations:H(e,"no-migrations"),outOfOrder:H(e,"out-of-order")});case"stop":{let i=l();return i?Ns(i):cl(await Gt())}case"status":{let i=l();return i?Rs(i):ll(await Gt())}case"usage":return gl(a(),{days:Fe(e,"days"),history:!!e.flags.history});case"mcp":return pl({version:ee,allowDelete:!!e.flags["allow-delete"]});case"init":return Ag(e);case"upgrade":return kl({check:H(e,"check")});default:throw new g(`unknown command: ${t}`)}}async function Ag(e){let t=Xo({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.`),H(e,"env")?await xl(o):await Gr(o),t.kind==="link"){let s=Ee(o),a=Ye(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")??Tg(process.cwd());d(`Creating a project called "${n}"\u2026`),await wr({name:n,region:x(e,"region"),wait:!0,link:process.cwd(),showUrl:!H(e,"env")});let r=Kt({cwd:process.cwd()});return r&&H(e,"env")&&await xl(r),0}async function xl(e){let{connection:t}=await Promise.resolve().then(()=>(V(),be)),n=await t(e),r=Ja(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 Tg(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Rg(e){if(e instanceof _)return De(e.code,e.message,e.details),e.exitCode;if(e instanceof g)return De("usage",e.message),ke.usage;if(e instanceof je)return De("not-signed-in",e.message),ke["not-signed-in"];if(e instanceof de){let r=Be(e.status,e.serverCode);return De(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),ke[r]}let t=ns(e);if(t)return De("usage",t),ke.usage;let n=Vt(e);return De(n,e instanceof Error?e.message:String(e)),ke[n]}function Mo(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Og(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Ng(){yt(Og(process.argv.slice(2)));let e;try{e=Fo(process.argv.slice(2))}catch(t){De("usage",String(t instanceof Error?t.message:t)),Mo(ke.usage);return}yt(e.json),ys(e.flags.quiet===!0),rs(Yt({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{Mo(await Pg(e))}catch(t){Mo(Rg(t))}}Ng();