snoutdata 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -43,6 +43,7 @@ This CLI is built to be driven by a program. The contract, which every command h
43
43
  | `8` | Over a quota | The plan's limit. Do not retry. |
44
44
  | `9` | Network | Retry. |
45
45
  | `10` | Timed out | Retry, or raise `--timeout`. |
46
+ | `127` | A tool this command needs is not installed | The message names it. The shell’s own convention, so a CI log reads right. |
46
47
 
47
48
  ### Credentials, without a browser
48
49
 
@@ -88,6 +89,8 @@ snoutdata link --ref R # point this folder at an existing project
88
89
 
89
90
  ```bash
90
91
  snoutdata login # through a browser
92
+ snoutdata login --sso # your company's identity provider; asks for a work email
93
+ snoutdata login --sso --domain acme.com # or name the domain and skip the question
91
94
  snoutdata login --device # print a code to type into a browser anywhere
92
95
  snoutdata login --no-browser # print the URL instead of opening one
93
96
  snoutdata logout
@@ -118,6 +121,36 @@ snoutdata db push [--dir migrations] [--dry-run] # run the .sql files, once eac
118
121
  snoutdata db restore --file DUMP [--force]
119
122
  ```
120
123
 
124
+ **Types**
125
+
126
+ ```bash
127
+ snoutdata gen types typescript [--ref R] [--schema public] [--out FILE]
128
+ snoutdata gen types typescript --db-url postgres://... # any Postgres, ours or not
129
+ snoutdata gen types typescript --local # the database `snoutdata start` runs
130
+ ```
131
+
132
+ Your schema as a TypeScript `Database` type, on stdout. Read out of `pg_catalog`, so an enum
133
+ column says `mood` and a generated column is optional on insert.
134
+
135
+ **Local**
136
+
137
+ ```bash
138
+ snoutdata start [--port 54322] [--dir migrations] [--no-migrations]
139
+ snoutdata stop # the data stays
140
+ snoutdata status # running, how big, on what port
141
+ ```
142
+
143
+ A Postgres on your own machine, the same build as a hosted project, with `migrations/*.sql` and
144
+ `seed.sql` applied on the way up, using the same migration ledger `db push` uses, so a folder means
145
+ the same thing here and in the cloud. It publishes on 54322 by default, which is where
146
+ `supabase start` puts its Postgres, so a `.env` written for one works against the other; pass
147
+ `--port` if you run both at once.
148
+
149
+ It needs **Podman** (not Docker, and deliberately with no fallback: a database that works under
150
+ Docker and not under Podman is one that works here and not in the cloud). The database image is
151
+ fetched from `ghcr.io/snoutdata/snoutpod-postgres` the first time you run it, which takes a few
152
+ minutes once and nothing after that. Nothing else in this CLI needs anything installed at all.
153
+
121
154
  **Agents**
122
155
 
123
156
  ```bash
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var Rn=Object.defineProperty;var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var Dn=(e,t)=>{for(var n in t)Rn(e,n,{get:t[n],enumerable:!0})};import{chmodSync as Ln,existsSync as ut,mkdirSync as dt,readFileSync as pt,rmSync as Un,writeFileSync as mt}from"node:fs";import{homedir as ke}from"node:os";import{dirname as qe,join as ft,parse as Mn,resolve as Bn}from"node:path";function Fe(e=ke()){return ft(e,".snoutdata","auth.json")}function gt(e=ke()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=Fe(e);if(!ut(n))return null;try{let r=JSON.parse(pt(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function L(e,t=ke()){let n=Fe(t);dt(qe(n),{recursive:!0}),mt(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),Ln(n,384)}function ht(e=ke()){Un(Fe(e),{force:!0})}function yt(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function wt(e){return ft(e,".snoutdata","project.json")}function qn(e){let t=Bn(e),n=Mn(t).root;for(;;){let r=wt(t);if(ut(r))try{let o=JSON.parse(pt(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=qe(t)}}function Se(e,t){let n=wt(e);return dt(qe(n),{recursive:!0}),mt(n,`${JSON.stringify(t,null,2)}
4
- `),n}function xe(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:qn(e.cwd??process.cwd())?.ref??null}var Q=C(()=>{"use strict"});function Te(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 Fn){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 $e(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 bt(e){We=e}function ee(){return(We??Te({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function ve(){return(We??Te({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Fn,We,ce=C(()=>{"use strict";Fn=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];We=null});import{existsSync as Wn,readFileSync as Gn}from"node:fs";import{homedir as Ge,hostname as Kn}from"node:os";import{join as te}from"node:path";function Jn(e,t,n){if(t==="win32"){let r=n.APPDATA??te(Ge(),"AppData","Roaming");return te(r,e)}return t==="darwin"?te(Ge(),"Library","Application Support",e):te(n.XDG_CONFIG_HOME??te(Ge(),".config"),e)}function St(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>te(Jn(n,e,t),"mcp.json"))}function Vn(e=process.platform,t=process.env){return St(e,t)[0]}function zn(e=Vn()){if(!Wn(e))return null;try{let t=JSON.parse(Gn(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Hn(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}async function Oe(e){if(e!==void 0)return kt(e);let t="no-config";for(let n of St()){let r=zn(n);if(!r)continue;let o=await kt(r);if(o.available)return o;(o.skip!=="not-running"||t==="no-config")&&(t=o.skip)}return{available:!1,skip:t}}async function kt(e){let t=Hn(e);if(t)return{available:!1,skip:t};let n=e,r;try{let o=await fetch(`http://127.0.0.1:${n.port}/cli/whoami`,{headers:{Authorization:`Bearer ${n.token}`},signal:AbortSignal.timeout(Yn)});if(!o.ok)return{available:!1,skip:"not-running"};r=await o.json()}catch{return{available:!1,skip:"not-running"}}return r.signedIn?r.canMintToken?{available:!0,who:r,config:n}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function xt(e,t){let n=await fetch(`http://127.0.0.1:${e.port}/cli/token`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"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??`the app refused (${n.status})`}}function Pe(){return Kn()}async function Ke(e,t,n,r){let o=await fetch(`http://127.0.0.1:${e.port}/mcp`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"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??"the app refused");return s.result}async function Tt(e){return await Ke(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await Ke(e,"tools/list",{},3e3)).tools??[]}async function $t(e,t,n){return Ke(e,"tools/call",{name:t,arguments:n},12e4)}var Yn,Ae=C(()=>{"use strict";Yn=1500});function y(e,t,n){throw new G(e,t,n)}function Ot(e){return e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e>=500?"server":"failed"}function Pt(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(Xn.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 K,G,Xn,H=C(()=>{"use strict";K={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,"tool-missing":127,server:1},G=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return K[this.code]}};Xn=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});async function At(e,t){let n=await fetch(`${U}/functions/v1/${e}`,{method:"POST",headers:{apikey:J,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return{status:n.status,body:r}}async function _e(e={}){let{status:t,body:n}=await At("cloud-device-start",{client:e});return t===429&&y("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&y("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)&&y("server",n.error??`could not start a pairing (${t})`),n}async function je(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&y("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(c=>setTimeout(c,n));let{status:o,body:s}=await At("cloud-device-poll",{secret:e});switch(o===404&&y("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&y("usage",s.error??"that is not a pairing secret"),o!==200&&y("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":y("forbidden",s.error??"that pairing was refused in the browser");case"used":y("conflict",s.error??"that pairing has already been completed");case"expired":y("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 Je=C(()=>{"use strict";j();H()});function le(e){Ee=e}function jt(e){_t=e}function k(e){return Et?`\x1B[2m${e}\x1B[0m`:e}function S(e){return Et?`\x1B[1m${e}\x1B[0m`:e}function h(e,t){if(Ee){process.stdout.write(`${JSON.stringify(e,null,2)}
5
- `);return}t()}function i(e){!Ee&&!_t&&process.stderr.write(`${e}
6
- `)}function Y(e,t,n){process.stderr.write(`${t}
7
- `),Ee&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
8
- `)}function R(e){process.stderr.write(`${e}
9
- `)}function M(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
10
- `)}function X(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,c="second";for(let[l,u]of o){if(s<l)break;s=Math.round(s/l),c=u}let a=`${s} ${c}${s===1?"":"s"}`;return n?`in ${a}`:`${a} ago`}var Ee,_t,Et,D=C(()=>{"use strict";Ee=!1,_t=!1;Et=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Rt,randomBytes as Ve}from"node:crypto";import{createServer as Dt}from"node:http";import{spawn as Qn}from"node:child_process";function Re(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function De(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.":lr(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
2
+ var ms=Object.defineProperty;var $=(e,t)=>()=>(e&&(t=e(e=0)),t);var bn=(e,t)=>{for(var n in t)ms(e,n,{get:t[n],enumerable:!0})};function Sn(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]=gs(o);if(fs.has(s)){let i=a??e[++r];if(i===void 0)throw new w(`--${s} needs a value`);n[s]=i;continue}if(a!==void 0)throw new w(`--${s} does not take a value`);if(!ps.has(s))throw new w(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function gs(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function A(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function U(e,t){return e.flags[t]===!0}function Ee(e,t){let n=A(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new w(`--${t} must be a whole number, not "${n}"`);return r}var w,fs,ps,le=$(()=>{"use strict";w=class extends Error{},fs=new Set(["name","at","region","ref","file","provider","domain","expires","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint"]);ps=new Set(["json","window","help","version","force","no-verify-jwt","stdin","quiet","status","no-wait","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history"])});import{chmodSync as hs,existsSync as kn,mkdirSync as xn,readFileSync as vn,rmSync as ys,writeFileSync as Pn}from"node:fs";import{homedir as Fe}from"node:os";import{dirname as ht,join as En,parse as ws,resolve as bs}from"node:path";function yt(e=Fe()){return En(e,".snoutdata","auth.json")}function _n(e=Fe()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=yt(e);if(!kn(n))return null;try{let r=JSON.parse(vn(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function z(e,t=Fe()){let n=yt(t);xn(ht(n),{recursive:!0}),Pn(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),hs(n,384)}function Tn(e=Fe()){ys(yt(e),{force:!0})}function $n(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function An(e){return En(e,".snoutdata","project.json")}function Ss(e){let t=bs(e),n=ws(t).root;for(;;){let r=An(t);if(kn(r))try{let o=JSON.parse(vn(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=ht(t)}}function Be(e,t){let n=An(e);return xn(ht(n),{recursive:!0}),Pn(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function He(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Ss(e.cwd??process.cwd())?.ref??null}var pe=$(()=>{"use strict"});function qe(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 ks){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 ge(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 Rn(e){wt=e}function te(){return(wt??qe({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function he(){return(wt??qe({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var ks,wt,ye=$(()=>{"use strict";ks=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];wt=null});import{existsSync as xs,readFileSync as vs}from"node:fs";import{homedir as bt,hostname as Ps}from"node:os";import{join as we}from"node:path";function Es(e,t,n){if(t==="win32"){let r=n.APPDATA??we(bt(),"AppData","Roaming");return we(r,e)}return t==="darwin"?we(bt(),"Library","Application Support",e):we(n.XDG_CONFIG_HOME??we(bt(),".config"),e)}function Nn(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>we(Es(n,e,t),"mcp.json"))}function _s(e=process.platform,t=process.env){return Nn(e,t)[0]}function Ts(e=_s()){if(!xs(e))return null;try{let t=JSON.parse(vs(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function $s(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}async function Je(e){if(e!==void 0)return On(e);let t="no-config";for(let n of Nn()){let r=Ts(n);if(!r)continue;let o=await On(r);if(o.available)return o;(o.skip!=="not-running"||t==="no-config")&&(t=o.skip)}return{available:!1,skip:t}}async function On(e){let t=$s(e);if(t)return{available:!1,skip:t};let n=e,r;try{let o=await fetch(`http://127.0.0.1:${n.port}/cli/whoami`,{headers:{Authorization:`Bearer ${n.token}`},signal:AbortSignal.timeout(As)});if(!o.ok)return{available:!1,skip:"not-running"};r=await o.json()}catch{return{available:!1,skip:"not-running"}}return r.signedIn?r.canMintToken?{available:!0,who:r,config:n}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function Dn(e,t){let n=await fetch(`http://127.0.0.1:${e.port}/cli/token`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"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??`the app refused (${n.status})`}}function We(){return Ps()}async function St(e,t,n,r){let o=await fetch(`http://127.0.0.1:${e.port}/mcp`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"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??"the app refused");return s.result}async function In(e){return await St(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await St(e,"tools/list",{},3e3)).tools??[]}async function jn(e,t,n){return St(e,"tools/call",{name:t,arguments:n},12e4)}var As,Ke=$(()=>{"use strict";As=1500});function P(e,t,n){throw new q(e,t,n)}function Ve(e){return e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e>=500?"server":"failed"}function Mn(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(Rs.has(o))return"network";if(o==="ABORT_ERR"||o==="ETIMEDOUT")return"timeout"}return e instanceof Error&&/fetch failed|network|socket hang up/i.test(e.message)?"network":"failed"}var ne,q,Rs,re=$(()=>{"use strict";ne={ok:0,failed:1,usage:2,"not-signed-in":3,"not-ready":4,forbidden:5,"not-found":6,conflict:7,quota:8,network:9,timeout:10,"tool-missing":127,server:1},q=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return ne[this.code]}};Rs=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});async function Ln(e,t){let n=await fetch(`${G}/functions/v1/${e}`,{method:"POST",headers:{apikey:Y,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return{status:n.status,body:r}}async function Ge(e={}){let{status:t,body:n}=await Ln("cloud-device-start",{client:e});return t===429&&P("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&P("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)&&P("server",n.error??`could not start a pairing (${t})`),n}async function ze(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&P("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 Ln("cloud-device-poll",{secret:e});switch(o===404&&P("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&P("usage",s.error??"that is not a pairing secret"),o!==200&&P("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":P("forbidden",s.error??"that pairing was refused in the browser");case"used":P("conflict",s.error??"that pairing has already been completed");case"expired":P("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 kt=$(()=>{"use strict";M();re()});function _e(e){Ye=e}function Fn(e){Un=e}function x(e){return Bn?`\x1B[2m${e}\x1B[0m`:e}function _(e){return Bn?`\x1B[1m${e}\x1B[0m`:e}function h(e,t){if(Ye){process.stdout.write(`${JSON.stringify(e,null,2)}
5
+ `);return}t()}function l(e){!Ye&&!Un&&process.stderr.write(`${e}
6
+ `)}function ue(e,t,n){process.stderr.write(`${t}
7
+ `),Ye&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
8
+ `)}function J(e){process.stderr.write(`${e}
9
+ `)}function L(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
10
+ `)}function X(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[u,d]of o){if(s<u)break;s=Math.round(s/u),a=d}let i=`${s} ${a}${s===1?"":"s"}`;return n?`in ${i}`:`${i} ago`}var Ye,Un,Bn,F=$(()=>{"use strict";Ye=!1,Un=!1;Bn=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Jn,randomBytes as xt}from"node:crypto";import{createServer as Ns}from"node:http";import{createInterface as Ds}from"node:readline/promises";import{spawn as Is}from"node:child_process";function Xe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Hn(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.":Ys(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
11
11
  <meta name="viewport" content="width=device-width,initial-scale=1"></head>
12
12
  <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">
13
13
  <div style="text-align:center;max-width:460px;padding:40px 24px">
@@ -19,44 +19,184 @@ var Rn=Object.defineProperty;var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var Dn=(e,t)=>{f
19
19
  <p style="color:#8f887e;margin:0;font-size:15px;line-height:1.5">${o}</p>
20
20
  ${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>'}
21
21
  </div>
22
- </body></html>`}function Nt(){let e=Re(Ve(32)),t=Re(Rt("sha256").update(e).digest());return{verifier:e,challenge:t}}function er(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 It(e){let{command:t,args:n,verbatim:r}=er(process.platform,e),o=Qn(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function or(e){let t=e??Re(Ve(32));return{raw:t,hashed:Rt("sha256").update(t).digest("hex")}}function sr(e){return`${tr}?${new URLSearchParams({client_id:Ct,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 ar(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?n.searchParams.get("state")!==t?{kind:"error",message:"the browser came back with a state that did not match"}:{kind:"ok",code:o}:{kind:"error",message:"the browser came back without a code"}}async function ir(e,t,n){let r=await fetch(nr,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Ct,client_secret:rr,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 cr(e){let{verifier:t,challenge:n}=Nt(),{raw:r,hashed:o}=or(),s=Re(Ve(16)),{code:c,redirectUri:a}=await new Promise((d,m)=>{let g="",p=Dt((A,w)=>{let _=ar(A.url??"/",s);if(_.kind==="ignore"){w.writeHead(404).end();return}w.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),w.end(_.kind==="ok"?De("ok"):De("failed",_.message)),p.close(),_.kind==="ok"?d({code:_.code,redirectUri:g}):m(new Error(_.message))});p.listen(0,"127.0.0.1",()=>{let A=p.address();g=`http://127.0.0.1:${typeof A=="object"&&A?A.port:0}`;let _=sr({redirectUri:g,challenge:n,hashedNonce:o,state:s});process.stdout.write(`${_}
23
- `),e.noBrowser?i("Open that in a browser, on any machine, and sign in."):(i("Opening that in your browser. If nothing opens, paste it into one."),It(_))}),p.on("error",m),setTimeout(()=>{p.close(),m(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()}),l=await ir(c,t,a),u=await fetch(`${U}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:J,"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:l,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 Lt(await u.json())}function Lt(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return L(n),n}async function Ne(e={}){let t=e.provider??"google";if(t==="google")return cr(e);let{verifier:n,challenge:r}=Nt(),o=await new Promise((c,a)=>{let l=Dt((d,m)=>{let g=new URL(d.url??"/","http://127.0.0.1"),p=g.searchParams.get("code"),E=g.searchParams.get("error_description")??g.searchParams.get("error");m.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),m.end(p?De("ok"):De("failed",E??"No code came back.")),l.close(),p?c(p):a(new Error(E??"the browser came back without a code"))});l.listen(0,"127.0.0.1",()=>{let d=l.address(),g=`http://127.0.0.1:${typeof d=="object"&&d?d.port:0}/callback`,p=new URL(`${U}/auth/v1/authorize`);p.searchParams.set("provider",t),p.searchParams.set("redirect_to",g),p.searchParams.set("code_challenge",r),p.searchParams.set("code_challenge_method","s256"),p.searchParams.set("prompt","select_account"),process.stdout.write(`${p.toString()}
24
- `),e.noBrowser?i("Open that in a browser, on any machine, and sign in."):(i("Opening that in your browser. If nothing opens, paste it into one."),It(p.toString()))}),setTimeout(()=>{l.close(),a(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()}),s=await fetch(`${U}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:J,"Content-Type":"application/json"},body:JSON.stringify({auth_code:o,code_verifier:n})});if(!s.ok)throw new Error(`could not exchange the code: ${(await s.text()).slice(0,200)}`);return Lt(await s.json())}function lr(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var tr,nr,Ct,rr,ze=C(()=>{"use strict";j();Q();D();tr="https://accounts.google.com/o/oauth2/v2/auth",nr="https://oauth2.googleapis.com/token",Ct="1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com",rr="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"});import{createInterface as ur}from"node:readline/promises";async function He(e){let t=ur({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 dr(){let e=await Oe();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&i(k("The SnoutData app is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(i(""),i(`The SnoutData app is open here, signed in as ${S(t)}.`),i(k("It can create an access token for this terminal. Your sign-in itself is not handed over,")),i(k("and you can revoke the token later with `snoutdata tokens revoke`.")),!await He("Use it?"))return null;i("Asking the app. Approve it there.");let n=await xt(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return i(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return L(r),i(`Signed in with "${n.name}".`),r}async function pr(){if(!await He("Sign in with a browser?"))return null;try{let e=await Ne({});return i(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return i(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function mr(){if(!await He("Sign in by typing a code into a browser on another machine?"))return null;let e=await _e({name:Pe(),platform:process.platform});i(""),i(` Open ${S(e.verifyUrl)}`),i(` Type ${S(e.code)}`),i(""),i(k("The code is typed rather than in the link on purpose: it is what proves the person")),i(k("at the browser is the person at this terminal. Waiting\u2026"));let t=await je(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return L(n),i(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Ut(){if(!ee())return null;let e=await dr();if(e)return e;let t=await pr();return t||mr()}var Mt=C(()=>{"use strict";Q();Ae();Je();ce();ze();D()});var oe={};Dn(oe,{ANON_KEY:()=>J,ApiError:()=>B,FUNCTIONS:()=>Bt,NotSignedIn:()=>V,SUPABASE_URL:()=>U,call:()=>b,connection:()=>N,exportStatus:()=>re,listProjects:()=>q,listTeams:()=>Ie,requestExport:()=>ue,waitForReady:()=>de,whoami:()=>ne});function fr(){let e=ve();return e?`not signed in, and ${$e(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token from \`snoutdata tokens create\`.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function gr(){let e=gt();if(e||(e=await Ut()),!e)throw new V;if(!yt(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${U}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:J,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new V;let n=await t.json(),r={accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at,email:e.email};return L(r),r.accessToken}async function b(e,t={},n="POST"){let r=await gr(),o=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",s=await fetch(`${Bt}/${e}${o}`,{method:n,headers:{Authorization:`Bearer ${r}`,apikey:J,"Content-Type":"application/json"},body:n==="GET"?void 0:JSON.stringify(t)}),c=await s.text(),a;try{a=c?JSON.parse(c):{}}catch{throw new B(s.status,c.slice(0,300)||s.statusText)}if(!s.ok){let l=typeof a=="object"&&a&&"error"in a?String(a.error):`${s.status} ${s.statusText}`;throw s.status===401?new V(l):new B(s.status,l)}return a}async function ne(){return b("cloud-whoami")}async function q(){return b("cloud-project-list")}async function N(e){return b("cloud-project-connection",{ref:e})}async function Ie(){return b("cloud-team-list")}async function ue(e){return b("cloud-project-export",{ref:e})}async function re(e){return b("cloud-project-export",{ref:e},"GET")}async function de(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await q(),o=r.find(s=>s.ref===e);if(!o)throw new B(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new B(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var U,J,Bt,B,V,j=C(()=>{"use strict";Q();ce();Mt();U=process.env.SNOUTDATA_SUPABASE_URL??"https://krlhuuvhhgkwfsutvxtb.supabase.co",J=process.env.SNOUTDATA_ANON_KEY??"sb_publishable_B0gbZinAncINGbDGOcP9rg_odcbriG3",Bt=`${U}/functions/v1`,B=class extends Error{status;constructor(t,n){super(n),this.status=t}},V=class extends Error{constructor(t){super(t??fr())}}});var T=class extends Error{},Nn=new Set(["name","region","ref","file","provider","expires","timeout","out","team","dir","days","schema","db-url","postgrest-version","port"]);function lt(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,c]=Cn(o);if(Nn.has(s)){let a=c??e[++r];if(a===void 0)throw new T(`--${s} needs a value`);n[s]=a;continue}if(c!==void 0)throw new T(`--${s} does not take a value`);if(!In.has(s))throw new T(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}var In=new Set(["json","help","version","force","quiet","status","no-wait","no-browser","device","env","dry-run","out-of-order","allow-delete","history"]);function Cn(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function $(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function I(e,t){return e.flags[t]===!0}function ie(e,t){let n=$(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new T(`--${t} must be a whole number, not "${n}"`);return r}j();H();ce();Q();Je();Ae();D();ze();j();j();Q();D();async function qt(){let{projects:e,allowance:t}=await q();h({projects:e,allowance:t},()=>{if(e.length===0){i('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${M([["REF","NAME","STATE","REGION","LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?S("read-only"):n.state==="ready"?n.state:S(n.state),n.region,X(n.lastConnectionAt)])])}
25
- `)})}async function Ye(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;i(`Created ${n}.`);let r=t.project;e.wait&&(i("Waiting for it to start\u2026"),r=await de(n,{timeoutMs:e.timeoutMs,onTick:o=>i(k(` ${o}`))})),e.link&&Se(e.link,{ref:n,name:r.name}),h({project:r,password:t.password,uri:`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`},()=>{process.stdout.write(`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require
26
- `),i(""),i(`Ready. ${S(n)} in ${r.region}.`),i("The password is stored for you: `snoutdata db url` prints this again.")})}async function Ft(e,t){let n=`cloud-project-${e}`,r=await b(n,{ref:t});h(r,()=>{i(r.changed?`${t}: ${e}d.`:`${t} was already ${e==="delete"?"deleted":`${e}d`}.`)})}async function Wt(e){let t=await b("cloud-project-reset-password",{ref:e});h(t,()=>{process.stdout.write(`${t.password}
27
- `),i(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Gt(){let{teams:e}=await Ie();h({teams:e},()=>{if(e.length===0){i("You are not on any teams.");return}process.stdout.write(`${M([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
28
- `)})}async function Kt(e){let{teams:t}=await Ie(),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}j();D();async function Jt(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays});h(t,()=>{process.stdout.write(`${t.token}
29
- `),i(""),i(`${S(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),i("This is the only time it is shown: only its hash is stored."),i(k(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function Vt(){let{tokens:e}=await b("cloud-token-list");h({tokens:e},()=>{if(e.length===0){i("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${M([["PREFIX","NAME","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?S(t.prefix):t.prefix,t.name,t.revokedAt?"revoked":t.live?"live":"expired",X(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
30
- `)})}async function zt(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);h(n,()=>{i(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}j();D();async function Ht(e){let t=await N(e);h({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){i(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${Xt(t.anonKey,t.serviceRoleKey)}
31
- `),wr(t.keysIssuedAt)})}async function Yt(e,t){if(!t.force)throw i(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),i("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new T("keys rotate needs --force, because this cannot be undone");let n=await b("cloud-project-rotate-jwt",{ref:e});h(n,()=>{process.stdout.write(`${Xt(n.anonKey,n.serviceRoleKey)}
32
- `),i(""),i(n.breaks),i(k(" snoutdata keys --json to read them again later"))})}function Xt(e,t){return M([["KEY","VALUE"],["anon",e],["service_role",t]])}function wr(e){i(""),i(`${S("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),i(`${S("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&i(k(` issued ${e.slice(0,10)}`))}import{spawn as Ze}from"node:child_process";import{appendFileSync as kr,existsSync as nn,readFileSync as Sr,writeFileSync as tn}from"node:fs";import{join as xr}from"node:path";import{open as rn}from"node:fs/promises";function Zt(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 Qt(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 en(e){let t=e.split(`
33
- `).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}}j();H();ce();j();D();async function on(e){try{let{projects:t}=await q(),n=t.find(r=>r.ref===e);n?.readOnly?R(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&R(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Qe(e){await on(e);let t=await N(e);h(t,()=>{process.stdout.write(`${t.uri}
34
- `)})}async function sn(e,t){if(t.length===0&&!ee()){let o=ve();y("usage",`db psql with no statements opens an interactive session, and ${o?$e(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await on(e);let n=await N(e);n.wakesInstantly||i("This project is paused; the connection will wake it, which takes a few seconds.");let r=Ze("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,PGSSLMODE:"require"}});return new Promise((o,s)=>{r.on("error",c=>{if(c.code==="ENOENT"){s(new G("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new G("failed",String(c)))}),r.on("close",c=>o(c??0))})}function an(e,t,n="DATABASE_URL"){let r=xr(e,".env"),o=`${n}=${t}`;if(!nn(r))return tn(r,`${o}
35
- `),{path:r,replaced:!1};let s=Sr(r,"utf8"),c=new RegExp(`^${n}=.*$`,"m");return c.test(s)?(tn(r,s.replace(c,o)),{path:r,replaced:!0}):(kr(r,s.endsWith(`
22
+ </body></html>`}function vt(){let e=Xe(xt(32)),t=Xe(Jn("sha256").update(e).digest());return{verifier:e,challenge:t}}function js(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 Cs(e){let{command:t,args:n,verbatim:r}=js(process.platform,e),o=Is(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function Fs(e){let t=e??Xe(xt(32));return{raw:t,hashed:Jn("sha256").update(t).digest("hex")}}function Bs(e){return`${Ms}?${new URLSearchParams({client_id:Wn,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 Hs(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 Pt(e){return new Promise((t,n)=>{let r="",o=Ns((a,i)=>{let u=Hs(a.url??"/",e.state);if(u.kind==="ignore"){i.writeHead(404).end();return}i.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),i.end(u.kind==="ok"?Hn("ok"):Hn("failed",u.message)),o.close(),u.kind==="ok"?t({code:u.code,redirectUri:r}):n(new Error(u.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(u=>{process.stdout.write(`${u}
23
+ `),e.noBrowser?l("Open that in a browser, on any machine, and sign in."):(l("Opening that in your browser. If nothing opens, paste it into one."),Cs(u))},u=>{o.close(),n(u)})}),o.on("error",n),setTimeout(()=>{o.close(),n(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()})}async function Kn(e,t){let n=await fetch(`${G}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:Y,"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 Vn(await n.json())}async function qs(e,t,n){let r=await fetch(Ls,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Wn,client_secret:Us,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 Js(e){let{verifier:t,challenge:n}=vt(),{raw:r,hashed:o}=Fs(),s=Xe(xt(16)),{code:a,redirectUri:i}=await Pt({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:m=>Bs({redirectUri:m,challenge:n,hashedNonce:o,state:s})}),u=await qs(a,t,i),d=await fetch(`${G}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:Y,"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:u,nonce:r})});if(!d.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await d.text()).slice(0,200)}`);return Vn(await d.json())}function Vn(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return z(n),n}function qn(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 Ws(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 Ks(e,t,n){let r=Ws(t);return e===404||/no sso provider/i.test(r)?new q("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 q("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new q(Ve(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Vs(e){let t=await fetch(`${G}/auth/v1/sso`,{method:"POST",headers:{apikey:Y,"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 Ks(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new q("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 Gs(e){if(e!==void 0){let r=qn(e);if(!r)throw new w(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!te()){let r=he();throw new w(`login --sso needs --domain <your work email or company domain>${r?`, because ${ge(r)}`:""}`)}let t=Ds({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new w("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=qn(o);if(s)return s;l("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new w("no domain to sign in with: pass --domain <your work email or company domain>")}async function zs(e){let t=await Gs(e.domain),{verifier:n,challenge:r}=vt();l(`Signing in with the identity provider for ${_(t)}.`);let{code:o}=await Pt({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Vs({domain:t,redirectTo:s,challenge:r})});return Kn(o,n)}async function Ze(e={}){if(e.sso)return zs(e);let t=e.provider??"google";if(t==="google")return Js(e);let{verifier:n,challenge:r}=vt(),{code:o}=await Pt({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${G}/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 Kn(o,n)}function Ys(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Ms,Ls,Wn,Us,Et=$(()=>{"use strict";M();le();pe();re();ye();F();Ms="https://accounts.google.com/o/oauth2/v2/auth",Ls="https://oauth2.googleapis.com/token",Wn="1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com",Us="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"});import{createInterface as Xs}from"node:readline/promises";async function _t(e){let t=Xs({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 Zs(){let e=await Je();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&l(x("The SnoutData app is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(l(""),l(`The SnoutData app is open here, signed in as ${_(t)}.`),l(x("It can create an access token for this terminal. Your sign-in itself is not handed over,")),l(x("and you can revoke the token later with `snoutdata tokens revoke`.")),!await _t("Use it?"))return null;l("Asking the app. Approve it there.");let n=await Dn(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return l(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return z(r),l(`Signed in with "${n.name}".`),r}async function Qs(){if(!await _t("Sign in with a browser?"))return null;try{let e=await Ze({});return l(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return l(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function ea(){if(!await _t("Sign in by typing a code into a browser on another machine?"))return null;let e=await Ge({name:We(),platform:process.platform});l(""),l(` Open ${_(e.verifyUrl)}`),l(` Type ${_(e.code)}`),l(""),l(x("The code is typed rather than in the link on purpose: it is what proves the person")),l(x("at the browser is the person at this terminal. Waiting\u2026"));let t=await ze(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return z(n),l(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Gn(){if(!te())return null;let e=await Zs();if(e)return e;let t=await Qs();return t||ea()}var zn=$(()=>{"use strict";pe();Ke();kt();ye();Et();F()});var de={};bn(de,{ANON_KEY:()=>Y,ApiError:()=>Z,FUNCTIONS:()=>Yn,NotSignedIn:()=>oe,SUPABASE_URL:()=>G,call:()=>S,connection:()=>W,exportStatus:()=>Se,listProjects:()=>V,listTeams:()=>Qe,requestExport:()=>Te,waitForReady:()=>$e,whoami:()=>be});function ta(){let e=he();return e?`not signed in, and ${ge(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token from \`snoutdata tokens create\`.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function na(){let e=_n();if(e||(e=await Gn()),!e)throw new oe;if(!$n(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${G}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:Y,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new oe;let n=await t.json(),r={accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at,email:e.email};return z(r),r.accessToken}async function S(e,t={},n="POST"){let r=await na(),o=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",s=await fetch(`${Yn}/${e}${o}`,{method:n,headers:{Authorization:`Bearer ${r}`,apikey:Y,"Content-Type":"application/json"},body:n==="GET"?void 0:JSON.stringify(t)}),a=await s.text(),i;try{i=a?JSON.parse(a):{}}catch{throw new Z(s.status,a.slice(0,300)||s.statusText)}if(!s.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${s.status} ${s.statusText}`;throw s.status===401?new oe(u):new Z(s.status,u)}return i}async function be(){return S("cloud-whoami")}async function V(){return S("cloud-project-list")}async function W(e){return S("cloud-project-connection",{ref:e})}async function Qe(){return S("cloud-team-list")}async function Te(e){return S("cloud-project-export",{ref:e})}async function Se(e){return S("cloud-project-export",{ref:e},"GET")}async function $e(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await V(),o=r.find(s=>s.ref===e);if(!o)throw new Z(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new Z(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var G,Y,Yn,Z,oe,M=$(()=>{"use strict";pe();ye();zn();G=process.env.SNOUTDATA_SUPABASE_URL??"https://accounts.snoutdata.com",Y=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpmN2c2NW1iOGZhcmEiLCJyb2xlIjoiYW5vbiIsImlhdCI6MTc4OTUxNTUyNCwiZXhwIjoyMTA0ODc1NTI0fQ.TiFJbsmmELR8FIje8lbg-95UJFtoj7IwmC2PZtWPAk8",Yn=`${G}/functions/v1`,Z=class extends Error{status;constructor(t,n){super(n),this.status=t}},oe=class extends Error{constructor(t){super(t??ta())}}});function Xt(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function qr(e,t,n){let r="";for(;r.length<n;)r+=Xt(e,t);return r}var Yt,Hr,Jr=$(()=>{"use strict";Yt="0123456789abcdefghjkmnpqrstvwxyz",Hr="abcdefghjkmnpqrstvwxyz"});function Vr(e){return Xt(e,Wr)+qr(e,Yt,Kr-1)}function yi(e){return hi.test(e)}function K(e){if(!yi(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function D(e,t=wi){return K(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Wr,Kr,hi,wi,se=$(()=>{"use strict";Jr();Wr=Hr,Kr=13,hi=new RegExp(`^[${Wr}][${Yt}]{${Kr-1}}$`);wi="db.snoutdata.com"});var Gr=$(()=>{"use strict";se()});function Yr(e,t){let n=zr(e,7),r=zr(t,24);return Math.max(2,Math.ceil(n*24/r))}function zr(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Zt=$(()=>{"use strict"});function Xr(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Qt=$(()=>{"use strict"});var Bu,Zr=$(()=>{"use strict";se();Bu=7*24});var qu,Qr=$(()=>{"use strict";qu=7*24*60*60*1e3});var bi,eo=$(()=>{"use strict";Qr();bi=60*60*1e3});var to=$(()=>{"use strict"});var no=$(()=>{"use strict"});var Xu,Zu,ro=$(()=>{"use strict";Xu=2*1024*1024,Zu=64*1024});var oo,so,Si,ao=$(()=>{"use strict";oo="/snoutfn",so="/_snoutpod/health",Si=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
24
+ // every time the agent starts, from packages/snoutpod/src/host/functionMain.ts.
25
+ const MOUNT = ${JSON.stringify(oo)};
26
+ const HEALTH = ${JSON.stringify(so)};
27
+
28
+ /**
29
+ * Manifests, cached by the file's modification time.
30
+ *
31
+ * The agent writes a manifest by rename, so a reader either sees the whole old one or the
32
+ * whole new one. mtime is what says which \u2014 cheaper than re-reading and re-parsing a file
33
+ * with secrets in it on every invocation, and exact, unlike a TTL.
34
+ */
35
+ const manifests = new Map();
36
+
37
+ async function manifestFor(ref) {
38
+ const path = MOUNT + '/projects/' + ref + '.json';
39
+ let stat;
40
+ try {
41
+ stat = await Deno.stat(path);
42
+ } catch {
43
+ return null;
44
+ }
45
+ const stamp = stat.mtime ? stat.mtime.getTime() : 0;
46
+ const held = manifests.get(ref);
47
+ if (held && held.stamp === stamp) {
48
+ return held.manifest;
49
+ }
50
+ try {
51
+ const manifest = JSON.parse(await Deno.readTextFile(path));
52
+ manifests.set(ref, { stamp, manifest });
53
+ return manifest;
54
+ } catch (error) {
55
+ console.error('manifest for ' + ref + ' could not be read: ' + (error?.message ?? error));
56
+ return null;
57
+ }
58
+ }
59
+
60
+ function refuse(status, message, hint) {
61
+ // The same JSON shape the front door's refusals use, so a customer sees one error
62
+ // format from this system rather than one per component.
63
+ return new Response(JSON.stringify({ message, hint: hint ?? undefined }), {
64
+ status,
65
+ headers: { 'content-type': 'application/json' }
66
+ });
67
+ }
68
+
69
+ /**
70
+ * The function a path names.
71
+ *
72
+ * The front door strips \`/functions/v1\`, so what arrives is \`/<name>\` or
73
+ * \`/<name>/anything\`. The rule is the same one \`functionNameFromPath\` applies in the
74
+ * control package -- letters, digits, hyphens and underscores, no dot and no slash --
75
+ * because the door decides whether THIS function may be called without a key using that
76
+ * function, and a name that meant two different things in the two places is a way past it.
77
+ */
78
+ function functionName(pathname) {
79
+ const first = pathname.replace(/^\\/+/, '').split('/')[0] ?? '';
80
+ return /^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(first) ? first : null;
81
+ }
82
+
83
+ Deno.serve(async (req) => {
84
+ const url = new URL(req.url);
85
+
86
+ // Before anything else, and needing no project: this is what the agent polls and what
87
+ // decides whether the front door believes the runtime is up.
88
+ if (url.pathname === HEALTH) {
89
+ return new Response('ok\\n', { headers: { 'content-type': 'text/plain' } });
90
+ }
91
+
92
+ // Whose request this is, written by the front door from the hostname it resolved.
93
+ // NOT from anything the client sent: a shared server that took the tenant from a
94
+ // client's own header would let anybody address anybody (the Phase B finding).
95
+ const ref = req.headers.get('x-snoutdata-ref');
96
+ if (!ref) {
97
+ return refuse(400, 'This request did not arrive through the front door.', 'Call https://<ref>.api.snoutdata.com/functions/v1/<name>');
98
+ }
99
+
100
+ const name = functionName(url.pathname);
101
+ if (!name) {
102
+ return refuse(404, 'No function was named in this request.', 'The path is /functions/v1/<name>');
103
+ }
104
+
105
+ const manifest = await manifestFor(ref);
106
+ const deployed = manifest?.functions?.find((one) => one.name === name);
107
+ if (!deployed) {
108
+ // The same answer for "this project has deployed nothing" and "not that one".
109
+ // A project's function names are not a secret, but neither are they a list to
110
+ // hand out one 404 at a time to somebody walking the namespace.
111
+ return refuse(404, 'There is no function called ' + name + ' in this project.', 'Deploy it with: snoutdata functions deploy ' + name);
112
+ }
113
+
114
+ const servicePath = MOUNT + '/bundles/' + deployed.digest;
115
+ const limits = manifest.limits ?? {};
116
+ try {
117
+ const worker = await EdgeRuntime.userWorkers.create({
118
+ servicePath,
119
+ memoryLimitMb: limits.memoryMb ?? 128,
120
+ workerTimeoutMs: limits.wallMs ?? 10000,
121
+ cpuTimeSoftLimitMs: limits.cpuMs ?? 2000,
122
+ cpuTimeHardLimitMs: limits.hardCpuMs ?? 3500,
123
+ noModuleCache: false,
124
+ importMapPath: null,
125
+ // THE WHOLE OF WHAT THIS WORKER MAY SEE. A user worker does not inherit the
126
+ // main worker's environment (measured), so this list is not a filter over
127
+ // something larger -- it is everything.
128
+ envVars: Object.entries(manifest.env ?? {}),
129
+ // False, so identical requests share a warm isolate. The isolate is still one
130
+ // project's: \`servicePath\` is the digest of THIS project's bundle, and two
131
+ // projects that deployed byte-identical code have different digests because a
132
+ // bundle is stored per project (migration 041).
133
+ forceCreate: false
134
+ });
135
+ return await worker.fetch(req);
136
+ } catch (error) {
137
+ const detail = error?.message ?? String(error);
138
+ console.error(ref + '/' + name + ': ' + detail);
139
+ // 500 and the runtime's own words. A function that ran out of time or memory
140
+ // should hear which -- the alternative is the front door's 502, which says only
141
+ // that something upstream did not answer and cannot be acted on.
142
+ return refuse(500, 'This function could not be run.', detail);
143
+ }
144
+ });
145
+ `});function en(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function at(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 xi(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 xi(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(en("the subnet",n,io),en("the sink",r,ki),!lo(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!lo(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",`${en("a denied prefix",s,io)},${r}`);return o.push(e.name),o}function co(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function lo(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(co(e)&s)>>>0===(co(n??"")&s)>>>0}function uo(e){return e?.internal===!0}var io,ki,tn=$(()=>{"use strict";io=/^((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])$/,ki=/^((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 vi,it=$(()=>{"use strict";vi=7*24*60*60});var mo=$(()=>{"use strict";se();ro();ao();tn();it()});function po(e){let t=[];for(let n of e.split(`
146
+ `)){let r=n.trim();if(!r)continue;let[o,s,a,i]=r.split("|");!o||s===void 0||t.push({name:o,active:s==="t",walStatus:a??"",inactiveSeconds:i?Number(i):null})}return t}var fo,nn=$(()=>{"use strict";fo="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var Sd,kd,go=$(()=>{"use strict";se();it();Sd=6*60*60,kd=5*60});var ho=$(()=>{"use strict"});var yo,em,tm,nm,rm,wo=$(()=>{"use strict";Gr();Zt();Qt();Zr();eo();to();no();mo();nn();go();ho();it();yo=120,em=15*60*1e3,tm=60*60*1e3,nm=60*60*1e3,rm=10*60*1e3});import{execFile as Ei}from"node:child_process";function bo(){return(e,t,n={})=>new Promise((r,o)=>{let s=Ei(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,i,u)=>{if(a&&typeof a.code=="string"){o(a);return}let d=a&&typeof a.code=="number"?a.code:0;r({code:d,stdout:String(i),stderr:String(u)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function O(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
147
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var ct=$(()=>{"use strict"});import{readdir as _i,readFile as cm}from"node:fs/promises";async function So(e,t,n={}){let r=n.binary??"podman",o=D(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),u=(Array.isArray(a)?a[0]:a)?.Mountpoint;return u?await(n.look??Ti)(u)!=="empty":!1}async function Ti(e){try{return(await _i(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var ko=$(()=>{"use strict";se();ct()});function xo(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}function Po(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=${xo("the access key",e.key,Ai)}`),n.push("--env",vo)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${xo("the endpoint",e.endpoint,$i)}`),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 Eo(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");return{[vo]:e.secret}}var vo,$i,Ai,_o=$(()=>{"use strict";vo="SNOUTPOD_S3_SECRET",$i=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Ai=/^[A-Za-z0-9/+=_.-]{1,256}$/});import{createHash as Ri,randomBytes as Oi}from"node:crypto";import{access as Ni,constants as Di}from"node:fs/promises";import{delimiter as Ii,join as ji}from"node:path";function an(e){if(!Mi.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Ie(e){if(!Li.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function Ui(e,t,n){return`postgres://${Ie(t)}:${n}@127.0.0.1:5432/${K(e)}`}function To(e,t,n,r){let o={...t.env};if(t.dbUriEnv&&t.dbUriRole){let s=n.get(t.dbUriRole);if(s===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]=Ui(e.ref,t.dbUriRole,s)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function $o(e,t){return`${D(e).pod}-${an(t)}`}function lt(e){return`snoutpod-host-${an(e)}`}function Fi(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return Ri("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 Bi(e,t){let n=[],r=on(t);for(let o of e??[])if(an(o.name),sn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){sn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`)}return n}function on(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 sn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Ro(e){let t=[];for(let n of Object.keys(e)){if(!Hi.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function qi(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Do.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(sn(e.hostPort,`${e.name}: hostPort`),["--label",`${cn}=${e.hostPort}:${t}`])}function Ji(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||!Do.test(r)?null:{port:n,path:r}}async function Ki(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Ni(o,Di.X_OK));for(let o of n.split(Ii))if(o)try{let s=ji(o,e);return await r(s),s}catch{}return null}function Vi(){return["\\set ON_ERROR_STOP 0","select 'create extension if not exists pg_prewarm' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","with candidate as ("," select c.oid, 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(pg_prewarm(oid)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4;",""].join(`
148
+ `)}function zi(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:Oo;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Oo)}`),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 Yi(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Wi;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Ki(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function jo(e){let t=e.binary??"podman",n=e.exec,r=Yi(e),o=e.probe??Io;async function s(c){try{await n(t,["exec","--interactive",D(c).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",c,"--quiet","--file","-"],{timeoutMs:Gi,stdin:Vi()})}catch{}}async function a(){let c=await O(n,t,["ps","--all","--filter",`label=${ve}`,"--format","json"]),f=c.trim()?JSON.parse(c):[],p=new Map;for(let y of f){let g=y.Labels?.[ve],T=y.Labels?.[Ne];if(!g||!T)continue;let I=p.get(g)??[];I.push(T),p.set(g,I)}let k=[];for(let y of f){let g=y.Labels?.[ve];if(!g||y.Labels?.[Ne])continue;let T=Number(y.Labels?.[rn]);k.push({ref:g,status:Xi(y.State),generation:Number.isInteger(T)?T:null,hasData:await e.hasData(g),services:p.get(g)??[]})}return k}async function i(c){let f=D(c.ref),p={SNOUTPOD_OWNER_PASSWORD:c.ownerPassword,POSTGRES_PASSWORD:c.adminPassword,...Eo(e.repository)},k=[...await r(),"run","--detach","--name",f.pod,"--label",`${ve}=${c.ref}`,"--label",`${rn}=${c.generation}`,"--userns=auto","--memory",`${c.memoryMb}m`,...Qi(c.cpus),"--restart","no",...e.network?["--network",e.network]:[],...c.port?on(e.podBind).flatMap(y=>["--publish",`${y}:${c.port}:5432`]):[],...Bi(c.services,e.podBind),"--volume",`${f.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${c.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${c.bucket}`,"--env",`SNOUTPOD_S3_REGION=${c.region}`,...zi(c.tuning).flatMap(y=>["--env",y]),"--env",`SNOUTPOD_RETENTION_FULL=${Yr(c.backup?.retentionDays,c.backup?.fullIntervalHours)}`,...Po(e.repository),c.image,"snoutpod-postgres"];await O(n,t,k,{env:p})}async function u(c){let f=await O(n,t,["ps","--all","--filter",`label=${ve}=${K(c)}`,"--filter",`label=${Ne}`,"--format","json"]),p=f.trim()?JSON.parse(f):[],k=[];for(let y of p){let g=y.Labels?.[Ne];g&&k.push({name:g,container:$o(c,g)})}return k}async function d(c){let f=[];for(let T of c.services??[])T.dbUriEnv&&T.dbUriRole&&!f.includes(T.dbUriRole)&&f.push(Ie(T.dbUriRole));let p=new Map;if(f.length===0)return p;let k={},y=[],g=[];return f.forEach((T,I)=>{let R=Oi(24).toString("base64url"),ie=`SNOUTPOD_SERVICE_PASSWORD_${I}`;p.set(T,R),k[ie]=R,y.push("--env",ie),g.push(`\\getenv service_password_${I} ${ie}`),g.push(`\\set service_role_${I} ${T}`),g.push(`alter role :"service_role_${I}" with password :'service_password_${I}';`)}),g.push(""),await O(n,t,["exec","--interactive",...y,D(c.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:k,stdin:g.join(`
149
+ `)}),p}async function m(c){for(let k of await u(c.ref))await O(n,t,["rm","--force","--ignore",k.container]);let f=await d(c),p;(c.services??[]).some(k=>k.smtpSecretEnv)&&(p=await e.smtpSecret?.().catch(()=>{}));for(let k of c.services??[]){let y=$o(c.ref,k.name),g=[...await r(),"run","--detach","--name",y,"--label",`${ve}=${c.ref}`,"--label",`${rn}=${c.generation}`,"--label",`${Ne}=${k.name}`,"--network",`container:${D(c.ref).pod}`,"--userns",`container:${D(c.ref).pod}`,"--memory",`${k.memoryMb}m`,"--restart","no",...qi(k),...Ro(To(c,k,f,p)),k.image];await O(n,t,g,{env:To(c,k,f,p)})}}return{list:a,create:i,createServices:m,async start(c){await O(n,t,["start",D(c).pod])},async startServices(c){for(let f of await u(c))await O(n,t,["start",f.container])},async applyHostServices(c){let f=[],p=await O(n,t,["ps","--all","--filter",`label=${De}`,"--format","json"]),k=p.trim()?JSON.parse(p):[],y=new Map;for(let T of k){let I=T.Labels?.[De];I&&y.set(I,{spec:T.Labels?.[Ao]??"",state:T.State??""})}for(let T of c)try{await g(T)}catch(I){f.push({name:T.name,action:"failed",reason:I instanceof Error?I.message:String(I)})}async function g(T){let I=e.hostServiceLocal?.(T,c),R=I?{...T,...I.env&&Object.keys(I.env).length>0?{env:{...T.env,...I.env}}:{},...I.volumes?{volumes:I.volumes}:{},...I.network?{network:I.network}:{}}:T,ie=lt(R.name),wn=Fi(R),ce=y.get(R.name);if(y.delete(R.name),ce&&ce.spec===wn){ce.state!=="running"&&(await O(n,t,["start",ie]),f.push({name:R.name,action:"started",reason:ce.state}));return}let ds=ce?"the spec changed":"it was not here";ce&&await O(n,t,["rm","--force","--ignore",ie]),await O(n,t,[...await r(),"run","--detach","--name",ie,"--label",`${De}=${R.name}`,"--label",`${Ao}=${wn}`,...R.healthPath?["--label",`${cn}=${R.hostPort}:${R.healthPath}`]:[],"--memory",`${R.memoryMb}m`,"--restart","no",...R.network?["--network",R.network]:e.network?["--network",e.network]:[],...(R.volumes??[]).flatMap(pt=>["--volume",pt]),...on(e.podBind).flatMap(pt=>["--publish",`${pt}:${R.hostPort}:${R.port}`]),...R.adminPort&&R.adminHostPort?["--publish",`127.0.0.1:${R.adminHostPort}:${R.adminPort}`]:[],...Ro(R.env),R.image,...R.command??[]],{env:{...R.env}}),f.push({name:R.name,action:ce?"recreated":"created",reason:ds})}for(let T of y.keys())await O(n,t,["rm","--force","--ignore",lt(T)]),f.push({name:T,action:"removed",reason:"it is no longer on the list"});return f},async hostServiceHealth(c){let f=await O(n,t,["ps","--all","--filter",`label=${De}`,"--format","json"]),p=(f.trim()?JSON.parse(f):[]).map(y=>y.Labels?.[De]??"").filter(y=>y!=="");if(p.length===0)return[];let k=Date.now()+c;for(;;){let y=[];for(let g of p)y.push(await No(n,t,g,lt(g),o));if(y.every(g=>g.ok)||Date.now()>=k)return y;await new Promise(g=>setTimeout(g,500))}},async serviceHealth(c,f){let p=await u(c);if(p.length===0)return[];let k=Date.now()+f;for(;;){let y=[];for(let g of p)y.push(await No(n,t,g.name,g.container,o));if(y.every(g=>g.ok)||Date.now()>=k)return y;await new Promise(g=>setTimeout(g,500))}},async stop(c,f){for(let p of await u(c))await O(n,t,["stop","--time",String(Ci),p.container]);await O(n,t,["stop","--time",String(f),D(c).pod])},async remove(c){for(let f of await u(c))await O(n,t,["rm","--force","--ignore",f.container]);await O(n,t,["rm","--force","--ignore",D(c).pod])},async removeData(c){await O(n,t,["volume","rm","--force",D(c).pod])},async applyOwnerPassword(c){let f=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${c.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
150
+ `);await O(n,t,["exec","--interactive","--env","SNOUTPOD_OWNER_PASSWORD",D(c.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:c.ownerPassword},stdin:f})},async applyRolePassword(c,f,p){let k=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Ie(f)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
151
+ `);await O(n,t,["exec","--interactive","--env","SNOUTPOD_ROLE_PASSWORD",D(c).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:p},stdin:k})},async ensureHostServiceDatabase(c,f,p){let k=lt(c),y=Ie(f),g=Ie(p),T=R=>["exec",k,"psql","--username",y,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",R];(await O(n,t,T(`select 1 from pg_database where datname = '${g}'`))).trim()!=="1"&&await O(n,t,T(`create database "${g}"`))},async replicationSlots(c){let f=await O(n,t,["exec",D(c).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",c,"-tA","-F","|","-c",fo]);return po(f)},async dropReplicationSlot(c,f){if(!/^[a-z0-9_]{1,63}$/.test(f))throw new Error(`not a replication slot name: ${JSON.stringify(f)}`);await O(n,t,["exec",D(c).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",c,"-v","ON_ERROR_STOP=1","-c",`select pg_drop_replication_slot('${f}')`])},async renameProject(c,f){f!==c.ref&&await O(n,t,["exec","--interactive",D(c.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Zi(f,c.ref)})},async waitReady(c,f){let p=D(c).pod,k=Date.now()+f;for(;;){let y=await n(t,["exec",p,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(y.code===0&&y.stdout.trim()==="t")return s(c),!0;if(Date.now()>=k)return!1;await new Promise(g=>setTimeout(g,500))}},async capacity(){let c=await a(),f=await e.capacity();return{pods:c.length,...f}},async databaseBytes(c){let f=await n(t,["exec",D(c).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",c,"-tAc","select pg_database_size(current_database())"]);return f.code===0?Xr(f.stdout):null},async setReadOnly(c,f){let p=D(c);await O(n,t,["exec",p.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${p.database} set default_transaction_read_only = ${f?"on":"off"}`])},async archiveState(c){let f=await n(t,["exec",D(c).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",c,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), failed_count from pg_stat_archiver`]);if(f.code!==0)return null;let[p="",k=""]=f.stdout.trim().split(`
152
+ `)[0]?.split("|")??[],y=Number(k);return{lastArchivedAt:p.trim()||null,failures:Number.isInteger(y)&&y>=0?y:0}}}}async function No(e,t,n,r,o=Io){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,i;try{let c=JSON.parse(s.stdout);a=c[0]?.State,i=c[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let u=a?.Status??"unknown";if(u!=="running")return{name:n,ok:!1,detail:u};let d=Ji(i?.[cn]);if(!d)return{name:n,ok:!0,detail:"running"};let m=await o(d.port,d.path);return m===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${m}`}}function Xi(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Zi(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(`
153
+ `)}function Qi(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var ve,rn,Ne,Ci,Mi,Li,De,Ao,Hi,Do,cn,Io,Wi,Gi,Oo,Co=$(()=>{"use strict";se();Zt();nn();ct();_o();Qt();ve="snoutpod.ref",rn="snoutpod.generation",Ne="snoutpod.service",Ci=10,Mi=/^[a-z][a-z0-9-]{0,30}$/;Li=/^[a-z][a-z0-9_]{0,62}$/;De="snoutpod.host-service",Ao="snoutpod.host-service-spec";Hi=/^[A-Za-z_][A-Za-z0-9_]*$/;Do=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,cn="snoutpod.health";Io=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()})},Wi="crun";Gi=3e4,Oo=200});var Ko={};bn(Ko,{DEFAULT_LOCAL_MEMORY_MB:()=>Uo,DEFAULT_LOCAL_PORT:()=>rc,ENGINE:()=>C,ENGINE_NAME:()=>dn,IMAGE:()=>fe,NETWORK:()=>ae,NO_BLACKHOLE_ROUTES:()=>Bo,PUBLIC_IMAGE:()=>Pe,capacity:()=>Fo,ensureImage:()=>ic,ensureNetwork:()=>mc,exec:()=>H,hasData:()=>ut,localPsql:()=>gc,localRuntime:()=>dc,localSpec:()=>Jo,localStatus:()=>mn,localUri:()=>qo,networkPolicyInPlace:()=>Ho,newLocalPassword:()=>sc,newLocalRef:()=>oc,podLog:()=>Wo,podName:()=>dt,podmanReady:()=>lc,removeLocal:()=>wc,startLocal:()=>pc,stopLocal:()=>yc});import{randomBytes as Lo}from"node:crypto";import{existsSync as Mo}from"node:fs";import{platform as un}from"node:os";import{delimiter as ec,join as tc}from"node:path";function nc(){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(ec))if(t&&e.some(n=>Mo(tc(t,n))))return"podman";return Mo("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function oc(){return Vr(e=>Lo(e))}function sc(){return Lo(18).toString("base64url")}async function ic(e,t=H){if(process.env.SNOUTPOD_IMAGE||(await t(C,["image","exists",fe])).code===0)return null;e?.(`Fetching the database image (${Pe}). This happens once.`);let n=await t(C,["pull",Pe],{timeoutMs:cc}).catch(o=>({code:1,stdout:"",stderr:o instanceof Error?o.message:String(o)}));if(n.code!==0)return`could not fetch the database image ${Pe}: ${n.stderr.trim()||n.stdout.trim()||"the pull failed"}`;let r=await t(C,["tag",Pe,fe]);return r.code!==0?`fetched ${Pe} but could not tag it as ${fe}: ${r.stderr.trim()}`:(e?.("Got it."),null)}async function lc(e=ac){if(dn!=="podman")return`SNOUTPOD_ENGINE=${dn}: 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 t;try{t=await H(C,["--version"])}catch{return`${C} 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(t.code!==0)return`${C} --version failed: ${t.stderr.trim()||t.stdout.trim()}`;if((await H(C,["image","exists",fe])).code!==0)return`the pod image ${fe} is not on this machine. ${e}`;let r=await H(C,["machine","inspect","--format","{{.Rootful}}"]);return r.code===0&&r.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 uc(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 H(C,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
154
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function ut(e){let t=un()==="darwin"||un()==="win32";return So(H,e,{binary:C,look:t?uc:void 0})}async function Fo(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function dc(e){return jo({exec:H,binary:C,network:ae,hasData:ut,capacity:Fo,repository:e})}function dt(e){return D(e).pod}async function mc(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await fc()||await H(C,at({name:ae}));return}let t=at({name:ae,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Ho())return;await e?.(),await H(C,["network","rm","--force",ae]).catch(()=>{});let o=await H(C,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Bo}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${ae} with egress blocked${n?`: ${n}`:""}`)}async function fc(){return(await H(C,["network","inspect",ae,"--format","json"])).code===0}async function Ho(){let e=await H(C,["network","inspect",ae,"--format","json"]);if(e.code!==0)return!1;try{return uo(JSON.parse(e.stdout)?.[0])}catch{return!1}}function qo(e){let t=D(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Jo(e,t={}){return K(e.ref),{ref:e.ref,generation:1,image:t.image??fe,memoryMb:t.memoryMb??Uo,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 ln(e,t){e.onProgress?.(t)}async function pc(e,t,n={}){let r=Jo(t,n),o=n.timeoutMs??12e4,s=n.hasData??ut,a=(await e.list()).find(i=>i.ref===t.ref);if(a?a.status==="stopped"?(ln(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):ln(n,"The database is already running."):(ln(n,await s(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(r)),!await e.waitReady(t.ref,o))throw new Error(await hc(t.ref,o,n.podLog??Wo));return await e.applyOwnerPassword(r),await e.createServices(r),mn(e,t,n)}async function gc(e,t){let n=D(e.ref);return H(C,["exec","--interactive","-e","PGPASSWORD",dt(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 Wo(e){let t=await H(C,["logs","--tail","20",dt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function hc(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${dt(e)} never became ready in ${t}ms
155
+ --- pod log ---
156
+ ${r}`}async function yc(e,t,n={}){K(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,yo),mn(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function wc(e,t,n){K(t),await e.remove(t),n&&await e.removeData(t)}async function mn(e,t,n={}){K(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??ut;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?qo(t):null}}var dn,C,fe,ae,rc,Uo,H,ac,Pe,cc,Bo,Vo=$(()=>{"use strict";wo();ct();ko();tn();Co();se();dn=process.env.SNOUTPOD_ENGINE??"podman";C=nc(),fe=process.env.SNOUTPOD_IMAGE??"snoutpod/postgres:17",ae=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",rc=54322,Uo=512,H=bo();ac="cd packages/snoutpod && npm run image:build",Pe="ghcr.io/snoutdata/snoutpod-postgres:17";cc=20*60*1e3;Bo="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});le();var gt=[{name:"init",summary:"Create or link a project and write a DATABASE_URL",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:["--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to",flags:[]},{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"]},{name:"tokens revoke",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",flags:["--name","--region","--team","--no-wait","--timeout"]},{name:"projects pause",summary:"Stop a project",flags:["--ref"]},{name:"projects resume",summary:"Start a paused project",flags:["--ref"]},{name:"projects delete",summary:"Delete a project",flags:["--ref"]},{name:"projects show",summary:"One project: state, products, functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage and the data API: on or off",flags:["--ref"]},{name:"products enable",summary:"Turn auth, storage or data-api on",flags:["--ref"]},{name:"products disable",summary:"Turn auth, storage or data-api off",flags:["--ref"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",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 delete",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 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:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}];M();re();ye();pe();kt();Ke();F();Et();M();M();pe();F();async function Xn(){let{projects:e,allowance:t}=await V();h({projects:e,allowance:t},()=>{if(e.length===0){l('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${L([["REF","NAME","STATE","REGION","LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?_("read-only"):n.state==="ready"?n.state:_(n.state),n.region,X(n.lastConnectionAt)])])}
157
+ `)})}async function Tt(e){let t=await S("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;l(`Created ${n}.`);let r=t.project;e.wait&&(l("Waiting for it to start\u2026"),r=await $e(n,{timeoutMs:e.timeoutMs,onTick:o=>l(x(` ${o}`))})),e.link&&Be(e.link,{ref:n,name:r.name}),h({project:r,password:t.password,uri:`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`},()=>{process.stdout.write(`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require
158
+ `),l(""),l(`Ready. ${_(n)} in ${r.region}.`),l("The password is stored for you: `snoutdata db url` prints this again.")})}async function Zn(e,t){let n=`cloud-project-${e}`,r=await S(n,{ref:t});h(r,()=>{l(r.changed?`${t}: ${e}d.`:`${t} was already ${e==="delete"?"deleted":`${e}d`}.`)})}async function Qn(e){let t=await S("cloud-project-reset-password",{ref:e});h(t,()=>{process.stdout.write(`${t.password}
159
+ `),l(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function er(){let{teams:e}=await Qe();h({teams:e},()=>{if(e.length===0){l("You are not on any teams.");return}process.stdout.write(`${L([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
160
+ `)})}async function tr(e){let{teams:t}=await Qe(),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}M();F();async function nr(e){let t=await S("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays});h(t,()=>{process.stdout.write(`${t.token}
161
+ `),l(""),l(`${_(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),l("This is the only time it is shown: only its hash is stored."),l(x(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function rr(){let{tokens:e}=await S("cloud-token-list");h({tokens:e},()=>{if(e.length===0){l("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${L([["PREFIX","NAME","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?_(t.prefix):t.prefix,t.name,t.revokedAt?"revoked":t.live?"live":"expired",X(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
162
+ `)})}async function or(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await S("cloud-token-revoke",t);h(n,()=>{l(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}M();le();F();async function sr(e){let t=await W(e);h({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){l(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${ir(t.anonKey,t.serviceRoleKey)}
163
+ `),sa(t.keysIssuedAt)})}async function ar(e,t){if(!t.force)throw l(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),l("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new w("keys rotate needs --force, because this cannot be undone");let n=await S("cloud-project-rotate-jwt",{ref:e});h(n,()=>{process.stdout.write(`${ir(n.anonKey,n.serviceRoleKey)}
164
+ `),l(""),l(n.breaks),l(x(" snoutdata keys --json to read them again later"))})}function ir(e,t){return L([["KEY","VALUE"],["anon",e],["service_role",t]])}function sa(e){l(""),l(`${_("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),l(`${_("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&l(x(` issued ${e.slice(0,10)}`))}M();le();F();import{readdir as pa,readFile as ga,stat as ha}from"node:fs/promises";import{join as Nt,relative as ya,sep as wa}from"node:path";var ia=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function ca(e){return ia.test(e)}function At(e){if(!ca(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 la=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function ua(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!==".."&&la.test(n))}var $t=2*1024*1024,cr=200;function lr(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>cr)throw new Error(`a function may hold ${cr} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!ua(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,i)=>a.path<i.path?-1:a.path>i.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=da(o);if(s.length>$t)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${$t/1024} KB. A function is source code; put large files in Storage.`);return o}function da(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 ma=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/,fa=["SUPABASE_URL","SUPABASE_ANON_KEY","SUPABASE_SERVICE_ROLE_KEY","SUPABASE_DB_URL"];function Rt(e){return ma.test(e)?e.startsWith("SNOUT_")?`names beginning with SNOUT_ are set by the platform, so ${e} cannot be one of yours.`:fa.includes(e)?`${e} is set for you from this project's own keys and cannot be overridden.`:null:`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`}var Hl=64*1024;var ur=["supabase/functions","functions"];async function Ot(e){try{return(await ha(e)).isDirectory()}catch{return!1}}async function ba(e,t){if(t){if(!await Ot(t))throw new w(`${t} is not a directory`);return t}for(let n of ur){let r=Nt(n,e);if(await Ot(r))return r}throw new w(`no folder found for ${e}. Looked in ${ur.map(n=>`${n}/${e}`).join(" and ")}; use --dir to say where it is.`)}async function dr(e){let t=[];async function n(r){for(let o of await pa(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Nt(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await ga(s),i=a.toString("utf8");if(Buffer.compare(Buffer.from(i,"utf8"),a)!==0)throw new w(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:ya(e,s).split(wa).join("/"),text:i})}}return await n(e),t}async function Sa(e,t,n){let r=await dr(t);if(r.length===0)throw new w(`${t} has no files in it`);let o=n??(r.some(i=>i.path==="index.ts")?"index.ts":"");if(!o)throw new w(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Nt(t,"..","_shared");if(!await Ot(s))return{entrypoint:o,files:r};let a=(await dr(s)).map(i=>({path:`_shared/${i.path}`,text:i.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(i=>({path:`${e}/${i.path}`,text:i.text})),...a]}}async function Dt(e,t,n){At(t);let r=await ba(t,n.dir),{entrypoint:o,files:s}=await Sa(t,r,n.entrypoint),a=lr({entrypoint:o,files:s});return{answer:await S("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function mr(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Dt(e,t,n),i=r.functions.find(u=>u.name===t);h(r,()=>{let u=Math.max(1,Math.round(a/1024));l(`${_(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${u} KB)`),i&&l(` ${i.url}`),n.verifyJwt||(l(""),l(_(" Anybody who knows this URL can run it. No API key is required.")),l(x(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&l(x(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function It(e){return S("cloud-project-functions",{ref:e})}function jt(e,t){return At(t),S("cloud-project-functions",{ref:e,action:"delete",name:t})}async function fr(e){let t=await It(e);h(t,()=>{if(t.functions.length===0){l(`${e} has no functions.`),l(x(" snoutdata functions deploy <name> from supabase/functions/<name>"));return}process.stdout.write(`${L([["NAME","KEY","SIZE","URL"],...t.functions.map(n=>[n.name,n.verifyJwt?"required":_("open"),n.bytes===null?"-":`${Math.max(1,Math.round(n.bytes/1024))} KB`,n.url])])}
165
+ `),t.limit.maxFunctions!==null&&l(x(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`))})}async function pr(e,t){let n=await jt(e,t);h(n,()=>{l(`${t} is gone. It stops answering within a few seconds.`)})}M();le();F();function xa(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function va(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function gr(e,t,n){if(t.length===0)throw new w("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new w("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await va()})}else for(let s of t){let a=xa(s);if(!a)throw new w(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Rt(s.name);if(a)throw new w(a)}let o=null;for(let s of r)o=await S("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});h(o,()=>{for(let s of r)l(`${_(s.name)} set`);l(x(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||l(x(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function hr(e,t){let n=Rt(t);if(n)throw new w(n);let r=await S("cloud-project-secrets",{ref:e,action:"unset",name:t});h(r,()=>{l(`${t} is gone.`)})}async function yr(e){let t=await S("cloud-project-secrets",{ref:e});h(t,()=>{if(t.secrets.length===0){l(`${e} has no function secrets.`),l(x(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${L([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,X(n.updatedAt)])])}
166
+ `),l(x("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Lt}from"node:child_process";import{appendFileSync as Ea,existsSync as kr,readFileSync as _a,writeFileSync as Mt}from"node:fs";import{join as Ta}from"node:path";import{open as xr}from"node:fs/promises";function wr(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 br(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 Sr(e){let t=e.split(`
167
+ `).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}}M();re();ye();M();F();async function vr(e){try{let{projects:t}=await V(),n=t.find(r=>r.ref===e);n?.readOnly?J(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&J(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Ut(e){await vr(e);let t=await W(e);h(t,()=>{process.stdout.write(`${t.uri}
168
+ `)})}async function Pr(e,t){if(t.length===0&&!te()){let o=he();P("usage",`db psql with no statements opens an interactive session, and ${o?ge(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await vr(e);let n=await W(e);n.wakesInstantly||l("This project is paused; the connection will wake it, which takes a few seconds.");let r=Lt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,PGSSLMODE:"require"}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new q("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new q("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Er(e,t,n="DATABASE_URL"){let r=Ta(e,".env"),o=`${n}=${t}`;if(!kr(r))return Mt(r,`${o}
169
+ `),{path:r,replaced:!1};let s=_a(r,"utf8"),a=new RegExp(`^${n}=.*$`,"m");return a.test(s)?(Mt(r,s.replace(a,o)),{path:r,replaced:!0}):(Ea(r,s.endsWith(`
36
170
  `)?`${o}
37
171
  `:`
38
172
  ${o}
39
- `),{path:r,replaced:!1})}async function cn(e,t={}){if(t.statusOnly){let{export:s}=await re(e);return h({ref:e,export:s},()=>Xe(s)),0}let n=await ue(e);if(!n.export.pending&&n.export.url&&!t.out)return h({ref:e,export:n.export},()=>Xe(n.export)),0;i("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Tr(e,t.timeoutMs);if(r.error&&y("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return h({ref:e,export:r},()=>Xe(r)),0;r.url||y("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await $r(r.url,t.out);return h({ref:e,path:t.out,bytes:o},()=>{i(`Saved ${F(o)} to ${t.out}.`),i(k("Restore it with: pg_restore --dbname <your database> "+t.out))}),0}async function Tr(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await re(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function $r(e,t){let n=await fetch(e);if(!n.ok||!n.body)throw new Error(`the download failed: ${n.status} ${n.statusText}`);let r=await rn(t,"w"),o=0;try{for await(let s of n.body)await r.write(s),o+=s.byteLength}finally{await r.close()}return o}function Xe(e){if(e.error){R(`The last export did not finish: ${e.error}`);return}if(e.pending){i("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){i("This project has never been exported.");return}i(`A copy taken ${X(e.completedAt)}, ${F(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
40
- `),e.urlExpiresAt&&i(k(`That link stops working ${X(e.urlExpiresAt)}.`))):i(k("The link has expired. Run the export again to sign a new one; the copy is kept."))}function F(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function ln(e,t){let n=t.file;n||y("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),nn(n)||y("usage",`no file at ${n}`,{file:n});let r=await rn(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let c=Zt(o.slice(0,s.bytesRead)),a=await N(e);a.wakesInstantly||i("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:l}=await q(),u=l.find(E=>E.ref===e),d=await vr(a);d===null&&y("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let m=Qt(c,{existingTables:d,readOnly:u?.readOnly===!0},{force:t.force});m.ok||y("conflict",m.reason,{ref:e,file:n,refusal:m.code}),i(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let g=await Or(a,m.tool,n),p=en(g.stderr);return g.code!==0&&p.fatal.length===0&&p.total>0?(h({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:p.harmless},()=>{i(`Restored into ${e}.`),i(`${p.harmless} error${p.harmless===1?"":"s"} above ${p.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(g.code!==0&&y("failed",`${m.tool} exited ${g.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:p.fatal}),h({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>i(`Restored into ${e}.`)),0)}async function vr(e){return new Promise(t=>{let n=Ze("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e')"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{R(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function Or(e,t,n){let r=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],o=t==="psql"?[...r,"--no-psqlrc","-v","ON_ERROR_STOP=1","-f",n]:[...r,"--no-owner","--no-privileges",n];return new Promise(s=>{let c=Ze(t,o,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),a="";c.stderr?.on("data",l=>{let u=String(l);a+=u,process.stderr.write(u)}),c.on("error",l=>{R(l.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(l)),s({code:127,stderr:a})}),c.on("close",l=>s({code:l??0,stderr:a}))})}j();D();H();import{spawn as _r}from"node:child_process";import{createHash as jr}from"node:crypto";import{existsSync as Er,readdirSync as Rr,readFileSync as Dr,statSync as Nr}from"node:fs";import{isAbsolute as Ir,join as et,relative as Cr,resolve as Lr}from"node:path";function Ar(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Ce(e,t,n={}){let r=Ar(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let d=o.get(u.name);if(!d){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}d.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 c=t.map(u=>u.name).sort((u,d)=>u<d?-1:u>d?1:0)[t.length-1],a=new Set(t.map(u=>u.name)),l=[];for(let u of r)if(!a.has(u.name)){if(c!==void 0&&u.name<c&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${c}, 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}l.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:l}}function pe(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function dn(e){let t=Cr(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var me="_snoutdata_migrations",Ur=`create table if not exists ${me} (
173
+ `),{path:r,replaced:!1})}async function _r(e,t={}){if(t.statusOnly){let{export:a}=await Se(e);return h({ref:e,export:a},()=>Ct(a)),0}let n=await Te(e);if(!n.export.pending&&n.export.url&&!t.out)return h({ref:e,export:n.export},()=>Ct(n.export)),0;l("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Aa(e,t.timeoutMs);if(r.error&&P("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return h({ref:e,export:r},()=>Ct(r)),0;r.url||P("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Ra(r.url,t.out),s=r.rolesSql?$a(t.out):null;return s&&r.rolesSql&&Mt(s,r.rolesSql.endsWith(`
174
+ `)?r.rolesSql:`${r.rolesSql}
175
+ `,"utf8"),h({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{l(`Saved ${Q(o)} to ${t.out}.`),s?(l(`Saved the roles it needs to ${s}.`),l(x("Restore it with, in this order:")),l(x(` psql --dbname <your database> -f ${s}`)),l(x(` pg_restore --dbname <your database> ${t.out}`)),l(x("The roles are created without passwords. Set them yourself if anything signs in as one."))):(l(x("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),l(x("Owners and grants are skipped: this export carries no roles file.")))}),0}function $a(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Aa(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Se(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function Ra(e,t){let n=await fetch(e);if(!n.ok||!n.body)throw new Error(`the download failed: ${n.status} ${n.statusText}`);let r=await xr(t,"w"),o=0;try{for await(let s of n.body)await r.write(s),o+=s.byteLength}finally{await r.close()}return o}function Ct(e){if(e.error){J(`The last export did not finish: ${e.error}`);return}if(e.pending){l("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){l("This project has never been exported.");return}l(`A copy taken ${X(e.completedAt)}, ${Q(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
176
+ `),e.urlExpiresAt&&l(x(`That link stops working ${X(e.urlExpiresAt)}.`))):l(x("The link has expired. Run the export again to sign a new one; the copy is kept."))}function Q(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function Tr(e,t){let n=t.file;n||P("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),kr(n)||P("usage",`no file at ${n}`,{file:n});let r=await xr(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=wr(o.slice(0,s.bytesRead)),i=await W(e);i.wakesInstantly||l("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:u}=await V(),d=u.find(k=>k.ref===e),m=await Oa(i);m===null&&P("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let c=br(a,{existingTables:m,readOnly:d?.readOnly===!0},{force:t.force});c.ok||P("conflict",c.reason,{ref:e,file:n,refusal:c.code}),l(`Restoring ${n} into ${e} with ${c.tool} (${c.format} format).`);let f=await Na(i,c.tool,n),p=Sr(f.stderr);return f.code!==0&&p.fatal.length===0&&p.total>0?(h({ref:e,file:n,ok:!0,tool:c.tool,format:c.format,ignoredErrors:p.harmless},()=>{l(`Restored into ${e}.`),l(`${p.harmless} error${p.harmless===1?"":"s"} above ${p.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(f.code!==0&&P("failed",`${c.tool} exited ${f.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:p.fatal}),h({ref:e,file:n,ok:!0,tool:c.tool,format:c.format},()=>l(`Restored into ${e}.`)),0)}async function Oa(e){return new Promise(t=>{let n=Lt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e')"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{J(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function Na(e,t,n){let r=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],o=t==="psql"?[...r,"--no-psqlrc","-v","ON_ERROR_STOP=1","-f",n]:[...r,"--no-owner","--no-privileges",n];return new Promise(s=>{let a=Lt(t,o,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),i="";a.stderr?.on("data",u=>{let d=String(u);i+=d,process.stderr.write(d)}),a.on("error",u=>{J(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),s({code:127,stderr:i})}),a.on("close",u=>s({code:u??0,stderr:i}))})}M();le();F();var et=["auth","storage","data-api"];function ke(e){return{error:e instanceof Error?e.message:String(e)}}async function tt(e){let[t,n,r]=await Promise.all([S("cloud-project-auth",{ref:e,action:"status"}).then(o=>({enabled:o.enabled===!0,google:o.google?.enabled===!0,saml:o.saml?.enabled===!0}),ke),S("cloud-project-storage",{ref:e}).then(o=>({enabled:o.storage?.enabled===!0,pending:o.storage?.pending===!0,bytes:o.storage?.bytes??null,files:o.storage?.files??null}),ke),S("cloud-project-data-api",{ref:e}).then(o=>({enabled:o.dataApi?.enabled===!0,allowedOnPlan:o.dataApi?.allowed===!0}),ke)]);return{ref:e,auth:t,storage:n,dataApi:r}}function $r(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!et.includes(t))throw new w(`say which product: ${et.join(", ")}`);return t}async function Ft(e,t,n){if(t==="auth"){let r=await S("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return await S(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`}}async function Ar(e){let t=await tt(e);h(t,()=>{let n=(s,a,i="")=>[s,a&&typeof a=="object"&&"error"in a?`could not read: ${a.error}`:a.enabled?_("on"):"off",i],r=t.storage,o=t.dataApi;process.stdout.write(`${L([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"paid plans only":"")])}
177
+ `),l(x(` snoutdata products enable storage --ref ${e}`))})}async function Rr(e,t,n){let r=await Ft(e,t,n);h(r,()=>l(r.note))}async function nt(e){let t=await S("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Bt(e,t,n){if(!n)throw new w(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await S("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Ia(e,t){let n=[["TYPE","NAME","VALUE"]];e.record&&n.push([e.record.type,e.record.name,e.record.value]),e.certificateRecord&&n.push([e.certificateRecord.type,e.certificateRecord.name,e.certificateRecord.value]),n.push(["CNAME",e.hostname,`${t}.api.snoutdata.com`]),process.stdout.write(`${L(n)}
178
+ `)}async function Or(e,t,n){if(t===void 0||t==="list"){let o=await nt(e);h(o,()=>{if(o.domains.length===0){l(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${L([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":_("no"),s.certificate?.state??""])])}
179
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new w(`unknown command: domains ${t}`);let r=await Bt(e,t,n??"");h(r,()=>{if(t==="remove"){l(`${n} removed.`);return}r.domain&&(l(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||Ia(r.domain,e)),r.next&&l(r.next)})}async function Ht(e){return S("cloud-project-restore",{ref:e},"GET")}async function qt(e,t,n){if(Number.isNaN(Date.parse(t)))throw new w(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return S("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Nr(e){let t=await Ht(e);h(t,()=>{let n=t.restore;if(!n.available){l(n.pitrEnabled?`${e} has no backup to restore from yet.`:`Point-in-time restore is not part of the ${n.tier} plan.`);return}l(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),l(x(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function Dr(e,t,n){let r=await qt(e,t,n);h(r,()=>{l(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${_(r.project.name)} (${r.project.ref}).`),l(x(" snoutdata projects list to see when it is ready"))})}async function Jt(e){let{projects:t}=await V(),n=t.find(i=>i.ref===e);if(!n)throw new w(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([tt(e),S("cloud-project-functions",{ref:e}).then(i=>(i.functions??[]).map(u=>u.name),ke),S("cloud-project-secrets",{ref:e}).then(i=>(i.secrets??[]).map(u=>u.name),ke),nt(e).then(i=>i.domains.map(u=>({hostname:u.hostname,verified:u.verified})),ke)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ir(e){let t=await Jt(e);h(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${L([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],["auth / storage / data-api",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)}`],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
180
+ `)})}M();F();re();import{spawn as Ca}from"node:child_process";import{createHash as Ma}from"node:crypto";import{existsSync as La,readdirSync as Ua,readFileSync as Fa,statSync as Ba}from"node:fs";import{isAbsolute as Ha,join as Wt,relative as qa,resolve as Ja}from"node:path";function ja(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function rt(e,t,n={}){let r=ja(e),o=new Map(r.map(d=>[d.name,d])),s=[];for(let d of t){let m=o.get(d.name);if(!m){s.push({code:"missing",name:d.name,reason:`${d.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}m.checksum!==d.checksum&&s.push({code:"changed",name:d.name,reason:`${d.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(d=>d.name).sort((d,m)=>d<m?-1:d>m?1:0)[t.length-1],i=new Set(t.map(d=>d.name)),u=[];for(let d of r)if(!i.has(d.name)){if(a!==void 0&&d.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:d.name,reason:`${d.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}u.push(d)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:u}}function Ae(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function Mr(e){let t=qa(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Re="_snoutdata_migrations",Wa=`create table if not exists ${Re} (
41
181
  name text primary key,
42
182
  checksum text not null,
43
183
  applied_at timestamptz not null default now()
44
- );`;function Mr(e){if(!Er(e)||!Nr(e).isDirectory())throw new Error(`no migrations folder at ${dn(e)}. Put your .sql files there, or pass --dir.`);return Rr(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=et(e,t),r=Dr(n,"utf8");return{name:t,path:n,sql:r,checksum:jr("sha256").update(r,"utf8").digest("hex")}})}function tt(e,t,n=[]){return new Promise(r=>{let o=_r("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),s="",c="";o.stdout.on("data",a=>s+=String(a)),o.stderr.on("data",a=>c+=String(a)),o.on("error",a=>{r({code:127,out:"",err:a.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(a)})}),o.on("close",a=>r({code:a??0,out:s,err:c})),o.stdin.end(t)})}async function Br(e){let t=await tt(e,Ur);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${me}`);let n=await tt(e,`select name || E'\\t' || checksum from ${me};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${me}`);return n.out.split(`
45
- `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function un(e){return`'${e.replace(/'/g,"''")}'`}async function pn(e,t={}){let n=await nt(e,t,r=>i(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
46
- `);y("conflict",`Refused. This folder and ${e} do not agree:
47
- ${r}`,{ref:e,problems:n.problems})}y("failed",`${n.failed} failed, and nothing after it was tried.
48
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return h(n,()=>{if(n.wouldApply){i(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)i(` ${r}`);return}if(n.upToDate){i(`${e} is up to date.`);return}i(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function nt(e,t={},n=()=>{}){let r=Lr(t.dir===void 0?et(process.cwd(),"migrations"):Ir(t.dir)?t.dir:et(process.cwd(),t.dir)),o=[];function s(p){o.push(p),n(p)}let c=Mr(r);if(c.length===0)return s(`No .sql files in ${dn(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let a=await N(e);a.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let l=await Br(a),u=Ce(c,l,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let d=new Map(c.map(p=>[p.name,p])),m=u.pending.map(p=>d.get(p.name));if(m.length===0)return s(`${e} is up to date: ${l.length} migration${l.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let p of m)pe(p.sql)||s(`${p.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(p=>p.name),notes:o}}let g=[];for(let p of m){let E=`insert into ${me} (name, checksum) values (${un(p.name)}, ${un(p.checksum)});`,A=pe(p.sql);A||s(`${p.name} runs outside a transaction, so an interruption can leave it half applied.`);let w=A?`begin;
184
+ );`;function Ka(e){if(!La(e)||!Ba(e).isDirectory())throw new Error(`no migrations folder at ${Mr(e)}. Put your .sql files there, or pass --dir.`);return Ua(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Wt(e,t),r=Fa(n,"utf8");return{name:t,path:n,sql:r,checksum:Ma("sha256").update(r,"utf8").digest("hex")}})}function Kt(e,t,n=[]){return new Promise(r=>{let o=Ca("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),s="",a="";o.stdout.on("data",i=>s+=String(i)),o.stderr.on("data",i=>a+=String(i)),o.on("error",i=>{r({code:127,out:"",err:i.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(i)})}),o.on("close",i=>r({code:i??0,out:s,err:a})),o.stdin.end(t)})}async function Va(e){let t=await Kt(e,Wa);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Re}`);let n=await Kt(e,`select name || E'\\t' || checksum from ${Re};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Re}`);return n.out.split(`
185
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function Cr(e){return`'${e.replace(/'/g,"''")}'`}async function Lr(e,t={}){let n=await Vt(e,t,r=>l(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
186
+ `);P("conflict",`Refused. This folder and ${e} do not agree:
187
+ ${r}`,{ref:e,problems:n.problems})}P("failed",`${n.failed} failed, and nothing after it was tried.
188
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return h(n,()=>{if(n.wouldApply){l(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)l(` ${r}`);return}if(n.upToDate){l(`${e} is up to date.`);return}l(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function Vt(e,t={},n=()=>{}){let r=Ja(t.dir===void 0?Wt(process.cwd(),"migrations"):Ha(t.dir)?t.dir:Wt(process.cwd(),t.dir)),o=[];function s(p){o.push(p),n(p)}let a=Ka(r);if(a.length===0)return s(`No .sql files in ${Mr(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let i=await W(e);i.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let u=await Va(i),d=rt(a,u,{outOfOrder:t.outOfOrder});if(!d.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...d.problems],notes:o};let m=new Map(a.map(p=>[p.name,p])),c=d.pending.map(p=>m.get(p.name));if(c.length===0)return s(`${e} is up to date: ${u.length} migration${u.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let p of c)Ae(p.sql)||s(`${p.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:c.map(p=>p.name),notes:o}}let f=[];for(let p of c){let k=`insert into ${Re} (name, checksum) values (${Cr(p.name)}, ${Cr(p.checksum)});`,y=Ae(p.sql);y||s(`${p.name} runs outside a transaction, so an interruption can leave it half applied.`);let g=y?`begin;
49
189
  ${p.sql}
50
- ${E}
190
+ ${k}
51
191
  commit;
52
192
  `:`${p.sql}
53
- ${E}
54
- `,_=await tt(a,w);if(_.code!==0)return A&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:g,failed:p.name,error:_.err.trim(),notes:o};g.push(p.name),s(` applied ${p.name}`)}return{ref:e,directory:r,ok:!0,applied:g,notes:o}}j();H();D();import{spawn as Qr}from"node:child_process";import{writeFileSync as eo}from"node:fs";import{isAbsolute as to,resolve as no}from"node:path";var qr=" ";function f(e){return qr.repeat(e)}function v(e){return JSON.stringify(e)}function W(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:v(e)}function Z(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Fr(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 Wr={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"},Gr={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Kr(e,t){let n=Gr[e]?.[t];return n||(Wr[t]??"unknown")}function Le(e,t){let{base:n,array:r}=Fr(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Kr(e.driverId,n),c=s==="json"?"Json":s;return r?`${c}[]`:c}function Jr(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function rt(e,t,n,r){let o=Le(e,t.dataType),s=t.nullable?`${o} | null`:o;return`${f(n)}${W(t.name)}${r?"?":""}: ${s}`}function Vr(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((c,a)=>c===n[a])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function zr(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(`${f(t)}Relationships: []`),n;n.push(`${f(t)}Relationships: [`);for(let o of r)n.push(`${f(t+1)}{`),n.push(`${f(t+2)}foreignKeyName: ${v(o.constraintName)}`),n.push(`${f(t+2)}columns: [${o.columns.map(v).join(", ")}]`),n.push(`${f(t+2)}isOneToOne: ${Vr(e,o)?"true":"false"}`),n.push(`${f(t+2)}referencedRelation: ${v(o.referencedTable)}`),n.push(`${f(t+2)}referencedColumns: [${o.referencedColumns.map(v).join(", ")}]`),n.push(`${f(t+1)}},`);return n.push(`${f(t)}]`),n}function mn(e,t,n){let r=Z(t.columns),o=[];o.push(`${f(n)}${W(t.name)}: {`),o.push(`${f(n+1)}Row: {`);for(let s of r)o.push(rt(e,s,n+2,!1));if(o.push(`${f(n+1)}}`),t.kind!=="view"){o.push(`${f(n+1)}Insert: {`);for(let s of r)o.push(rt(e,s,n+2,Jr(s)));o.push(`${f(n+1)}}`),o.push(`${f(n+1)}Update: {`);for(let s of r)o.push(rt(e,s,n+2,!0));o.push(`${f(n+1)}}`)}return o.push(...zr(t,n+1)),o.push(`${f(n)}}`),o}function fe(e,t){return[`${f(t)}${e}: {`,`${f(t+1)}[_ in never]: never`,`${f(t)}}`]}function Hr(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 c=e.rowRefs.get(s.toLowerCase());return c?`${c}[]`:`${Le(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??Le(e,n)}function Yr(e,t,n){let r=[],o=Z((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${f(n)}${W(t.name)}: {`),o.length===0)r.push(`${f(n+1)}Args: never`);else{r.push(`${f(n+1)}Args: {`);for(let s of o)r.push(`${f(n+2)}${W(s.name)}: ${Le(e,s.dataType)}`);r.push(`${f(n+1)}}`)}return r.push(`${f(n+1)}Returns: ${Hr(e,t)}`),r.push(`${f(n)}}`),r}function se(e,t){return e.schema&&e.schema.length>0?e.schema:t}function fn(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let g=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${v(g)}][${v("Enums")}][${v(m.name)}]`)}let c=new Map;for(let m of e.tables){let g=se(m,n),p=m.kind==="view"?"Views":"Tables";c.set(m.name.toLowerCase(),`Database[${v(g)}][${v(p)}][${v(m.name)}][${v("Row")}]`)}let a={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:c},l=new Set([n]);for(let m of e.tables)l.add(se(m,n));for(let m of e.routines??[])l.add(se(m,n));for(let m of o)l.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...l].sort(),d=[];d.push("export type Json ="),d.push(" | string"),d.push(" | number"),d.push(" | boolean"),d.push(" | null"),d.push(" | { [key: string]: Json | undefined }"),d.push(" | Json[]"),d.push(""),d.push("export type Database = {"),t.postgrestVersion&&(d.push(`${f(1)}__InternalSupabase: {`),d.push(`${f(2)}PostgrestVersion: ${v(t.postgrestVersion)}`),d.push(`${f(1)}}`));for(let m of u){let g=Z(e.tables.filter(w=>se(w,n)===m&&w.kind!=="view")),p=Z(e.tables.filter(w=>se(w,n)===m&&w.kind==="view")),E=Z((e.routines??[]).filter(w=>se(w,n)===m)),A=Z(o.filter(w=>(w.schema&&w.schema.length>0?w.schema:n)===m));if(d.push(`${f(1)}${W(m)}: {`),g.length===0)d.push(...fe("Tables",2));else{d.push(`${f(2)}Tables: {`);for(let w of g)d.push(...mn(a,w,3));d.push(`${f(2)}}`)}if(p.length===0)d.push(...fe("Views",2));else{d.push(`${f(2)}Views: {`);for(let w of p)d.push(...mn(a,w,3));d.push(`${f(2)}}`)}if(E.length===0)d.push(...fe("Functions",2));else{d.push(`${f(2)}Functions: {`);for(let w of E)d.push(...Yr(a,w,3));d.push(`${f(2)}}`)}if(A.length===0)d.push(...fe("Enums",2));else{d.push(`${f(2)}Enums: {`);for(let w of A)d.push(`${f(3)}${W(w.name)}: ${w.values.map(v).join(" | ")}`);d.push(`${f(2)}}`)}d.push(...fe("CompositeTypes",2)),d.push(`${f(1)}}`)}return d.push("}"),t.helpers!==!1&&(d.push(""),d.push(Zr(n))),t.constants!==!1&&(d.push(""),d.push(Xr(u,o,n))),`${d.join(`
193
+ ${k}
194
+ `,T=await Kt(i,g);if(T.code!==0)return y&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:f,failed:p.name,error:T.err.trim(),notes:o};f.push(p.name),s(` applied ${p.name}`)}return{ref:e,directory:r,ok:!0,applied:f,notes:o}}M();re();F();import{spawn as ai}from"node:child_process";import{writeFileSync as ii}from"node:fs";import{isAbsolute as ci,resolve as li}from"node:path";var Ga=" ";function b(e){return Ga.repeat(e)}function B(e){return JSON.stringify(e)}function ee(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:B(e)}function me(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function za(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 Ya={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"},Xa={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Za(e,t){let n=Xa[e]?.[t];return n||(Ya[t]??"unknown")}function ot(e,t){let{base:n,array:r}=za(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Za(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Qa(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function Gt(e,t,n,r){let o=ot(e,t.dataType),s=t.nullable?`${o} | null`:o;return`${b(n)}${ee(t.name)}${r?"?":""}: ${s}`}function ei(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,i)=>a===n[i])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function ti(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(`${b(t)}Relationships: []`),n;n.push(`${b(t)}Relationships: [`);for(let o of r)n.push(`${b(t+1)}{`),n.push(`${b(t+2)}foreignKeyName: ${B(o.constraintName)}`),n.push(`${b(t+2)}columns: [${o.columns.map(B).join(", ")}]`),n.push(`${b(t+2)}isOneToOne: ${ei(e,o)?"true":"false"}`),n.push(`${b(t+2)}referencedRelation: ${B(o.referencedTable)}`),n.push(`${b(t+2)}referencedColumns: [${o.referencedColumns.map(B).join(", ")}]`),n.push(`${b(t+1)}},`);return n.push(`${b(t)}]`),n}function Ur(e,t,n){let r=me(t.columns),o=[];o.push(`${b(n)}${ee(t.name)}: {`),o.push(`${b(n+1)}Row: {`);for(let s of r)o.push(Gt(e,s,n+2,!1));if(o.push(`${b(n+1)}}`),t.kind!=="view"){o.push(`${b(n+1)}Insert: {`);for(let s of r)o.push(Gt(e,s,n+2,Qa(s)));o.push(`${b(n+1)}}`),o.push(`${b(n+1)}Update: {`);for(let s of r)o.push(Gt(e,s,n+2,!0));o.push(`${b(n+1)}}`)}return o.push(...ti(t,n+1)),o.push(`${b(n)}}`),o}function Oe(e,t){return[`${b(t)}${e}: {`,`${b(t+1)}[_ in never]: never`,`${b(t)}}`]}function ni(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}[]`:`${ot(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??ot(e,n)}function ri(e,t,n){let r=[],o=me((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${b(n)}${ee(t.name)}: {`),o.length===0)r.push(`${b(n+1)}Args: never`);else{r.push(`${b(n+1)}Args: {`);for(let s of o)r.push(`${b(n+2)}${ee(s.name)}: ${ot(e,s.dataType)}`);r.push(`${b(n+1)}}`)}return r.push(`${b(n+1)}Returns: ${ni(e,t)}`),r.push(`${b(n)}}`),r}function xe(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Fr(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let c of o){let f=c.schema&&c.schema.length>0?c.schema:n;s.set(c.name.toLowerCase(),`Database[${B(f)}][${B("Enums")}][${B(c.name)}]`)}let a=new Map;for(let c of e.tables){let f=xe(c,n),p=c.kind==="view"?"Views":"Tables";a.set(c.name.toLowerCase(),`Database[${B(f)}][${B(p)}][${B(c.name)}][${B("Row")}]`)}let i={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},u=new Set([n]);for(let c of e.tables)u.add(xe(c,n));for(let c of e.routines??[])u.add(xe(c,n));for(let c of o)u.add(c.schema&&c.schema.length>0?c.schema:n);let d=[...u].sort(),m=[];m.push("export type Json ="),m.push(" | string"),m.push(" | number"),m.push(" | boolean"),m.push(" | null"),m.push(" | { [key: string]: Json | undefined }"),m.push(" | Json[]"),m.push(""),m.push("export type Database = {"),t.postgrestVersion&&(m.push(`${b(1)}__InternalSupabase: {`),m.push(`${b(2)}PostgrestVersion: ${B(t.postgrestVersion)}`),m.push(`${b(1)}}`));for(let c of d){let f=me(e.tables.filter(g=>xe(g,n)===c&&g.kind!=="view")),p=me(e.tables.filter(g=>xe(g,n)===c&&g.kind==="view")),k=me((e.routines??[]).filter(g=>xe(g,n)===c)),y=me(o.filter(g=>(g.schema&&g.schema.length>0?g.schema:n)===c));if(m.push(`${b(1)}${ee(c)}: {`),f.length===0)m.push(...Oe("Tables",2));else{m.push(`${b(2)}Tables: {`);for(let g of f)m.push(...Ur(i,g,3));m.push(`${b(2)}}`)}if(p.length===0)m.push(...Oe("Views",2));else{m.push(`${b(2)}Views: {`);for(let g of p)m.push(...Ur(i,g,3));m.push(`${b(2)}}`)}if(k.length===0)m.push(...Oe("Functions",2));else{m.push(`${b(2)}Functions: {`);for(let g of k)m.push(...ri(i,g,3));m.push(`${b(2)}}`)}if(y.length===0)m.push(...Oe("Enums",2));else{m.push(`${b(2)}Enums: {`);for(let g of y)m.push(`${b(3)}${ee(g.name)}: ${g.values.map(B).join(" | ")}`);m.push(`${b(2)}}`)}m.push(...Oe("CompositeTypes",2)),m.push(`${b(1)}}`)}return m.push("}"),t.helpers!==!1&&(m.push(""),m.push(si(n))),t.constants!==!1&&(m.push(""),m.push(oi(d,o,n))),`${m.join(`
55
195
  `)}
56
- `}function Xr(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=Z(t.filter(c=>(c.schema&&c.schema.length>0?c.schema:n)===o));if(s.length===0){r.push(`${f(1)}${W(o)}: {`),r.push(`${f(2)}Enums: {},`),r.push(`${f(1)}},`);continue}r.push(`${f(1)}${W(o)}: {`),r.push(`${f(2)}Enums: {`);for(let c of s)r.push(`${f(3)}${W(c.name)}: [${c.values.map(v).join(", ")}],`);r.push(`${f(2)}},`),r.push(`${f(1)}},`)}return r.push("} as const"),r.join(`
57
- `)}function Zr(e){return`type DatabaseWithoutInternals = Omit<Database, "__InternalSupabase">
196
+ `}function oi(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=me(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${b(1)}${ee(o)}: {`),r.push(`${b(2)}Enums: {},`),r.push(`${b(1)}},`);continue}r.push(`${b(1)}${ee(o)}: {`),r.push(`${b(2)}Enums: {`);for(let a of s)r.push(`${b(3)}${ee(a.name)}: [${a.values.map(B).join(", ")}],`);r.push(`${b(2)}},`),r.push(`${b(1)}},`)}return r.push("} as const"),r.join(`
197
+ `)}function si(e){return`type DatabaseWithoutInternals = Omit<Database, "__InternalSupabase">
58
198
 
59
- type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${v(e)}>]
199
+ type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${B(e)}>]
60
200
 
61
201
  export type Tables<
62
202
  DefaultSchemaTableNameOrOptions extends
@@ -169,7 +309,7 @@ export type CompositeTypes<
169
309
  ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
170
310
  : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
171
311
  ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
172
- : never`}function ro(e){return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:"require",label:e.ref}}function Ue(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function ae(e,t){return new Promise(n=>{let r=Qr("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA"],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.sslMode}}),o="",s="";r.stdout.on("data",c=>o+=String(c)),r.stderr.on("data",c=>s+=String(c)),r.on("error",c=>{n({code:127,out:"",err:c.code==="ENOENT"?"psql is not installed, and gen types needs it to read the schema. `snoutdata db url` prints a connection string for any client.":String(c)})}),r.on("close",c=>n({code:c??0,out:o,err:s})),r.stdin.end(t)})}var oo=`
312
+ : never`}function ui(e){return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:"require",label:e.ref}}function st(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function zt(e,t){return new Promise(n=>{let r=ai("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA"],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.sslMode}}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{n({code:127,out:"",err:a.code==="ENOENT"?"psql is not installed, and gen types needs it to read the schema. `snoutdata db url` prints a connection string for any client.":String(a)})}),r.on("close",a=>n({code:a??0,out:o,err:s})),r.stdin.end(t)})}var di=`
173
313
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
174
314
  select json_build_object(
175
315
  'tables', coalesce((
@@ -272,31 +412,34 @@ select json_build_object(
272
412
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
273
413
  ), '[]'::json)
274
414
  )::text
275
- `;function so(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function ao(e,t,n=0){let r=e.tables.map(c=>{let a=c.primaryKey??[],l=new Set(a),u=c.columns.map(d=>({name:d.name,dataType:d.dataType,nullable:d.nullable,isPrimaryKey:l.has(d.name),default:d.default,autoIncrement:d.autoIncrement}));return{name:c.name,schema:c.schema,kind:c.kind,columns:u,primaryKey:a,foreignKeys:(c.foreignKeys??[]).map(d=>({constraintName:d.constraintName,columns:d.columns??[],referencedTable:d.referencedTable,referencedColumns:d.referencedColumns??[]})),indexes:(c.indexes??[]).filter(d=>Array.isArray(d.columns)&&d.columns.every(m=>typeof m=="string")).map(d=>({name:d.name,unique:d.unique,columns:d.columns??[]}))}}),o=e.routines.map(c=>({name:c.name,schema:c.schema,kind:c.kind,returnType:c.returnType??void 0,parameters:(c.parameters??[]).map(a=>({name:a.name,dataType:a.dataType,mode:a.mode}))})),s=e.enums.map(c=>({name:c.name,schema:c.schema,values:c.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}async function io(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Ue(e.dbUrl):ro(await N(co(e))),r=await ae(n,oo.replace("$SCHEMAS$",so(t)));r.code===127&&y("tool-missing",r.err.trim()),r.code!==0&&y("failed",r.err.trim()||`could not read the schema of ${n.label}`);let o=r.out.trim();o||y("failed",`${n.label} returned nothing for the schema query`);let s;try{s=JSON.parse(o)}catch{y("failed",`could not read the schema of ${n.label}: the answer was not JSON`)}let{schema:c,enums:a}=ao(s,n.database,Date.now()),l=fn(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:a,postgrestVersion:e.postgrestVersion}),u=null;return e.out&&(u=to(e.out)?e.out:no(process.cwd(),e.out),eo(u,l,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(d=>d.kind!=="view").length,views:c.tables.filter(d=>d.kind==="view").length,functions:(c.routines??[]).length,enums:a.length,path:u,types:l}}function co(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 gn(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&y("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await io(t);return r.path&&i(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),h(r,()=>{r.path||process.stdout.write(r.types)}),0}H();import{createHash as lo}from"node:crypto";import{existsSync as ot,mkdirSync as uo,readFileSync as st,readdirSync as po,statSync as mo,writeFileSync as fo}from"node:fs";import{dirname as go,isAbsolute as ho,join as he,resolve as yo}from"node:path";D();var hn=process.env.SNOUTDATA_LOCAL_RUNTIME??"@snout/snoutpod/local";async function ye(){let e;try{e=await import(hn)}catch(t){y("tool-missing",`the local pod runtime (${hn}) is not available to this build of the CLI, so \`snoutdata start\` cannot run. Point SNOUTDATA_LOCAL_RUNTIME at packages/snoutpod/dist/local/project.js, or use a build that bundles it.`,{detail:t instanceof Error?t.message:String(t)})}return{ready:()=>e.podmanReady(),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),mint:t=>({ref:e.newLocalRef(),port:t,ownerPassword:e.newLocalPassword(),adminPassword:e.newLocalPassword()})}}function wn(e){return he(e,".snoutdata","local.json")}function Me(e){let t=wn(e);if(!ot(t))return null;try{let n=JSON.parse(st(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 wo(e,t){let n=wn(e);return uo(go(n),{recursive:!0}),fo(n,`${JSON.stringify(t,null,2)}
276
- `,{encoding:"utf8",mode:384}),n}var bo=54322;async function bn(e){let t=await e.ready();t&&y("tool-missing",t),await e.prepare()}async function ko(e,t={}){let n=t.cwd??process.cwd();await bn(e);let r=Me(n),o=r??e.mint(t.port??bo),s=r?null:wo(n,o),c=await e.start(o,d=>i(d)),a=e.uri(o);if(t.noMigrations)return{status:c,uri:a,applied:[],seeded:!1,created:s};let l=await So(a,n,t),u=await xo(a,n,l.length>0||!!s);return{status:c,uri:a,applied:l,seeded:u,created:s}}async function So(e,t,n){let r=yo(n.dir===void 0?he(t,"migrations"):ho(n.dir)?n.dir:he(t,n.dir));if(!ot(r)||!mo(r).isDirectory())return[];let o=po(r).filter(g=>g.endsWith(".sql")).map(g=>{let p=st(he(r,g),"utf8");return{name:g,sql:p,checksum:lo("sha256").update(p,"utf8").digest("hex")}});if(o.length===0)return[];let s=Ue(e),c=await ae(s,To);c.code!==0&&y(c.code===127?"tool-missing":"failed",c.err.trim()||`could not create ${ge}`);let a=await ae(s,`select name || E'\\t' || checksum from ${ge};`);a.code!==0&&y("failed",a.err.trim()||`could not read ${ge}`);let l=a.out.split(`
277
- `).map(g=>g.trim()).filter(g=>g.length>0).map(g=>{let p=g.indexOf(" ");return{name:g.slice(0,p),checksum:g.slice(p+1)}}),u=Ce(o,l,{outOfOrder:n.outOfOrder});u.ok||y("conflict",`Refused. This folder and the local database do not agree:
278
- ${u.problems.map(g=>` ${g.reason}`).join(`
279
- `)}`,{problems:u.problems});let d=new Map(o.map(g=>[g.name,g])),m=[];for(let g of u.pending){let p=d.get(g.name),E=`insert into ${ge} (name, checksum) values (${yn(p.name)}, ${yn(p.checksum)});`,A=pe(p.sql);A||i(`${p.name} runs outside a transaction, so an interruption can leave it half applied.`);let w=A?`begin;
280
- ${p.sql}
281
- ${E}
415
+ `;function mi(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function fi(e,t,n=0){let r=e.tables.map(a=>{let i=a.primaryKey??[],u=new Set(i),d=a.columns.map(m=>({name:m.name,dataType:m.dataType,nullable:m.nullable,isPrimaryKey:u.has(m.name),default:m.default,autoIncrement:m.autoIncrement}));return{name:a.name,schema:a.schema,kind:a.kind,columns:d,primaryKey:i,foreignKeys:(a.foreignKeys??[]).map(m=>({constraintName:m.constraintName,columns:m.columns??[],referencedTable:m.referencedTable,referencedColumns:m.referencedColumns??[]})),indexes:(a.indexes??[]).filter(m=>Array.isArray(m.columns)&&m.columns.every(c=>typeof c=="string")).map(m=>({name:m.name,unique:m.unique,columns:m.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(i=>({name:i.name,dataType:i.dataType,mode:i.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}async function pi(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?st(e.dbUrl):ui(await W(gi(e))),r=di.replace("$SCHEMAS$",mi(t)),o=await(e.sql?e.sql(r):zt(n,r));o.code===127&&P("tool-missing",o.err.trim()),o.code!==0&&P("failed",o.err.trim()||`could not read the schema of ${n.label}`);let s=o.out.trim();s||P("failed",`${n.label} returned nothing for the schema query`);let a;try{a=JSON.parse(s)}catch{P("failed",`could not read the schema of ${n.label}: the answer was not JSON`)}let{schema:i,enums:u}=fi(a,n.database,Date.now()),d=Fr(i,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:u,postgrestVersion:e.dataApiVersion}),m=null;return e.out&&(m=ci(e.out)?e.out:li(process.cwd(),e.out),ii(m,d,"utf8")),{target:n.label,schemas:t,tables:i.tables.filter(c=>c.kind!=="view").length,views:i.tables.filter(c=>c.kind==="view").length,functions:(i.routines??[]).length,enums:u.length,path:m,types:d}}function gi(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 Br(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&P("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await pi(t);return r.path&&l(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),h(r,()=>{r.path||process.stdout.write(r.types)}),0}re();import{createHash as bc}from"node:crypto";import{existsSync as fn,mkdirSync as Sc,readFileSync as pn,readdirSync as kc,statSync as xc,writeFileSync as vc}from"node:fs";import{dirname as Pc,isAbsolute as zo,join as Ce,resolve as Yo}from"node:path";import{pathToFileURL as Ec}from"node:url";F();function _c(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:zo(e)||e.startsWith("./")||e.startsWith("../")?Ec(Yo(e)).href:e}function Tc(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(_c(e)):Promise.resolve().then(()=>(Vo(),Ko))}function $c(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 Ac="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 Me(){let e;try{e=await Tc()}catch(t){P("tool-missing",$c(t))}return{ready:()=>e.podmanReady(Ac),ensureImage:t=>e.ensureImage(t),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 Xo(e){return Ce(e,".snoutdata","local.json")}function mt(e){let t=Xo(e);if(!fn(t))return null;try{let n=JSON.parse(pn(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 Rc(e,t){let n=Xo(e);return Sc(Pc(n),{recursive:!0}),vc(n,`${JSON.stringify(t,null,2)}
416
+ `,{encoding:"utf8",mode:384}),n}var Oc=54322;async function Zo(e){let t=await e.ensureImage(r=>l(r));t&&P("tool-missing",t);let n=await e.ready();n&&P("tool-missing",n),await e.prepare()}function Qo(e,t,n,r=zt){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 Nc(e,t={}){let n=t.cwd??process.cwd();await Zo(e);let r=mt(n),o=r??e.mint(t.port??Oc),s=r?null:Rc(n,o),a=await e.start(o,c=>l(c)),i=e.uri(o);if(t.noMigrations)return{status:a,uri:i,applied:[],seeded:!1,created:s};let u=Qo(e,o,st(i)),d=await Dc(u,n,t),m=await Ic(u,n,d.length>0||!!s);return{status:a,uri:i,applied:d,seeded:m,created:s}}async function Dc(e,t,n){let r=Yo(n.dir===void 0?Ce(t,"migrations"):zo(n.dir)?n.dir:Ce(t,n.dir));if(!fn(r)||!xc(r).isDirectory())return[];let o=kc(r).filter(c=>c.endsWith(".sql")).map(c=>{let f=pn(Ce(r,c),"utf8");return{name:c,sql:f,checksum:bc("sha256").update(f,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(jc);s.code!==0&&P(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${je}`);let a=await e(`select name || E'\\t' || checksum from ${je};`);a.code!==0&&P("failed",a.err.trim()||`could not read ${je}`);let i=a.out.split(`
417
+ `).map(c=>c.trim()).filter(c=>c.length>0).map(c=>{let f=c.indexOf(" ");return{name:c.slice(0,f),checksum:c.slice(f+1)}}),u=rt(o,i,{outOfOrder:n.outOfOrder});u.ok||P("conflict",`Refused. This folder and the local database do not agree:
418
+ ${u.problems.map(c=>` ${c.reason}`).join(`
419
+ `)}`,{problems:u.problems});let d=new Map(o.map(c=>[c.name,c])),m=[];for(let c of u.pending){let f=d.get(c.name),p=`insert into ${je} (name, checksum) values (${Go(f.name)}, ${Go(f.checksum)});`,k=Ae(f.sql);k||l(`${f.name} runs outside a transaction, so an interruption can leave it half applied.`);let y=k?`begin;
420
+ ${f.sql}
421
+ ${p}
282
422
  commit;
283
- `:`${p.sql}
284
- ${E}
285
- `,_=await ae(s,w);_.code!==0&&y("failed",`${p.name} failed, and nothing after it was tried.
286
- ${_.err.trim()}`,{failed:p.name,applied:m}),m.push(p.name),i(` applied ${p.name}`)}return m}async function xo(e,t,n){let r=he(t,"seed.sql");if(!n||!ot(r))return!1;let o=await ae(Ue(e),st(r,"utf8"));return o.code!==0&&y("failed",`seed.sql failed.
287
- ${o.err.trim()}`),i(" ran seed.sql"),!0}var ge="_snoutdata_migrations",To=`create table if not exists ${ge} (
423
+ `:`${f.sql}
424
+ ${p}
425
+ `,g=await e(y);g.code!==0&&P("failed",`${f.name} failed, and nothing after it was tried.
426
+ ${g.err.trim()}`,{failed:f.name,applied:m}),m.push(f.name),l(` applied ${f.name}`)}return m}async function Ic(e,t,n){let r=Ce(t,"seed.sql");if(!n||!fn(r))return!1;let o=await e(pn(r,"utf8"));return o.code!==0&&P("failed",`seed.sql failed.
427
+ ${o.err.trim()}`),l(" ran seed.sql"),!0}var je="_snoutdata_migrations",jc=`create table if not exists ${je} (
288
428
  name text primary key,
289
429
  checksum text not null,
290
430
  applied_at timestamptz not null default now()
291
- );`;function yn(e){return`'${e.replace(/'/g,"''")}'`}function $o(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 kn(e,t={}){let n=await ko(e,t);return h(n,()=>{n.created&&(i(""),i(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),i(""),i(` ${S("Database")} running on port ${n.status.port}`),n.applied.length>0&&i(` ${S("Migrations")} ${n.applied.length} applied`),n.seeded&&i(` ${S("Seed")} seed.sql ran`),i(""),process.stdout.write(`${n.uri}
292
- `),i(k(" snoutdata gen types typescript --local > database.types.ts")),i(k(" snoutdata stop"))}),0}async function Sn(e,t={}){let n=t.cwd??process.cwd(),r=Me(n);r||y("not-found","there is no local database for this folder. `snoutdata start` makes one."),await bn(e);let o=await e.stop(r.ref);return h(o,()=>i("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function xn(e,t={}){let n=t.cwd??process.cwd(),r=Me(n);if(!r)return h({exists:!1,ref:null,status:"absent"},()=>i("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready();if(o)return h({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>i(o)),0;let s=await e.status(r);return h({...s,uri:s.uri},()=>{i(` ${S("Project")} ${s.ref}`),i(` ${S("State")} ${s.ready?"running":s.status}`),i(` ${S("Port")} ${s.port}`),i(` ${S("Size")} ${$o(s.databaseBytes)}`),s.services.length>0&&i(` ${S("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
293
- `)}),0}async function Tn(e,t=process.cwd()){let n=Me(t);return n||y("not-found","there is no local database for this folder. `snoutdata start` makes one."),e.uri(n)}j();D();var vo="2024-11-05";function O(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var P={type:"string"},Be=[{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:O({})},{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.",inputSchema:O({})},{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:O({name:P,region:P,teamId:P},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:O({ref:P},["ref"])},{name:"pause_project",description:"Stop a project. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:O({ref:P},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it.",inputSchema:O({ref:P},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:O({ref:P,dir:P,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:O({ref:P,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:O({ref:P},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:O({ref:P},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING IMMEDIATELY, including any DATABASE_URL already written into a file or a running process. Returns the new connection URL. Use it when a credential has leaked, not routinely.",inputSchema:O({ref:P},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:O({})},{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:O({})},{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. It is as capable as this account, so hand it over deliberately. Optionally expires, as an ISO date.",inputSchema:O({name:P,expires:P},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:O({id:P},["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:O({ref:P},["ref"])}];function Oo(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function we(e,t){return{jsonrpc:"2.0",id:e,result:t}}function at(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function x(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return we(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function z(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function $n(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 we(r,{protocolVersion:vo,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return we(r,{});case"tools/list":return we(r,{tools:Oo([...Be,...n.borrowed??[]])});case"tools/call":return Po(r,s,t,n);default:return at(r,-32601,`unknown method: ${o}`)}}async function Po(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Be.some(u=>u.name===o)||(r.borrowed?.some(u=>u.name===o)??!1)))return at(e,-32602,`unknown tool: ${o}`);let a=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password"],l=z(s,"ref");if(a.includes(o)&&!l)return x(e,`${o} needs a project ref. Call list_projects to see them.`,!0);try{switch(o){case"whoami":return x(e,await n.whoami());case"list_projects":return x(e,await n.listProjects());case"create_project":{let u=z(s,"name");return u?x(e,await n.createProject({name:u,region:z(s,"region")??void 0,teamId:z(s,"teamId")??void 0})):x(e,"create_project needs a name.",!0)}case"get_connection_url":{let u=await n.connection(l);return x(e,{url:u.uri,state:u.state,wakesInstantly:u.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 x(e,await n.setProjectState("pause",l));case"resume_project":return x(e,await n.setProjectState("resume",l));case"usage":{let u=typeof s.days=="number"?s.days:void 0;return x(e,await n.usage(l,u))}case"export_status":return x(e,await n.exportStatus(l));case"start_export":return x(e,await n.requestExport(l));case"reset_password":return x(e,await n.resetPassword(l));case"list_teams":return x(e,await n.listTeams());case"list_tokens":return x(e,await n.listTokens());case"create_token":{let u=z(s,"name");return u?x(e,await n.createToken(u,z(s,"expires")??void 0)):x(e,"create_token needs a name, so somebody can tell later what it was for.",!0)}case"revoke_token":{let u=z(s,"id");return u?x(e,await n.revokeToken(u)):x(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return x(e,await n.pushMigrations(l,{dir:z(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?x(e,await n.setProjectState("delete",l)):x(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(u=>u.name===o)?n.callBorrowed?we(e,await n.callBorrowed(o,s)):x(e,`${o} needs the SnoutData app, which is no longer answering.`,!0):at(e,-32602,`unknown tool: ${o}`)}}catch(u){return x(e,u instanceof Error?u.message:String(u),!0)}}function vn(e){let t=e.split(`
294
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}Ae();function Ao(){return{whoami:()=>ne(),listProjects:()=>q(),createProject:async e=>{let t=await b("cloud-project-create",{name:e.name,...e.region?{region:e.region}:{},...e.teamId?{teamId:e.teamId}:{}});try{return await de(t.project.ref)}catch{return{...t.project,ready:!1,note:`${t.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:(e,t)=>b("cloud-project-usage",{ref:e,...t?{days:t}:{}}),exportStatus:e=>re(e),requestExport:e=>ue(e),resetPassword:e=>b("cloud-project-reset-password",{ref:e}),listTeams:()=>b("cloud-team-list",{}),listTokens:()=>b("cloud-token-list",{}),createToken:(e,t)=>b("cloud-token-create",{name:e,...t?{expires:t}:{}}),revokeToken:e=>b("cloud-token-revoke",{id:e}),setProjectState:(e,t)=>b(`cloud-project-${e}`,{ref:t}),connection:e=>N(e),pushMigrations:(e,t)=>nt(e,{dir:t.dir,dryRun:t.dryRun})}}var On="app_";async function _o(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Oe();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Tt(t)).map(o=>({name:`${On}${o.name}`,description:`[SnoutData app, on this machine] ${o.description??o.name}`,inputSchema:o.inputSchema??{type:"object",properties:{}}})),config:t}}catch{return{tools:[],config:null}}}async function Pn(e){le(!0);let{tools:t,config:n}=await _o(),r=Ao();n&&(r.callBorrowed=(l,u)=>$t(n,l.slice(On.length),u));let o={...e,borrowed:t};R(t.length>0?`snoutdata mcp: ${Be.length} cloud tools, and ${t.length} borrowed from the SnoutData app on this machine.`:"snoutdata mcp: cloud tools only. The SnoutData app is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let l=await ne();R(`snoutdata mcp: signed in as ${l.email??"this account"}.`)}catch(l){return R(`snoutdata mcp: ${l instanceof Error?l.message:String(l)}`),R("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3}R(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let c=new Set;function a(l){c.add(l),l.finally(()=>c.delete(l))}return new Promise(l=>{process.stdin.on("data",u=>{let d=vn(s+u);s=d.rest;for(let m of d.messages)a(jo(m,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;c.size>0;)await Promise.allSettled([...c]);l(0)})()}),process.stdin.on("error",()=>l(1))})}async function jo(e,t,n){let r;try{r=JSON.parse(e)}catch{R(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await $n(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
431
+ );`;function Go(e){return`'${e.replace(/'/g,"''")}'`}function Cc(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 es(e,t={}){let n=await Nc(e,t);return h(n,()=>{n.created&&(l(""),l(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),l(""),l(` ${_("Database")} running on port ${n.status.port}`),n.applied.length>0&&l(` ${_("Migrations")} ${n.applied.length} applied`),n.seeded&&l(` ${_("Seed")} seed.sql ran`),l(""),process.stdout.write(`${n.uri}
432
+ `),l(x(" snoutdata gen types typescript --local > database.types.ts")),l(x(" snoutdata stop"))}),0}async function ts(e,t={}){let n=t.cwd??process.cwd(),r=mt(n);r||P("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Zo(e);let o=await e.stop(r.ref);return h(o,()=>l("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function ns(e,t={}){let n=t.cwd??process.cwd(),r=mt(n);if(!r)return h({exists:!1,ref:null,status:"absent"},()=>l("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready();if(o)return h({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>l(o)),0;let s=await e.status(r);return h({...s,uri:s.uri},()=>{l(` ${_("Project")} ${s.ref}`),l(` ${_("State")} ${s.ready?"running":s.status}`),l(` ${_("Port")} ${s.port}`),l(` ${_("Size")} ${Cc(s.databaseBytes)}`),s.services.length>0&&l(` ${_("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
433
+ `)}),0}async function rs(e,t=process.cwd()){let n=mt(t);n||P("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Qo(e,n,st(r))}}M();F();var Mc="2024-11-05";function N(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var E={type:"string"},ft=[{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:N({})},{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.",inputSchema:N({})},{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:N({name:E,region:E,teamId:E},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:N({ref:E},["ref"])},{name:"pause_project",description:"Stop a project. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:N({ref:E},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it.",inputSchema:N({ref:E},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:N({ref:E,dir:E,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:N({ref:E,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:N({ref:E},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:N({ref:E},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING IMMEDIATELY, including any DATABASE_URL already written into a file or a running process. Returns the new connection URL. Use it when a credential has leaked, not routinely.",inputSchema:N({ref:E},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:N({})},{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:N({})},{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. It is as capable as this account, so hand it over deliberately. Optionally expires, as an ISO date.",inputSchema:N({name:E,expires:E},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:N({id:E},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/functions/v1/<name>. Reads supabase/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:N({ref:E,name:E,dir:E,entrypoint:E,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, sizes, and whether each one needs an API key. Costs nothing.",inputSchema:N({ref:E},["ref"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:N({ref:E,name:E},["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:N({ref:E},["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 or a key.",inputSchema:N({ref:E},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:N({ref:E},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. The data API is on paid plans only, and a free project is refused in words.",inputSchema:N({ref:E,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:N({ref:E},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:N({ref:E,hostname:E},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:N({ref:E,hostname:E},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:N({ref:E,hostname:E},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:N({ref:E},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:N({ref:E,at:E,name:E},["ref","at"])},{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:N({ref:E},["ref"])}],Lc=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"]),Uc=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain"]);function Fc(e){return{...e,annotations:Lc.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Uc.has(e.name)}}}function Bc(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Le(e,t){return{jsonrpc:"2.0",id:e,result:t}}function gn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function v(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Le(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function j(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function os(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 Le(r,{protocolVersion:Mc,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Le(r,{});case"tools/list":return Le(r,{tools:Bc([...ft.map(Fc),...n.borrowed??[]])});case"tools/call":return Hc(r,s,t,n);default:return gn(r,-32601,`unknown method: ${o}`)}}async function Hc(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(ft.some(d=>d.name===o)||(r.borrowed?.some(d=>d.name===o)??!1)))return gn(e,-32602,`unknown tool: ${o}`);let i=["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"],u=j(s,"ref");if(i.includes(o)&&!u)return v(e,`${o} needs a project ref. Call list_projects to see them.`,!0);try{switch(o){case"whoami":return v(e,await n.whoami());case"list_projects":return v(e,await n.listProjects());case"create_project":{let d=j(s,"name");return d?v(e,await n.createProject({name:d,region:j(s,"region")??void 0,teamId:j(s,"teamId")??void 0})):v(e,"create_project needs a name.",!0)}case"get_connection_url":{let d=await n.connection(u);return v(e,{url:d.uri,state:d.state,wakesInstantly:d.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 v(e,await n.setProjectState("pause",u));case"resume_project":return v(e,await n.setProjectState("resume",u));case"usage":{let d=typeof s.days=="number"?s.days:void 0;return v(e,await n.usage(u,d))}case"export_status":return v(e,await n.exportStatus(u));case"start_export":return v(e,await n.requestExport(u));case"reset_password":return v(e,await n.resetPassword(u));case"list_teams":return v(e,await n.listTeams());case"get_project":return v(e,await n.getProject(u));case"list_products":return v(e,await n.getProducts(u));case"set_product":{let d=j(s,"product");return d!=="auth"&&d!=="storage"&&d!=="data-api"?v(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?v(e,"set_product needs enabled: true or false.",!0):v(e,await n.setProduct(u,d,s.enabled))}case"list_domains":return v(e,await n.listDomains(u));case"add_domain":case"verify_domain":case"remove_domain":{let d=j(s,"hostname");return d?v(e,await n.domainAction(u,o.split("_")[0],d)):v(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":return v(e,await n.restoreWindow(u));case"restore_to_point":{let d=j(s,"at");return d?v(e,await n.restoreTo(u,d,j(s,"name")??void 0)):v(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let d=j(s,"ref"),m=j(s,"name");return!d||!m?v(e,"deploy_function needs a project ref and a function name.",!0):v(e,await n.deployFunction(d,m,{dir:j(s,"dir")??void 0,entrypoint:j(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let d=j(s,"ref");return d?v(e,await n.listFunctions(d)):v(e,"list_functions needs a project ref.",!0)}case"delete_function":{let d=j(s,"ref"),m=j(s,"name");return!d||!m?v(e,"delete_function needs a project ref and a function name.",!0):v(e,await n.deleteFunction(d,m))}case"list_function_secrets":{let d=j(s,"ref");return d?v(e,await n.listFunctionSecrets(d)):v(e,"list_function_secrets needs a project ref.",!0)}case"list_tokens":return v(e,await n.listTokens());case"create_token":{let d=j(s,"name");return d?v(e,await n.createToken(d,j(s,"expires")??void 0)):v(e,"create_token needs a name, so somebody can tell later what it was for.",!0)}case"revoke_token":{let d=j(s,"id");return d?v(e,await n.revokeToken(d)):v(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return v(e,await n.pushMigrations(u,{dir:j(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?v(e,await n.setProjectState("delete",u)):v(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(d=>d.name===o)?n.callBorrowed?Le(e,await n.callBorrowed(o,s)):v(e,`${o} needs the SnoutData app, which is no longer answering.`,!0):gn(e,-32602,`unknown tool: ${o}`)}}catch(d){return v(e,d instanceof Error?d.message:String(d),!0)}}function ss(e){let t=e.split(`
434
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}Ke();function qc(){return{whoami:()=>be(),listProjects:()=>V(),createProject:async e=>{let t=await S("cloud-project-create",{name:e.name,...e.region?{region:e.region}:{},...e.teamId?{teamId:e.teamId}:{}});try{return await $e(t.project.ref)}catch{return{...t.project,ready:!1,note:`${t.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:(e,t)=>S("cloud-project-usage",{ref:e,...t?{days:t}:{}}),exportStatus:e=>Se(e),requestExport:e=>Te(e),resetPassword:e=>S("cloud-project-reset-password",{ref:e}),listTeams:()=>S("cloud-team-list",{}),deployFunction:(e,t,n)=>Dt(e,t,n).then(r=>r.answer),listFunctions:e=>It(e),deleteFunction:(e,t)=>jt(e,t),listFunctionSecrets:e=>S("cloud-project-secrets",{ref:e}),listTokens:()=>S("cloud-token-list",{}),createToken:(e,t)=>S("cloud-token-create",{name:e,...t?{expires:t}:{}}),revokeToken:e=>S("cloud-token-revoke",{id:e}),getProject:e=>Jt(e),getProducts:e=>tt(e),setProduct:(e,t,n)=>Ft(e,t,n),listDomains:e=>nt(e),domainAction:(e,t,n)=>Bt(e,t,n),restoreWindow:e=>Ht(e),restoreTo:(e,t,n)=>qt(e,t,n),setProjectState:(e,t)=>S(`cloud-project-${e}`,{ref:t}),connection:e=>W(e),pushMigrations:(e,t)=>Vt(e,{dir:t.dir,dryRun:t.dryRun})}}var as="app_";async function Jc(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Je();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await In(t)).map(o=>({name:`${as}${o.name}`,description:`[SnoutData app, on this machine] ${o.description??o.name}`,inputSchema:o.inputSchema??{type:"object",properties:{}}})),config:t}}catch{return{tools:[],config:null}}}async function is(e){_e(!0);let{tools:t,config:n}=await Jc(),r=qc();n&&(r.callBorrowed=(u,d)=>jn(n,u.slice(as.length),d));let o={...e,borrowed:t};J(t.length>0?`snoutdata mcp: ${ft.length} cloud tools, and ${t.length} borrowed from the SnoutData app on this machine.`:"snoutdata mcp: cloud tools only. The SnoutData app is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let u=await be();J(`snoutdata mcp: signed in as ${u.email??"this account"}.`)}catch(u){return J(`snoutdata mcp: ${u instanceof Error?u.message:String(u)}`),J("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3}J(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function i(u){a.add(u),u.finally(()=>a.delete(u))}return new Promise(u=>{process.stdin.on("data",d=>{let m=ss(s+d);s=m.rest;for(let c of m.messages)i(Wc(c,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);u(0)})()}),process.stdin.on("error",()=>u(1))})}async function Wc(e,t,n){let r;try{r=JSON.parse(e)}catch{J(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await os(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
295
435
  `)}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)}})}
296
- `)}}j();D();function Eo(e){return e>=1?"over":e>=.8?"near":"ok"}function An(e,t){let n=null,r=null,o=0,s=0;for(let a of e)o+=Number.isFinite(a.computeSeconds)?a.computeSeconds:0,s+=Number.isFinite(a.connections)?a.connections:0,!(a.dbBytes===null||!Number.isFinite(a.dbBytes))&&(r=r===null?a.dbBytes:Math.max(r,a.dbBytes),(!n||a.day>=n.day)&&(n={day:a.day,dbBytes:a.dbBytes,repoBytes:a.repoBytes}));let c=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:Eo(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:c}}function it(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 _n(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=An(n.days,n.limit);return h({...n,summary:r},()=>{if(i(S(`${n.name} (${n.ref})`)),!r.latest)i("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${F(r.storage.usedBytes)} of ${F(r.storage.limitBytes)} on ${n.limit?.tier}`;i(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 i(`${F(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&i("This project is currently read-only."),i(`Over ${r.days} day${r.days===1?"":"s"}: ${it(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&i(k(`Peak in that window was ${F(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(i(""),i(M([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":F(o.dbBytes),o.repoBytes===null?"-":F(o.repoBytes),it(o.computeSeconds),String(o.connections)])])),i(k("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}var be="0.2.0",jn=[{name:"init",summary:"Create or link a project and write a DATABASE_URL",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, or with --device by typing a code into one anywhere",flags:["--provider","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to",flags:[]},{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"]},{name:"tokens revoke",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",flags:["--name","--region","--team","--no-wait","--timeout"]},{name:"projects pause",summary:"Stop a project",flags:["--ref"]},{name:"projects resume",summary:"Start a paused project",flags:["--ref"]},{name:"projects delete",summary:"Delete a project",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 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",flags:["--file","--force"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],En=`snoutdata ${be} \u2014 hosted Postgres, from a terminal or an agent
436
+ `)}}M();F();function Kc(e){return e>=1?"over":e>=.8?"near":"ok"}function cs(e,t){let n=null,r=null,o=0,s=0;for(let i of e)o+=Number.isFinite(i.computeSeconds)?i.computeSeconds:0,s+=Number.isFinite(i.connections)?i.connections:0,!(i.dbBytes===null||!Number.isFinite(i.dbBytes))&&(r=r===null?i.dbBytes:Math.max(r,i.dbBytes),(!n||i.day>=n.day)&&(n={day:i.day,dbBytes:i.dbBytes,repoBytes:i.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:Kc(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function hn(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 ls(e,t={}){let n=await S("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=cs(n.days,n.limit);return h({...n,summary:r},()=>{if(l(_(`${n.name} (${n.ref})`)),!r.latest)l("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${Q(r.storage.usedBytes)} of ${Q(r.storage.limitBytes)} on ${n.limit?.tier}`;l(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 l(`${Q(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&l("This project is currently read-only."),l(`Over ${r.days} day${r.days===1?"":"s"}: ${hn(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&l(x(`Peak in that window was ${Q(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(l(""),l(L([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":Q(o.dbBytes),o.repoBytes===null?"-":Q(o.repoBytes),hn(o.computeSeconds),String(o.connections)])])),l(x("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}var Ue="0.4.0",us=`snoutdata ${Ue} \u2014 hosted Postgres, from a terminal or an agent
297
437
 
298
438
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use
299
439
  snoutdata login [--provider github] sign in through a browser
440
+ snoutdata login --sso [--domain D] sign in with your company's identity provider;
441
+ D is a work email or a domain, and it is asked
442
+ for when not given. Only the domain is sent
300
443
  snoutdata login --device sign in by typing a code into a browser anywhere
301
444
  snoutdata login --no-browser print the URL instead of opening one
302
445
  snoutdata logout
@@ -311,6 +454,14 @@ ${o.err.trim()}`),i(" ran seed.sql"),!0}var ge="_snoutdata_migrations",To=`crea
311
454
  snoutdata projects pause|resume|delete [--ref R]
312
455
  snoutdata link --ref R write .snoutdata/project.json here
313
456
 
457
+ snoutdata projects show [--ref R] one project: state, products, functions, domains
458
+
459
+ snoutdata products [--ref R] auth, storage and the data API: on or off
460
+ snoutdata products enable|disable auth|storage|data-api [--ref R]
461
+
462
+ snoutdata domains [--ref R] your own domains in front of the project's API
463
+ snoutdata domains add|verify|remove <hostname> [--ref R]
464
+
314
465
  snoutdata usage [--ref R] [--days 30] [--history] size, compute, and the plan's limit
315
466
 
316
467
  snoutdata start [--port 54322] [--dir migrations] a Postgres here, migrated and seeded
@@ -320,6 +471,16 @@ ${o.err.trim()}`),i(" ran seed.sql"),!0}var ge="_snoutdata_migrations",To=`crea
320
471
  snoutdata gen types typescript [--local] [--schema public] [--out FILE]
321
472
  the schema as a TypeScript Database type
322
473
 
474
+ snoutdata functions deploy <name> [--dir D] [--no-verify-jwt]
475
+ from supabase/functions/<name>, onto the edge
476
+ snoutdata functions list [--ref R]
477
+ snoutdata functions delete <name>
478
+
479
+ snoutdata secrets set NAME=value [...] the environment functions run with
480
+ snoutdata secrets set NAME --stdin the value from a pipe, not from history
481
+ snoutdata secrets list names and sizes; values are never readable
482
+ snoutdata secrets unset NAME
483
+
323
484
  snoutdata keys [--ref R] the anon and service_role API keys
324
485
  snoutdata keys rotate --force [--ref R] new ones, breaking every key already issued
325
486
 
@@ -331,6 +492,8 @@ ${o.err.trim()}`),i(" ran seed.sql"),!0}var ge="_snoutdata_migrations",To=`crea
331
492
  snoutdata db export --status [--ref R] what the last copy is doing
332
493
  snoutdata db push [--dir migrations] [--dry-run] run the .sql files, once each
333
494
  snoutdata db restore --file DUMP [--force] put a dump into this project
495
+ snoutdata db restore --window [--ref R] how far back a point-in-time restore can go
496
+ snoutdata db restore --at TIME [--name N] that moment, into a NEW project beside this one
334
497
 
335
498
  snoutdata mcp [--allow-delete] serve these operations to an agent, over stdio
336
499
  (plus the SnoutData app's own tools, when it is
@@ -349,10 +512,10 @@ A project comes from --ref, then SNOUTDATA_PROJECT, then
349
512
  .snoutdata/project.json in this folder or a parent. A token comes from
350
513
  SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes lasts
351
514
  an hour, so CI wants "tokens create", which does not expire.
352
- `;async function Ro(e){let[t,n,...r]=e.command;if(I(e,"version")||t==="version")return h({version:be},()=>process.stdout.write(`${be}
353
- `)),0;if(I(e,"help")&&t&&t!=="help"){let a=jn.filter(l=>l.name===t||l.name.startsWith(`${t} `));if(a.length>0)return h({commands:a},()=>{for(let l of a){let u=l.flags.length>0?` ${l.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${l.name}${u}
354
- ${l.summary}
515
+ `;async function Vc(e){let[t,n,...r]=e.command;if(U(e,"version")||t==="version")return h({version:Ue},()=>process.stdout.write(`${Ue}
516
+ `)),0;if(U(e,"help")&&t&&t!=="help"){let i=gt.filter(u=>u.name===t||u.name.startsWith(`${t} `));if(i.length>0)return h({commands:i},()=>{for(let u of i){let d=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${d}
517
+ ${u.summary}
355
518
  `)}process.stdout.write(`
356
519
  Every command also takes --json, --quiet and --help.
357
- `)}),0}let o=I(e,"help")||t==="help";if(o||!t)return h({version:be,usage:En.trim(),commands:jn},()=>process.stdout.write(En)),o?0:K.usage;let s=()=>{let a=ie(e,"timeout");if(a!==void 0){if(!(a>0))throw new T("--timeout is a number of seconds greater than zero");return a*1e3}},c=()=>{let a=xe({flag:$(e,"ref")});if(!a)throw new T("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return a};switch(t){case"login":{if(I(e,"device")){let l=await _e({name:Pe(),platform:process.platform});h({verifyUrl:l.verifyUrl,code:l.code,expiresAt:l.expiresAt},()=>{i(""),i(` Open ${l.verifyUrl}`),i(` Type ${l.code}`),i(""),i(k("Waiting for that to be approved\u2026"))});let u=await je(l.secret,{intervalMs:Math.max(1,l.interval)*1e3,expiresAt:l.expiresAt});return L({accessToken:u.token}),h({ok:!0,name:u.name??null},()=>i("Signed in.")),0}let a=await Ne({provider:$(e,"provider"),noBrowser:I(e,"no-browser")||!ee(),timeoutMs:s()});return h({email:a.email??null},()=>i(`Signed in${a.email?` as ${a.email}`:""}.`)),0}case"logout":{ht();let a=!!process.env.SNOUTDATA_ACCESS_TOKEN;return h({ok:!0,environmentTokenStillSet:a},()=>{i("Signed out."),a&&(i("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),i(k(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let a=await ne();return h(a,()=>{process.stdout.write(`${a.email??a.id}
358
- `),a.token&&i(`via access token ${a.token.prefix} (${a.token.name}).`)}),0}case"tokens":switch(n){case void 0:case"list":return await Vt(),0;case"create":{let a=$(e,"name")??r[0];if(!a)throw new T("tokens create needs --name (what is it for, and where does it live)");return await Jt({name:a,expiresInDays:ie(e,"expires")}),0}case"revoke":{let a=r[0];if(!a)throw new T("tokens revoke needs the token's id or its sdt_ prefix");return await zt(a),0}default:throw new T(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await qt(),0;case"create":{let a=$(e,"name")??r[0];if(!a)throw new T("projects create needs --name");let l=$(e,"team");return await Ye({teamId:l?await Kt(l):void 0,name:a,region:$(e,"region"),wait:!I(e,"no-wait"),timeoutMs:s(),link:void 0}),0}case"pause":case"resume":case"delete":return await Ft(n,$(e,"ref")??r[0]??c()),0;default:throw new T(`unknown command: projects ${n}`)}case"keys":switch(n){case void 0:case"show":case"list":return await Ht(c()),0;case"rotate":return await Yt(c(),{force:I(e,"force")}),0;default:throw new T(`unknown command: keys ${n}`)}case"teams":return await Gt(),0;case"link":{let a=$(e,"ref")??n;if(!a)throw new T("link needs --ref");let l=Se(process.cwd(),{ref:a});return h({ref:a,path:l},()=>i(`Linked ${a} (${l}).`)),0}case"db":switch(n){case"url":return await Qe(c()),0;case"psql":return sn(c(),r);case"reset-password":return await Wt(c()),0;case"push":return pn(c(),{dir:$(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return ln(c(),{file:$(e,"file"),force:!!e.flags.force});case"export":return cn(c(),{timeoutMs:s(),out:$(e,"out"),statusOnly:!!e.flags.status});default:throw new T(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, export, push, restore")}case"gen":{if(n!=="types")throw new T(n?`unknown command: gen ${n}`:"gen needs an action: types");let a=I(e,"local")?await Tn(await ye()):$(e,"db-url");return gn(r[0],{ref:a?void 0:c(),dbUrl:a,schemas:($(e,"schema")??"public").split(",").map(l=>l.trim()).filter(Boolean),out:$(e,"out"),postgrestVersion:$(e,"postgrest-version")})}case"start":return kn(await ye(),{port:ie(e,"port"),dir:$(e,"dir"),noMigrations:I(e,"no-migrations"),outOfOrder:I(e,"out-of-order")});case"stop":return Sn(await ye());case"status":return xn(await ye());case"usage":return _n(c(),{days:ie(e,"days"),history:!!e.flags.history});case"mcp":return Pn({version:be,allowDelete:!!e.flags["allow-delete"]});case"init":return Do(e);default:throw new T(`unknown command: ${t}`)}}async function Do(e){let t=xe({flag:$(e,"ref")});if(t)return i(`This folder is already linked to ${t}.`),await Qe(t),0;let n=$(e,"name")??No(process.cwd());i(`Creating a project called "${n}"\u2026`),await Ye({name:n,region:$(e,"region"),wait:!0,link:process.cwd()});let r=xe({cwd:process.cwd()});if(r&&I(e,"env")){let{connection:o}=await Promise.resolve().then(()=>(j(),oe)),s=await o(r),c=an(process.cwd(),s.uri);i(`${c.replaced?"Updated":"Wrote"} DATABASE_URL in ${c.path}.`)}return 0}function No(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Io(e){if(e instanceof G)return Y(e.code,e.message,e.details),e.exitCode;if(e instanceof T)return Y("usage",e.message),K.usage;if(e instanceof V)return Y("not-signed-in",e.message),K["not-signed-in"];if(e instanceof B){let n=Ot(e.status);return Y(n,e.message,{status:e.status}),K[n]}let t=Pt(e);return Y(t,e instanceof Error?e.message:String(e)),K[t]}function ct(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Co(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Lo(){le(Co(process.argv.slice(2)));let e;try{e=lt(process.argv.slice(2))}catch(t){Y("usage",String(t instanceof Error?t.message:t)),ct(K.usage);return}le(e.json),jt(e.flags.quiet===!0),bt(Te({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{ct(await Ro(e))}catch(t){ct(Io(t))}}Lo();
520
+ `)}),0}let o=U(e,"help")||t==="help";if(o||!t)return h({version:Ue,usage:us.trim(),commands:gt},()=>process.stdout.write(us)),o?0:ne.usage;let s=()=>{let i=Ee(e,"timeout");if(i!==void 0){if(!(i>0))throw new w("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=He({flag:A(e,"ref")});if(!i)throw new w("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i};switch(t){case"login":{if(U(e,"device")){let m=await Ge({name:We(),platform:process.platform});h({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{l(""),l(` Open ${m.verifyUrl}`),l(` Type ${m.code}`),l(""),l(x("Waiting for that to be approved\u2026"))});let c=await ze(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});return z({accessToken:c.token}),h({ok:!0,name:c.name??null},()=>l("Signed in.")),0}let i=A(e,"domain"),u=U(e,"sso")||i!==void 0;if(u&&A(e,"provider"))throw new w("--sso and --provider are two different doors: pass one or the other");let d=await Ze({provider:A(e,"provider"),sso:u,domain:i,noBrowser:U(e,"no-browser")||!te(),timeoutMs:s()});return h({email:d.email??null},()=>l(`Signed in${d.email?` as ${d.email}`:""}.`)),0}case"logout":{Tn();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return h({ok:!0,environmentTokenStillSet:i},()=>{l("Signed out."),i&&(l("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),l(x(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await be();return h(i,()=>{process.stdout.write(`${i.email??i.id}
521
+ `),i.token&&l(`via access token ${i.token.prefix} (${i.token.name}).`)}),0}case"tokens":switch(n){case void 0:case"list":return await rr(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new w("tokens create needs --name (what is it for, and where does it live)");return await nr({name:i,expiresInDays:Ee(e,"expires")}),0}case"revoke":{let i=r[0];if(!i)throw new w("tokens revoke needs the token's id or its sdt_ prefix");return await or(i),0}default:throw new w(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Xn(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new w("projects create needs --name");let u=A(e,"team");return await Tt({teamId:u?await tr(u):void 0,name:i,region:A(e,"region"),wait:!U(e,"no-wait"),timeoutMs:s(),link:void 0}),0}case"pause":case"resume":case"delete":return await Zn(n,A(e,"ref")??r[0]??a()),0;case"show":return await Ir(A(e,"ref")??r[0]??a()),0;default:throw new w(`unknown command: projects ${n}`)}case"functions":switch(n){case"deploy":{let i=r[0];if(!i)throw new w("functions deploy needs a name: snoutdata functions deploy <name>");return await mr(a(),i,{dir:A(e,"dir"),entrypoint:A(e,"entrypoint"),verifyJwt:!U(e,"no-verify-jwt")}),0}case void 0:case"list":return await fr(a()),0;case"delete":case"remove":{let i=r[0];if(!i)throw new w("functions delete needs a name");return await pr(a(),i),0}default:throw new w(`unknown command: functions ${n}`)}case"secrets":switch(n){case"set":return await gr(a(),r,{stdin:U(e,"stdin")}),0;case void 0:case"list":return await yr(a()),0;case"unset":case"remove":{let i=r[0];if(!i)throw new w("secrets unset needs a name");return await hr(a(),i),0}default:throw new w(`unknown command: secrets ${n}`)}case"keys":switch(n){case void 0:case"show":case"list":return await sr(a()),0;case"rotate":return await ar(a(),{force:U(e,"force")}),0;default:throw new w(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Ar(a()),0;if(n==="enable"||n==="disable")return await Rr(a(),$r(r[0]),n==="enable"),0;throw new w(`unknown command: products ${n}. products [enable|disable ${et.join("|")}]`)}case"domains":return await Or(a(),n,r[0]),0;case"teams":return await er(),0;case"link":{let i=A(e,"ref")??n;if(!i)throw new w("link needs --ref");let u=Be(process.cwd(),{ref:i});return h({ref:i,path:u},()=>l(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await Ut(a()),0;case"psql":return Pr(a(),r);case"reset-password":return await Qn(a()),0;case"push":return Lr(a(),{dir:A(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return U(e,"window")?(await Nr(a()),0):A(e,"at")?(await Dr(a(),A(e,"at"),A(e,"name")),0):Tr(a(),{file:A(e,"file"),force:!!e.flags.force});case"export":return _r(a(),{timeoutMs:s(),out:A(e,"out"),statusOnly:!!e.flags.status});default:throw new w(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, export, push, restore")}case"gen":{if(n!=="types")throw new w(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=U(e,"local")?await rs(await Me()):null,u=i?i.dbUrl:A(e,"db-url");return Br(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(A(e,"schema")??"public").split(",").map(d=>d.trim()).filter(Boolean),out:A(e,"out"),dataApiVersion:A(e,"data-api-version")??A(e,"postgrest-version"),sql:i?.sql})}case"start":return es(await Me(),{port:Ee(e,"port"),dir:A(e,"dir"),noMigrations:U(e,"no-migrations"),outOfOrder:U(e,"out-of-order")});case"stop":return ts(await Me());case"status":return ns(await Me());case"usage":return ls(a(),{days:Ee(e,"days"),history:!!e.flags.history});case"mcp":return is({version:Ue,allowDelete:!!e.flags["allow-delete"]});case"init":return Gc(e);default:throw new w(`unknown command: ${t}`)}}async function Gc(e){let t=He({flag:A(e,"ref")});if(t)return l(`This folder is already linked to ${t}.`),await Ut(t),0;let n=A(e,"name")??zc(process.cwd());l(`Creating a project called "${n}"\u2026`),await Tt({name:n,region:A(e,"region"),wait:!0,link:process.cwd()});let r=He({cwd:process.cwd()});if(r&&U(e,"env")){let{connection:o}=await Promise.resolve().then(()=>(M(),de)),s=await o(r),a=Er(process.cwd(),s.uri);l(`${a.replaced?"Updated":"Wrote"} DATABASE_URL in ${a.path}.`)}return 0}function zc(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Yc(e){if(e instanceof q)return ue(e.code,e.message,e.details),e.exitCode;if(e instanceof w)return ue("usage",e.message),ne.usage;if(e instanceof oe)return ue("not-signed-in",e.message),ne["not-signed-in"];if(e instanceof Z){let n=Ve(e.status);return ue(n,e.message,{status:e.status}),ne[n]}let t=Mn(e);return ue(t,e instanceof Error?e.message:String(e)),ne[t]}function yn(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Xc(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function Zc(){_e(Xc(process.argv.slice(2)));let e;try{e=Sn(process.argv.slice(2))}catch(t){ue("usage",String(t instanceof Error?t.message:t)),yn(ne.usage);return}_e(e.json),Fn(e.flags.quiet===!0),Rn(qe({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{yn(await Vc(e))}catch(t){yn(Yc(t))}}Zc();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snoutdata",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Hosted Postgres from a terminal or an agent. Create a database, get a DATABASE_URL, run migrations, and serve the same operations to an AI agent over MCP.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "SnoutData",
@@ -35,7 +35,7 @@
35
35
  "node": ">=20"
36
36
  },
37
37
  "scripts": {
38
- "build": "tsc -p tsconfig.json && node build.mjs",
38
+ "build": "node ../cloud/sync-engine.mjs cli cliControl && tsc -p tsconfig.json && node build.mjs",
39
39
  "typecheck": "tsc -p tsconfig.json --noEmit",
40
40
  "test": "tsc -p tsconfig.json && node --test \"out/**/*.test.js\"",
41
41
  "prepublishOnly": "npm run build"