snoutdata 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/snoutdata.mjs +291 -40
  2. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var tn=Object.defineProperty;var _=(e,t)=>()=>(e&&(t=e(e=0)),t);var nn=(e,t)=>{for(var n in t)tn(e,n,{get:t[n],enumerable:!0})};import{chmodSync as an,existsSync as Ke,mkdirSync as ze,readFileSync as Ye,rmSync as cn,writeFileSync as Ve}from"node:fs";import{homedir as le}from"node:os";import{dirname as $e,join as Xe,parse as ln,resolve as dn}from"node:path";function Pe(e=le()){return Xe(e,".snoutdata","auth.json")}function Ze(e=le()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=Pe(e);if(!Ke(n))return null;try{let r=JSON.parse(Ye(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function j(e,t=le()){let n=Pe(t);ze($e(n),{recursive:!0}),Ve(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),an(n,384)}function Qe(e=le()){cn(Pe(e),{force:!0})}function et(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function tt(e){return Xe(e,".snoutdata","project.json")}function un(e){let t=dn(e),n=ln(t).root;for(;;){let r=tt(t);if(Ke(r))try{let o=JSON.parse(Ye(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=$e(t)}}function de(e,t){let n=tt(e);return ze($e(n),{recursive:!0}),Ve(n,`${JSON.stringify(t,null,2)}
4
- `),n}function ue(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:un(e.cwd??process.cwd())?.ref??null}var z=_(()=>{"use strict"});function pe(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 pn){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 me(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 nt(e){Ee=e}function Y(){return(Ee??pe({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function fe(){return(Ee??pe({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var pn,Ee,ee=_(()=>{"use strict";pn=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];Ee=null});import{existsSync as mn,readFileSync as fn}from"node:fs";import{homedir as Oe,hostname as gn}from"node:os";import{join as V}from"node:path";function hn(e,t,n){if(t==="win32"){let r=n.APPDATA??V(Oe(),"AppData","Roaming");return V(r,e)}return t==="darwin"?V(Oe(),"Library","Application Support",e):V(n.XDG_CONFIG_HOME??V(Oe(),".config"),e)}function ot(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>V(hn(n,e,t),"mcp.json"))}function wn(e=process.platform,t=process.env){return ot(e,t)[0]}function yn(e=wn()){if(!mn(e))return null;try{let t=JSON.parse(fn(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function kn(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}async function ge(e){if(e!==void 0)return rt(e);let t="no-config";for(let n of ot()){let r=yn(n);if(!r)continue;let o=await rt(r);if(o.available)return o;(o.skip!=="not-running"||t==="no-config")&&(t=o.skip)}return{available:!1,skip:t}}async function rt(e){let t=kn(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(bn)});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 st(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 he(){return gn()}async function _e(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 at(e){return await _e(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await _e(e,"tools/list",{},3e3)).tools??[]}async function it(e,t,n){return _e(e,"tools/call",{name:t,arguments:n},12e4)}var bn,we=_(()=>{"use strict";bn=1500});function g(e,t,n){throw new B(e,t,n)}function lt(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 dt(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 L,B,xn,te=_(()=>{"use strict";L={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},B=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return L[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 ut(e,t){let n=await fetch(`${R}/functions/v1/${e}`,{method:"POST",headers:{apikey:F,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return{status:n.status,body:r}}async function ye(e={}){let{status:t,body:n}=await ut("cloud-device-start",{client:e});return t===429&&g("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&g("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)&&g("server",n.error??`could not start a pairing (${t})`),n}async function ke(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&g("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(d=>setTimeout(d,n));let{status:o,body:s}=await ut("cloud-device-poll",{secret:e});switch(o===404&&g("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&g("usage",s.error??"that is not a pairing secret"),o!==200&&g("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":g("forbidden",s.error??"that pairing was refused in the browser");case"used":g("conflict",s.error??"that pairing has already been completed");case"expired":g("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=_(()=>{"use strict";T();te()});function ne(e){be=e}function mt(e){pt=e}function y(e){return ft?`\x1B[2m${e}\x1B[0m`:e}function A(e){return ft?`\x1B[1m${e}\x1B[0m`:e}function p(e,t){if(be){process.stdout.write(`${JSON.stringify(e,null,2)}
5
- `);return}t()}function i(e){!be&&!pt&&process.stderr.write(`${e}
6
- `)}function G(e,t,n){process.stderr.write(`${t}
7
- `),be&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
8
- `)}function $(e){process.stderr.write(`${e}
9
- `)}function W(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 H(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,d="second";for(let[c,l]of o){if(s<c)break;s=Math.round(s/c),d=l}let a=`${s} ${d}${s===1?"":"s"}`;return n?`in ${a}`:`${a} ago`}var be,pt,ft,E=_(()=>{"use strict";be=!1,pt=!1;ft=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as gt,randomBytes as Re}from"node:crypto";import{createServer as ht}from"node:http";import{spawn as vn}from"node:child_process";function xe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Se(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.":Nn(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
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>
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,40 +19,281 @@ var tn=Object.defineProperty;var _=(e,t)=>()=>(e&&(t=e(e=0)),t);var nn=(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 wt(){let e=xe(Re(32)),t=xe(gt("sha256").update(e).digest());return{verifier:e,challenge:t}}function Tn(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 yt(e){let{command:t,args:n,verbatim:r}=Tn(process.platform,e),o=vn(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function En(e){let t=e??xe(Re(32));return{raw:t,hashed:gt("sha256").update(t).digest("hex")}}function On(e){return`${An}?${new URLSearchParams({client_id:kt,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 _n(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 jn(e,t,n){let r=await fetch($n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:kt,client_secret:Pn,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 Rn(e){let{verifier:t,challenge:n}=wt(),{raw:r,hashed:o}=En(),s=xe(Re(16)),{code:d,redirectUri:a}=await new Promise((w,m)=>{let k="",u=ht((O,K)=>{let P=_n(O.url??"/",s);if(P.kind==="ignore"){K.writeHead(404).end();return}K.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),K.end(P.kind==="ok"?Se("ok"):Se("failed",P.message)),u.close(),P.kind==="ok"?w({code:P.code,redirectUri:k}):m(new Error(P.message))});u.listen(0,"127.0.0.1",()=>{let O=u.address();k=`http://127.0.0.1:${typeof O=="object"&&O?O.port:0}`;let P=On({redirectUri:k,challenge:n,hashedNonce:o,state:s});process.stdout.write(`${P}
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."),yt(P))}),u.on("error",m),setTimeout(()=>{u.close(),m(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()}),c=await jn(d,t,a),l=await fetch(`${R}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:F,"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:c,nonce:r})});if(!l.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await l.text()).slice(0,200)}`);return bt(await l.json())}function bt(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return j(n),n}async function ve(e={}){let t=e.provider??"google";if(t==="google")return Rn(e);let{verifier:n,challenge:r}=wt(),o=await new Promise((d,a)=>{let c=ht((w,m)=>{let k=new URL(w.url??"/","http://127.0.0.1"),u=k.searchParams.get("code"),M=k.searchParams.get("error_description")??k.searchParams.get("error");m.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),m.end(u?Se("ok"):Se("failed",M??"No code came back.")),c.close(),u?d(u):a(new Error(M??"the browser came back without a code"))});c.listen(0,"127.0.0.1",()=>{let w=c.address(),k=`http://127.0.0.1:${typeof w=="object"&&w?w.port:0}/callback`,u=new URL(`${R}/auth/v1/authorize`);u.searchParams.set("provider",t),u.searchParams.set("redirect_to",k),u.searchParams.set("code_challenge",r),u.searchParams.set("code_challenge_method","s256"),u.searchParams.set("prompt","select_account"),process.stdout.write(`${u.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."),yt(u.toString()))}),setTimeout(()=>{c.close(),a(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()}),s=await fetch(`${R}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:F,"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 bt(await s.json())}function Nn(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var An,$n,kt,Pn,Ne=_(()=>{"use strict";T();z();E();An="https://accounts.google.com/o/oauth2/v2/auth",$n="https://oauth2.googleapis.com/token",kt="1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com",Pn="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"});import{createInterface as Dn}from"node:readline/promises";async function De(e){let t=Dn({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 Cn(){let e=await ge();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&i(y("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 ${A(t)}.`),i(y("It can create an access token for this terminal. Your sign-in itself is not handed over,")),i(y("and you can revoke the token later with `snoutdata tokens revoke`.")),!await De("Use it?"))return null;i("Asking the app. Approve it there.");let n=await st(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 j(r),i(`Signed in with "${n.name}".`),r}async function In(){if(!await De("Sign in with a browser?"))return null;try{let e=await ve({});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 Mn(){if(!await De("Sign in by typing a code into a browser on another machine?"))return null;let e=await ye({name:he(),platform:process.platform});i(""),i(` Open ${A(e.verifyUrl)}`),i(` Type ${A(e.code)}`),i(""),i(y("The code is typed rather than in the link on purpose: it is what proves the person")),i(y("at the browser is the person at this terminal. Waiting\u2026"));let t=await ke(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return j(n),i(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function xt(){if(!Y())return null;let e=await Cn();if(e)return e;let t=await In();return t||Mn()}var St=_(()=>{"use strict";z();we();je();ee();Ne();E()});var Q={};nn(Q,{ANON_KEY:()=>F,ApiError:()=>N,FUNCTIONS:()=>vt,NotSignedIn:()=>q,SUPABASE_URL:()=>R,call:()=>f,connection:()=>C,exportStatus:()=>Z,listProjects:()=>D,listTeams:()=>Te,requestExport:()=>re,waitForReady:()=>oe,whoami:()=>X});function Un(){let e=fe();return e?`not signed in, and ${me(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 Bn(){let e=Ze();if(e||(e=await xt()),!e)throw new q;if(!et(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${R}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:F,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new q;let n=await t.json(),r={accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at,email:e.email};return j(r),r.accessToken}async function f(e,t={},n="POST"){let r=await Bn(),o=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",s=await fetch(`${vt}/${e}${o}`,{method:n,headers:{Authorization:`Bearer ${r}`,apikey:F,"Content-Type":"application/json"},body:n==="GET"?void 0:JSON.stringify(t)}),d=await s.text(),a;try{a=d?JSON.parse(d):{}}catch{throw new N(s.status,d.slice(0,300)||s.statusText)}if(!s.ok){let c=typeof a=="object"&&a&&"error"in a?String(a.error):`${s.status} ${s.statusText}`;throw s.status===401?new q(c):new N(s.status,c)}return a}async function X(){return f("cloud-whoami")}async function D(){return f("cloud-project-list")}async function C(e){return f("cloud-project-connection",{ref:e})}async function Te(){return f("cloud-team-list")}async function re(e){return f("cloud-project-export",{ref:e})}async function Z(e){return f("cloud-project-export",{ref:e},"GET")}async function oe(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await D(),o=r.find(s=>s.ref===e);if(!o)throw new N(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new N(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var R,F,vt,N,q,T=_(()=>{"use strict";z();ee();St();R=process.env.SNOUTDATA_SUPABASE_URL??"https://krlhuuvhhgkwfsutvxtb.supabase.co",F=process.env.SNOUTDATA_ANON_KEY??"sb_publishable_B0gbZinAncINGbDGOcP9rg_odcbriG3",vt=`${R}/functions/v1`,N=class extends Error{status;constructor(t,n){super(n),this.status=t}},q=class extends Error{constructor(t){super(t??Un())}}});var b=class extends Error{},rn=new Set(["name","region","ref","file","provider","expires","timeout","out","team","dir","days"]);function He(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,d]=sn(o);if(rn.has(s)){let a=d??e[++r];if(a===void 0)throw new b(`--${s} needs a value`);n[s]=a;continue}if(d!==void 0)throw new b(`--${s} does not take a value`);if(!on.has(s))throw new b(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}var on=new Set(["json","help","version","force","quiet","status","no-wait","no-browser","device","env","dry-run","out-of-order","allow-delete","history"]);function sn(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function x(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function U(e,t){return e.flags[t]===!0}function ce(e,t){let n=x(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new b(`--${t} must be a whole number, not "${n}"`);return r}T();te();ee();z();je();we();E();Ne();T();T();z();E();async function Tt(){let{projects:e,allowance:t}=await D();p({projects:e,allowance:t},()=>{if(e.length===0){i('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${W([["REF","NAME","STATE","REGION","LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?A("read-only"):n.state==="ready"?n.state:A(n.state),n.region,H(n.lastConnectionAt)])])}
25
- `)})}async function Ce(e){let t=await f("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 oe(n,{timeoutMs:e.timeoutMs,onTick:o=>i(y(` ${o}`))})),e.link&&de(e.link,{ref:n,name:r.name}),p({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. ${A(n)} in ${r.region}.`),i("The password is stored for you: `snoutdata db url` prints this again.")})}async function At(e,t){let n=`cloud-project-${e}`,r=await f(n,{ref:t});p(r,()=>{i(r.changed?`${t}: ${e}d.`:`${t} was already ${e==="delete"?"deleted":`${e}d`}.`)})}async function $t(e){let t=await f("cloud-project-reset-password",{ref:e});p(t,()=>{process.stdout.write(`${t.password}
27
- `),i(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Pt(){let{teams:e}=await Te();p({teams:e},()=>{if(e.length===0){i("You are not on any teams.");return}process.stdout.write(`${W([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
28
- `)})}async function Et(e){let{teams:t}=await Te(),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}T();E();async function Ot(e){let t=await f("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays});p(t,()=>{process.stdout.write(`${t.token}
29
- `),i(""),i(`${A(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(y(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function _t(){let{tokens:e}=await f("cloud-token-list");p({tokens:e},()=>{if(e.length===0){i("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${W([["PREFIX","NAME","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?A(t.prefix):t.prefix,t.name,t.revokedAt?"revoked":t.live?"live":"expired",H(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
30
- `)})}async function jt(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await f("cloud-token-revoke",t);p(n,()=>{i(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}import{spawn as Me}from"node:child_process";import{appendFileSync as qn,existsSync as It,readFileSync as Jn,writeFileSync as Ct}from"node:fs";import{join as Gn}from"node:path";import{open as Mt}from"node:fs/promises";function Rt(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 Nt(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 Dt(e){let t=e.split(`
31
- `).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}}T();te();ee();T();E();async function Ut(e){try{let{projects:t}=await D(),n=t.find(r=>r.ref===e);n?.readOnly?$(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&$(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function Ue(e){await Ut(e);let t=await C(e);p(t,()=>{process.stdout.write(`${t.uri}
32
- `)})}async function Bt(e,t){if(t.length===0&&!Y()){let o=fe();g("usage",`db psql with no statements opens an interactive session, and ${o?me(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Ut(e);let n=await C(e);n.wakesInstantly||i("This project is paused; the connection will wake it, which takes a few seconds.");let r=Me("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",d=>{if(d.code==="ENOENT"){s(new B("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new B("failed",String(d)))}),r.on("close",d=>o(d??0))})}function Lt(e,t,n="DATABASE_URL"){let r=Gn(e,".env"),o=`${n}=${t}`;if(!It(r))return Ct(r,`${o}
33
- `),{path:r,replaced:!1};let s=Jn(r,"utf8"),d=new RegExp(`^${n}=.*$`,"m");return d.test(s)?(Ct(r,s.replace(d,o)),{path:r,replaced:!0}):(qn(r,s.endsWith(`
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(`
34
36
  `)?`${o}
35
37
  `:`
36
38
  ${o}
37
- `),{path:r,replaced:!1})}async function Ft(e,t={}){if(t.statusOnly){let{export:s}=await Z(e);return p({ref:e,export:s},()=>Ie(s)),0}let n=await re(e);if(!n.export.pending&&n.export.url&&!t.out)return p({ref:e,export:n.export},()=>Ie(n.export)),0;i("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Wn(e,t.timeoutMs);if(r.error&&g("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return p({ref:e,export:r},()=>Ie(r)),0;r.url||g("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Hn(r.url,t.out);return p({ref:e,path:t.out,bytes:o},()=>{i(`Saved ${I(o)} to ${t.out}.`),i(y("Restore it with: pg_restore --dbname <your database> "+t.out))}),0}async function Wn(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Z(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 Hn(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 Mt(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 Ie(e){if(e.error){$(`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 ${H(e.completedAt)}, ${I(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
38
- `),e.urlExpiresAt&&i(y(`That link stops working ${H(e.urlExpiresAt)}.`))):i(y("The link has expired. Run the export again to sign a new one; the copy is kept."))}function I(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 qt(e,t){let n=t.file;n||g("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),It(n)||g("usage",`no file at ${n}`,{file:n});let r=await Mt(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let d=Rt(o.slice(0,s.bytesRead)),a=await C(e);a.wakesInstantly||i("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:c}=await D(),l=c.find(M=>M.ref===e),w=await Kn(a);w===null&&g("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=Nt(d,{existingTables:w,readOnly:l?.readOnly===!0},{force:t.force});m.ok||g("conflict",m.reason,{ref:e,file:n,refusal:m.code}),i(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let k=await zn(a,m.tool,n),u=Dt(k.stderr);return k.code!==0&&u.fatal.length===0&&u.total>0?(p({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:u.harmless},()=>{i(`Restored into ${e}.`),i(`${u.harmless} error${u.harmless===1?"":"s"} above ${u.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(k.code!==0&&g("failed",`${m.tool} exited ${k.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:u.fatal}),p({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>i(`Restored into ${e}.`)),0)}async function Kn(e){return new Promise(t=>{let n=Me("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=>{$(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 zn(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 d=Me(t,o,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:"require"}}),a="";d.stderr?.on("data",c=>{let l=String(c);a+=l,process.stderr.write(l)}),d.on("error",c=>{$(c.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(c)),s({code:127,stderr:a})}),d.on("close",c=>s({code:c??0,stderr:a}))})}T();E();te();import{spawn as Xn}from"node:child_process";import{createHash as Zn}from"node:crypto";import{existsSync as Qn,readdirSync as er,readFileSync as tr,statSync as nr}from"node:fs";import{isAbsolute as rr,join as Le,relative as or,resolve as sr}from"node:path";function Vn(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Jt(e,t,n={}){let r=Vn(e),o=new Map(r.map(l=>[l.name,l])),s=[];for(let l of t){let w=o.get(l.name);if(!w){s.push({code:"missing",name:l.name,reason:`${l.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}w.checksum!==l.checksum&&s.push({code:"changed",name:l.name,reason:`${l.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 d=t.map(l=>l.name).sort((l,w)=>l<w?-1:l>w?1:0)[t.length-1],a=new Set(t.map(l=>l.name)),c=[];for(let l of r)if(!a.has(l.name)){if(d!==void 0&&l.name<d&&!n.outOfOrder){s.push({code:"out-of-order",name:l.name,reason:`${l.name} sorts before ${d}, 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}c.push(l)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:c}}function Be(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function Wt(e){let t=or(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var se="_snoutdata_migrations",ar=`create table if not exists ${se} (
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} (
39
41
  name text primary key,
40
42
  checksum text not null,
41
43
  applied_at timestamptz not null default now()
42
- );`;function ir(e){if(!Qn(e)||!nr(e).isDirectory())throw new Error(`no migrations folder at ${Wt(e)}. Put your .sql files there, or pass --dir.`);return er(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Le(e,t),r=tr(n,"utf8");return{name:t,path:n,sql:r,checksum:Zn("sha256").update(r,"utf8").digest("hex")}})}function Fe(e,t,n=[]){return new Promise(r=>{let o=Xn("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="",d="";o.stdout.on("data",a=>s+=String(a)),o.stderr.on("data",a=>d+=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:d})),o.stdin.end(t)})}async function cr(e){let t=await Fe(e,ar);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${se}`);let n=await Fe(e,`select name || E'\\t' || checksum from ${se};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${se}`);return n.out.split(`
43
- `).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 Gt(e){return`'${e.replace(/'/g,"''")}'`}async function Ht(e,t={}){let n=await qe(e,t,r=>i(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
44
- `);g("conflict",`Refused. This folder and ${e} do not agree:
45
- ${r}`,{ref:e,problems:n.problems})}g("failed",`${n.failed} failed, and nothing after it was tried.
46
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return p(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 qe(e,t={},n=()=>{}){let r=sr(t.dir===void 0?Le(process.cwd(),"migrations"):rr(t.dir)?t.dir:Le(process.cwd(),t.dir)),o=[];function s(u){o.push(u),n(u)}let d=ir(r);if(d.length===0)return s(`No .sql files in ${Wt(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let a=await C(e);a.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let c=await cr(a),l=Jt(d,c,{outOfOrder:t.outOfOrder});if(!l.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...l.problems],notes:o};let w=new Map(d.map(u=>[u.name,u])),m=l.pending.map(u=>w.get(u.name));if(m.length===0)return s(`${e} is up to date: ${c.length} migration${c.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let u of m)Be(u.sql)||s(`${u.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(u=>u.name),notes:o}}let k=[];for(let u of m){let M=`insert into ${se} (name, checksum) values (${Gt(u.name)}, ${Gt(u.checksum)});`,O=Be(u.sql);O||s(`${u.name} runs outside a transaction, so an interruption can leave it half applied.`);let K=O?`begin;
47
- ${u.sql}
48
- ${M}
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;
49
+ ${p.sql}
50
+ ${E}
49
51
  commit;
50
- `:`${u.sql}
51
- ${M}
52
- `,P=await Fe(a,K);if(P.code!==0)return O&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:k,failed:u.name,error:P.err.trim(),notes:o};k.push(u.name),s(` applied ${u.name}`)}return{ref:e,directory:r,ok:!0,applied:k,notes:o}}T();E();var lr="2024-11-05";function S(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var v={type:"string"},Ae=[{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:S({})},{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:S({})},{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:S({name:v,region:v,teamId:v},["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:S({ref:v},["ref"])},{name:"pause_project",description:"Stop a project. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:S({ref:v},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it.",inputSchema:S({ref:v},["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:S({ref:v,dir:v,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:S({ref:v,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:S({ref:v},["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:S({ref:v},["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:S({ref:v},["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:S({})},{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:S({})},{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:S({name:v,expires:v},["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:S({id:v},["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:S({ref:v},["ref"])}];function dr(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function ae(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Je(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function h(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return ae(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 Kt(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 ae(r,{protocolVersion:lr,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return ae(r,{});case"tools/list":return ae(r,{tools:dr([...Ae,...n.borrowed??[]])});case"tools/call":return ur(r,s,t,n);default:return Je(r,-32601,`unknown method: ${o}`)}}async function ur(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Ae.some(l=>l.name===o)||(r.borrowed?.some(l=>l.name===o)??!1)))return Je(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"],c=J(s,"ref");if(a.includes(o)&&!c)return h(e,`${o} needs a project ref. Call list_projects to see them.`,!0);try{switch(o){case"whoami":return h(e,await n.whoami());case"list_projects":return h(e,await n.listProjects());case"create_project":{let l=J(s,"name");return l?h(e,await n.createProject({name:l,region:J(s,"region")??void 0,teamId:J(s,"teamId")??void 0})):h(e,"create_project needs a name.",!0)}case"get_connection_url":{let l=await n.connection(c);return h(e,{url:l.uri,state:l.state,wakesInstantly:l.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 h(e,await n.setProjectState("pause",c));case"resume_project":return h(e,await n.setProjectState("resume",c));case"usage":{let l=typeof s.days=="number"?s.days:void 0;return h(e,await n.usage(c,l))}case"export_status":return h(e,await n.exportStatus(c));case"start_export":return h(e,await n.requestExport(c));case"reset_password":return h(e,await n.resetPassword(c));case"list_teams":return h(e,await n.listTeams());case"list_tokens":return h(e,await n.listTokens());case"create_token":{let l=J(s,"name");return l?h(e,await n.createToken(l,J(s,"expires")??void 0)):h(e,"create_token needs a name, so somebody can tell later what it was for.",!0)}case"revoke_token":{let l=J(s,"id");return l?h(e,await n.revokeToken(l)):h(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return h(e,await n.pushMigrations(c,{dir:J(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?h(e,await n.setProjectState("delete",c)):h(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(l=>l.name===o)?n.callBorrowed?ae(e,await n.callBorrowed(o,s)):h(e,`${o} needs the SnoutData app, which is no longer answering.`,!0):Je(e,-32602,`unknown tool: ${o}`)}}catch(l){return h(e,l instanceof Error?l.message:String(l),!0)}}function zt(e){let t=e.split(`
53
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}we();function pr(){return{whoami:()=>X(),listProjects:()=>D(),createProject:async e=>{let t=await f("cloud-project-create",{name:e.name,...e.region?{region:e.region}:{},...e.teamId?{teamId:e.teamId}:{}});try{return await oe(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)=>f("cloud-project-usage",{ref:e,...t?{days:t}:{}}),exportStatus:e=>Z(e),requestExport:e=>re(e),resetPassword:e=>f("cloud-project-reset-password",{ref:e}),listTeams:()=>f("cloud-team-list",{}),listTokens:()=>f("cloud-token-list",{}),createToken:(e,t)=>f("cloud-token-create",{name:e,...t?{expires:t}:{}}),revokeToken:e=>f("cloud-token-revoke",{id:e}),setProjectState:(e,t)=>f(`cloud-project-${e}`,{ref:t}),connection:e=>C(e),pushMigrations:(e,t)=>qe(e,{dir:t.dir,dryRun:t.dryRun})}}var Yt="app_";async function mr(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await ge();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await at(t)).map(o=>({name:`${Yt}${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 Vt(e){ne(!0);let{tools:t,config:n}=await mr(),r=pr();n&&(r.callBorrowed=(c,l)=>it(n,c.slice(Yt.length),l));let o={...e,borrowed:t};$(t.length>0?`snoutdata mcp: ${Ae.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 c=await X();$(`snoutdata mcp: signed in as ${c.email??"this account"}.`)}catch(c){return $(`snoutdata mcp: ${c instanceof Error?c.message:String(c)}`),$("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3}$(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let d=new Set;function a(c){d.add(c),c.finally(()=>d.delete(c))}return new Promise(c=>{process.stdin.on("data",l=>{let w=zt(s+l);s=w.rest;for(let m of w.messages)a(fr(m,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;d.size>0;)await Promise.allSettled([...d]);c(0)})()}),process.stdin.on("error",()=>c(1))})}async function fr(e,t,n){let r;try{r=JSON.parse(e)}catch{$(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Kt(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
52
+ `:`${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(`
55
+ `)}
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">
58
+
59
+ type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${v(e)}>]
60
+
61
+ export type Tables<
62
+ DefaultSchemaTableNameOrOptions extends
63
+ | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
64
+ | { schema: keyof DatabaseWithoutInternals },
65
+ TableName extends DefaultSchemaTableNameOrOptions extends {
66
+ schema: keyof DatabaseWithoutInternals
67
+ }
68
+ ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
69
+ DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
70
+ : never = never,
71
+ > = DefaultSchemaTableNameOrOptions extends {
72
+ schema: keyof DatabaseWithoutInternals
73
+ }
74
+ ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
75
+ DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
76
+ Row: infer R
77
+ }
78
+ ? R
79
+ : never
80
+ : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
81
+ DefaultSchema["Views"])
82
+ ? (DefaultSchema["Tables"] &
83
+ DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
84
+ Row: infer R
85
+ }
86
+ ? R
87
+ : never
88
+ : never
89
+
90
+ export type TablesInsert<
91
+ DefaultSchemaTableNameOrOptions extends
92
+ | keyof DefaultSchema["Tables"]
93
+ | { schema: keyof DatabaseWithoutInternals },
94
+ TableName extends DefaultSchemaTableNameOrOptions extends {
95
+ schema: keyof DatabaseWithoutInternals
96
+ }
97
+ ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
98
+ : never = never,
99
+ > = DefaultSchemaTableNameOrOptions extends {
100
+ schema: keyof DatabaseWithoutInternals
101
+ }
102
+ ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
103
+ Insert: infer I
104
+ }
105
+ ? I
106
+ : never
107
+ : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
108
+ ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
109
+ Insert: infer I
110
+ }
111
+ ? I
112
+ : never
113
+ : never
114
+
115
+ export type TablesUpdate<
116
+ DefaultSchemaTableNameOrOptions extends
117
+ | keyof DefaultSchema["Tables"]
118
+ | { schema: keyof DatabaseWithoutInternals },
119
+ TableName extends DefaultSchemaTableNameOrOptions extends {
120
+ schema: keyof DatabaseWithoutInternals
121
+ }
122
+ ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
123
+ : never = never,
124
+ > = DefaultSchemaTableNameOrOptions extends {
125
+ schema: keyof DatabaseWithoutInternals
126
+ }
127
+ ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
128
+ Update: infer U
129
+ }
130
+ ? U
131
+ : never
132
+ : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
133
+ ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
134
+ Update: infer U
135
+ }
136
+ ? U
137
+ : never
138
+ : never
139
+
140
+ export type Enums<
141
+ DefaultSchemaEnumNameOrOptions extends
142
+ | keyof DefaultSchema["Enums"]
143
+ | { schema: keyof DatabaseWithoutInternals },
144
+ EnumName extends DefaultSchemaEnumNameOrOptions extends {
145
+ schema: keyof DatabaseWithoutInternals
146
+ }
147
+ ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
148
+ : never = never,
149
+ > = DefaultSchemaEnumNameOrOptions extends {
150
+ schema: keyof DatabaseWithoutInternals
151
+ }
152
+ ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
153
+ : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
154
+ ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
155
+ : never
156
+
157
+ export type CompositeTypes<
158
+ PublicCompositeTypeNameOrOptions extends
159
+ | keyof DefaultSchema["CompositeTypes"]
160
+ | { schema: keyof DatabaseWithoutInternals },
161
+ CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
162
+ schema: keyof DatabaseWithoutInternals
163
+ }
164
+ ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
165
+ : never = never,
166
+ > = PublicCompositeTypeNameOrOptions extends {
167
+ schema: keyof DatabaseWithoutInternals
168
+ }
169
+ ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
170
+ : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
171
+ ? 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=`
173
+ with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
174
+ select json_build_object(
175
+ 'tables', coalesce((
176
+ select json_agg(json_build_object(
177
+ 'name', c.relname,
178
+ 'schema', n.nspname,
179
+ 'kind', case when c.relkind in ('v', 'm') then 'view' else 'table' end,
180
+ 'columns', coalesce((
181
+ select json_agg(json_build_object(
182
+ 'name', a.attname,
183
+ 'dataType', format_type(a.atttypid, a.atttypmod),
184
+ 'nullable', not a.attnotnull,
185
+ 'isPrimaryKey', false,
186
+ 'default', pg_get_expr(d.adbin, d.adrelid),
187
+ 'autoIncrement', a.attidentity <> '' or coalesce(pg_get_expr(d.adbin, d.adrelid), '') like 'nextval(%'
188
+ ) order by a.attnum)
189
+ from pg_attribute a
190
+ left join pg_attrdef d on d.adrelid = a.attrelid and d.adnum = a.attnum
191
+ where a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
192
+ ), '[]'::json),
193
+ 'primaryKey', coalesce((
194
+ select json_agg(att.attname order by k.ord)
195
+ from pg_constraint con
196
+ cross join lateral unnest(con.conkey) with ordinality as k(attnum, ord)
197
+ join pg_attribute att on att.attrelid = con.conrelid and att.attnum = k.attnum
198
+ where con.conrelid = c.oid and con.contype = 'p'
199
+ ), '[]'::json),
200
+ 'foreignKeys', coalesce((
201
+ select json_agg(json_build_object(
202
+ 'constraintName', con.conname,
203
+ 'columns', (
204
+ select json_agg(att.attname order by k.ord)
205
+ from unnest(con.conkey) with ordinality as k(attnum, ord)
206
+ join pg_attribute att on att.attrelid = con.conrelid and att.attnum = k.attnum
207
+ ),
208
+ 'referencedTable', rc.relname,
209
+ 'referencedColumns', (
210
+ select json_agg(att.attname order by k.ord)
211
+ from unnest(con.confkey) with ordinality as k(attnum, ord)
212
+ join pg_attribute att on att.attrelid = con.confrelid and att.attnum = k.attnum
213
+ )
214
+ ) order by con.conname)
215
+ from pg_constraint con
216
+ join pg_class rc on rc.oid = con.confrelid
217
+ where con.conrelid = c.oid and con.contype = 'f'
218
+ ), '[]'::json),
219
+ 'indexes', coalesce((
220
+ select json_agg(json_build_object(
221
+ 'name', ic.relname,
222
+ 'unique', i.indisunique,
223
+ 'columns', (
224
+ select json_agg(att.attname order by k.ord)
225
+ from unnest(i.indkey::int2[]) with ordinality as k(attnum, ord)
226
+ join pg_attribute att on att.attrelid = i.indrelid and att.attnum = k.attnum
227
+ )
228
+ ) order by ic.relname)
229
+ from pg_index i
230
+ join pg_class ic on ic.oid = i.indexrelid
231
+ where i.indrelid = c.oid and not i.indisprimary
232
+ ), '[]'::json)
233
+ ) order by n.nspname, c.relname)
234
+ from pg_class c
235
+ join pg_namespace n on n.oid = c.relnamespace
236
+ where c.relkind in ('r', 'p', 'v', 'm')
237
+ and n.nspname in (select nspname from wanted)
238
+ ), '[]'::json),
239
+ 'routines', coalesce((
240
+ select json_agg(json_build_object(
241
+ 'name', p.proname,
242
+ 'schema', n.nspname,
243
+ 'kind', case p.prokind when 'p' then 'procedure' else 'function' end,
244
+ 'returnType', pg_get_function_result(p.oid),
245
+ 'parameters', coalesce((
246
+ select json_agg(json_build_object(
247
+ 'name', coalesce(p.proargnames[a.ord], ''),
248
+ 'dataType', format_type(a.t, null),
249
+ 'mode', case coalesce(p.proargmodes[a.ord], 'i')
250
+ when 'o' then 'OUT' when 'b' then 'INOUT' when 'v' then 'VARIADIC' when 't' then 'OUT' else 'IN' end
251
+ ) order by a.ord)
252
+ from unnest(coalesce(p.proallargtypes, p.proargtypes::oid[])) with ordinality as a(t, ord)
253
+ ), '[]'::json)
254
+ ) order by n.nspname, p.proname)
255
+ from pg_proc p
256
+ join pg_namespace n on n.oid = p.pronamespace
257
+ where p.prokind in ('f', 'p')
258
+ and n.nspname in (select nspname from wanted)
259
+ and not exists (
260
+ select 1 from pg_depend dep
261
+ where dep.objid = p.oid and dep.classid = 'pg_proc'::regclass and dep.deptype = 'e'
262
+ )
263
+ ), '[]'::json),
264
+ 'enums', coalesce((
265
+ select json_agg(json_build_object(
266
+ 'name', t.typname,
267
+ 'schema', n.nspname,
268
+ 'values', (select json_agg(e.enumlabel order by e.enumsortorder) from pg_enum e where e.enumtypid = t.oid)
269
+ ) order by n.nspname, t.typname)
270
+ from pg_type t
271
+ join pg_namespace n on n.oid = t.typnamespace
272
+ where t.typtype = 'e' and n.nspname in (select nspname from wanted)
273
+ ), '[]'::json)
274
+ )::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}
282
+ 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} (
288
+ name text primary key,
289
+ checksum text not null,
290
+ 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)}
54
295
  `)}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)}})}
55
- `)}}T();E();function gr(e){return e>=1?"over":e>=.8?"near":"ok"}function Xt(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 d=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:gr(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:d}}function Ge(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 Zt(e,t={}){let n=await f("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Xt(n.days,n.limit);return p({...n,summary:r},()=>{if(i(A(`${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=`${I(r.storage.usedBytes)} of ${I(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(`${I(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"}: ${Ge(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&i(y(`Peak in that window was ${I(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(i(""),i(W([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":I(o.dbBytes),o.repoBytes===null?"-":I(o.repoBytes),Ge(o.computeSeconds),String(o.connections)])])),i(y("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}var ie="0.1.0",Qt=[{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:"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:"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 ${ie} \u2014 hosted Postgres, from a terminal or an agent
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
56
297
 
57
298
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use
58
299
  snoutdata login [--provider github] sign in through a browser
@@ -72,6 +313,16 @@ ${M}
72
313
 
73
314
  snoutdata usage [--ref R] [--days 30] [--history] size, compute, and the plan's limit
74
315
 
316
+ snoutdata start [--port 54322] [--dir migrations] a Postgres here, migrated and seeded
317
+ snoutdata stop stop it; the data stays
318
+ snoutdata status is it running, how big, on what port
319
+
320
+ snoutdata gen types typescript [--local] [--schema public] [--out FILE]
321
+ the schema as a TypeScript Database type
322
+
323
+ snoutdata keys [--ref R] the anon and service_role API keys
324
+ snoutdata keys rotate --force [--ref R] new ones, breaking every key already issued
325
+
75
326
  snoutdata teams teams you can share a project with
76
327
  snoutdata db url [--ref R] print a connection string
77
328
  snoutdata db psql [--ref R] [-- ...] open psql, with no password typed
@@ -98,10 +349,10 @@ A project comes from --ref, then SNOUTDATA_PROJECT, then
98
349
  .snoutdata/project.json in this folder or a parent. A token comes from
99
350
  SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes lasts
100
351
  an hour, so CI wants "tokens create", which does not expire.
101
- `;async function hr(e){let[t,n,...r]=e.command;if(U(e,"version")||t==="version")return p({version:ie},()=>process.stdout.write(`${ie}
102
- `)),0;if(U(e,"help")&&t&&t!=="help"){let a=Qt.filter(c=>c.name===t||c.name.startsWith(`${t} `));if(a.length>0)return p({commands:a},()=>{for(let c of a){let l=c.flags.length>0?` ${c.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${c.name}${l}
103
- ${c.summary}
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}
104
355
  `)}process.stdout.write(`
105
356
  Every command also takes --json, --quiet and --help.
106
- `)}),0}let o=U(e,"help")||t==="help";if(o||!t)return p({version:ie,usage:en.trim(),commands:Qt},()=>process.stdout.write(en)),o?0:L.usage;let s=()=>{let a=ce(e,"timeout");if(a!==void 0){if(!(a>0))throw new b("--timeout is a number of seconds greater than zero");return a*1e3}},d=()=>{let a=ue({flag:x(e,"ref")});if(!a)throw new b("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return a};switch(t){case"login":{if(U(e,"device")){let c=await ye({name:he(),platform:process.platform});p({verifyUrl:c.verifyUrl,code:c.code,expiresAt:c.expiresAt},()=>{i(""),i(` Open ${c.verifyUrl}`),i(` Type ${c.code}`),i(""),i(y("Waiting for that to be approved\u2026"))});let l=await ke(c.secret,{intervalMs:Math.max(1,c.interval)*1e3,expiresAt:c.expiresAt});return j({accessToken:l.token}),p({ok:!0,name:l.name??null},()=>i("Signed in.")),0}let a=await ve({provider:x(e,"provider"),noBrowser:U(e,"no-browser")||!Y(),timeoutMs:s()});return p({email:a.email??null},()=>i(`Signed in${a.email?` as ${a.email}`:""}.`)),0}case"logout":{Qe();let a=!!process.env.SNOUTDATA_ACCESS_TOKEN;return p({ok:!0,environmentTokenStillSet:a},()=>{i("Signed out."),a&&(i("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),i(y(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let a=await X();return p(a,()=>{process.stdout.write(`${a.email??a.id}
107
- `),a.token&&i(`via access token ${a.token.prefix} (${a.token.name}).`)}),0}case"tokens":switch(n){case void 0:case"list":return await _t(),0;case"create":{let a=x(e,"name")??r[0];if(!a)throw new b("tokens create needs --name (what is it for, and where does it live)");return await Ot({name:a,expiresInDays:ce(e,"expires")}),0}case"revoke":{let a=r[0];if(!a)throw new b("tokens revoke needs the token's id or its sdt_ prefix");return await jt(a),0}default:throw new b(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Tt(),0;case"create":{let a=x(e,"name")??r[0];if(!a)throw new b("projects create needs --name");let c=x(e,"team");return await Ce({teamId:c?await Et(c):void 0,name:a,region:x(e,"region"),wait:!U(e,"no-wait"),timeoutMs:s(),link:void 0}),0}case"pause":case"resume":case"delete":return await At(n,x(e,"ref")??r[0]??d()),0;default:throw new b(`unknown command: projects ${n}`)}case"teams":return await Pt(),0;case"link":{let a=x(e,"ref")??n;if(!a)throw new b("link needs --ref");let c=de(process.cwd(),{ref:a});return p({ref:a,path:c},()=>i(`Linked ${a} (${c}).`)),0}case"db":switch(n){case"url":return await Ue(d()),0;case"psql":return Bt(d(),r);case"reset-password":return await $t(d()),0;case"push":return Ht(d(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return qt(d(),{file:x(e,"file"),force:!!e.flags.force});case"export":return Ft(d(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new b(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, export, push, restore")}case"usage":return Zt(d(),{days:ce(e,"days"),history:!!e.flags.history});case"mcp":return Vt({version:ie,allowDelete:!!e.flags["allow-delete"]});case"init":return wr(e);default:throw new b(`unknown command: ${t}`)}}async function wr(e){let t=ue({flag:x(e,"ref")});if(t)return i(`This folder is already linked to ${t}.`),await Ue(t),0;let n=x(e,"name")??yr(process.cwd());i(`Creating a project called "${n}"\u2026`),await Ce({name:n,region:x(e,"region"),wait:!0,link:process.cwd()});let r=ue({cwd:process.cwd()});if(r&&U(e,"env")){let{connection:o}=await Promise.resolve().then(()=>(T(),Q)),s=await o(r),d=Lt(process.cwd(),s.uri);i(`${d.replaced?"Updated":"Wrote"} DATABASE_URL in ${d.path}.`)}return 0}function yr(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function kr(e){if(e instanceof B)return G(e.code,e.message,e.details),e.exitCode;if(e instanceof b)return G("usage",e.message),L.usage;if(e instanceof q)return G("not-signed-in",e.message),L["not-signed-in"];if(e instanceof N){let n=lt(e.status);return G(n,e.message,{status:e.status}),L[n]}let t=dt(e);return G(t,e instanceof Error?e.message:String(e)),L[t]}function We(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function br(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function xr(){ne(br(process.argv.slice(2)));let e;try{e=He(process.argv.slice(2))}catch(t){G("usage",String(t instanceof Error?t.message:t)),We(L.usage);return}ne(e.json),mt(e.flags.quiet===!0),nt(pe({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{We(await hr(e))}catch(t){We(kr(t))}}xr();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snoutdata",
3
- "version": "0.1.1",
3
+ "version": "0.2.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",