palbase 0.33.0 → 0.34.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 (48) hide show
  1. package/README.md +2 -2
  2. package/dist/ai/index.js +2 -2
  3. package/dist/backend/engine.js +2 -2
  4. package/dist/backend/env.js +1 -1
  5. package/dist/backend/index.js +3 -3
  6. package/dist/backend/stack.js +1 -1
  7. package/dist/backend/test.js +2 -2
  8. package/dist/chunks/{HKUBQA4K.js → 23NFADPH.js} +1 -1
  9. package/dist/chunks/{D4P4XLMT.js → 2KUOCM4W.js} +1 -1
  10. package/dist/chunks/{PCOLUMJ2.js → 3WO4MSQK.js} +2 -2
  11. package/dist/chunks/{AKE3ARBQ.js → 56CR6XQX.js} +1 -1
  12. package/dist/chunks/{S4UAMADF.js → 7KAQJOKQ.js} +2 -2
  13. package/dist/chunks/{NAHDBHAC.js → AAJTXVUF.js} +2 -2
  14. package/dist/chunks/{6D5QSXFP.js → BORV4XQE.js} +2 -2
  15. package/dist/chunks/{L5JYBOI3.js → CVZYB52V.js} +2 -2
  16. package/dist/chunks/{GVR2PSNL.js → DP7HCIWR.js} +2 -2
  17. package/dist/chunks/{24U4NDH6.js → FFLUB3R2.js} +2 -2
  18. package/dist/chunks/{KMWZK74K.js → HJTZ5RUV.js} +2 -2
  19. package/dist/chunks/{ECVJH7GC.js → I6FOJBJG.js} +2 -2
  20. package/dist/chunks/{7MEZ7I4T.js → JNARDSKO.js} +2 -2
  21. package/dist/chunks/{OBLZE7OG.js → QLGKS7OC.js} +2 -2
  22. package/dist/chunks/{7KKXGZP6.js → SVEMN34N.js} +1 -1
  23. package/dist/chunks/{5U6L3QNV.js → TGGBNVFF.js} +1 -1
  24. package/dist/chunks/{2TNGTQX6.js → TO7DBGJO.js} +2 -2
  25. package/dist/chunks/{W5YFK4GY.js → TSZBPILF.js} +1 -1
  26. package/dist/chunks/{BJ4U62LX.js → TXPV2FZL.js} +2 -2
  27. package/dist/chunks/{STM6MJLC.js → U2OSMAAB.js} +1 -1
  28. package/dist/chunks/{WRGRQURQ.js → VYJZI4ZF.js} +2 -2
  29. package/dist/chunks/{WBYKUJPE.js → WFXZMNDL.js} +2 -2
  30. package/dist/chunks/{HLESDZU4.js → ZXOT7B45.js} +2 -2
  31. package/dist/cli.js +3 -3
  32. package/dist/cloud/index.js +2 -2
  33. package/dist/refuse/ai.js +1 -1
  34. package/dist/refuse/backend.js +1 -1
  35. package/dist/refuse/cloud.js +1 -1
  36. package/dist/refuse/server.js +1 -1
  37. package/dist/server/index.js +2 -2
  38. package/dist/server/testing.js +2 -2
  39. package/dist/web/calls-ui.js +2 -2
  40. package/dist/web/index.js +2 -2
  41. package/dist/web/message.js +2 -2
  42. package/dist/web/palbe.js +2 -2
  43. package/dist/web/paywall.js +2 -2
  44. package/dist/web/push-sw.js +1 -1
  45. package/dist/web/ssr.js +2 -2
  46. package/dist/web/vite.js +2 -2
  47. package/package.json +2 -2
  48. package/types/internal/src/modules/machines.d.ts +25 -9
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! palbase 0.33.0 — Palbase SDK License (see LICENSE) */
2
+ /*! palbase 0.34.0 — Palbase SDK License (see LICENSE) */
3
3
  var Lb=Object.defineProperty;var We=(e,t)=>{for(var n in t)Lb(e,n,{get:t[n],enumerable:!0})};import{parseArgs as ww}from"node:util";var Db=Symbol.for("palbase.data"),Nb=globalThis,ep=Nb[Db]??=new WeakSet;function qb(e){return e instanceof Date||ArrayBuffer.isView(e)||e instanceof ArrayBuffer}function Ca(e){let t=[e];for(;t.length>0;){let n=t.pop();if(!(typeof n!="object"||n===null||ep.has(n)||qb(n))){ep.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var Kb="X-PoW-Challenge-ID",Gb="X-PoW-Nonce";function tp(e){if(typeof e!="object"||e===null)return null;let t=e;if(t.error!=="pow_required"||typeof t.challenge!="object"||t.challenge===null)return null;let{id:n,prefix:s,difficulty:a}=t.challenge;return typeof n!="string"||typeof s!="string"||typeof a!="number"||!Number.isInteger(a)||a<0?null:{id:n,prefix:s,difficulty:a}}function Ub(e){return 8*2**e}var Wn;async function zb(){if(Wn)return Wn;let e=globalThis;if(e.process?.versions?.node!==void 0||e.process?.versions?.bun!==void 0)try{let s=(await import("node:crypto")).createHash;if(typeof s=="function")return Wn=a=>new Uint8Array(s("sha256").update(a).digest()),Wn}catch{}let t=new TextEncoder;return Wn=async n=>new Uint8Array(await crypto.subtle.digest("SHA-256",t.encode(n))),Wn}function Bb(e){let t=0;for(let n of e){if(n===0){t+=8;continue}return t+Math.clz32(n)-24}return t}var Fb=4096,Jb=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));async function np(e,t={}){if(e.difficulty>24)throw new Error(`proof-of-work: difficulty ${e.difficulty} is above auth's ceiling of 24`);let n=await zb(),s=Ub(e.difficulty),a=Date.now()+(t.timeLimitMs??12e4);for(let r=0;r<s;r++){if(r%Fb===0&&r>0){if(await Jb(),t.signal?.aborted)throw t.signal.reason??new DOMException("proof-of-work aborted","AbortError");if(Date.now()>a)throw new Error(`proof-of-work: gave up on difficulty ${e.difficulty} after ${r} attempts`)}let i=n(e.prefix+r),o=i instanceof Uint8Array?i:await i;if(Bb(o)>=e.difficulty)return{[Kb]:e.id,[Gb]:String(r)}}throw new Error(`proof-of-work: no nonce for difficulty ${e.difficulty} within ${s} attempts`)}var j=class extends Error{status;code;requestId;retryAfter;body;op;get isNetworkError(){return this.code==="network_error"}constructor(t){super(t.message,t.cause===void 0?void 0:{cause:t.cause}),this.name="PalbaseError",this.op=t.op,this.status=t.status,this.code=t.code,this.requestId=t.requestId,this.retryAfter=t.retryAfter,this.body=t.body}};function c(e,t,n){let s=String(n);if(s===""||s==="."||s==="..")throw new j({op:e,status:0,code:"invalid_path_parameter",message:`${e}: ${t} cannot be ${s===""?"empty":JSON.stringify(s)}`});return encodeURIComponent(s)}var Wb=new Set(["apikey","authorization"]),Vb=64*1024;function bi(e){let t=ja(e.baseUrl),n=e.fetch??globalThis.fetch,s=e.solveProofOfWork??!0,a=async(r,i)=>{let o=Xb(t,r),l=new Headers;for(let[b,w]of Object.entries(r.headers??{}))w!=null&&!Wb.has(b.toLowerCase())&&l.set(b,String(w));for(let[b,w]of Object.entries(i))l.set(b,w);await Zb(r,e.credentials,l),l.has("accept")||l.set("accept","application/json");let d={method:r.method,headers:l,redirect:"manual",signal:r.signal,...r.keepalive?{keepalive:!0}:{}};r.body&&r.body.value!==void 0&&(l.set("content-type",r.body.contentType),d.body=Qb(r.body.kind,r.body.value),Vn(d.body)||(d.duplex="half"));let p;try{p=r.onUploadProgress&&e.xmlHttpRequest?await Hb(e.xmlHttpRequest,r.op,o,d,r.onUploadProgress):await n(o,d)}catch(b){throw r.signal?.aborted||b instanceof j?b:new j({op:r.op,status:0,code:"network_error",message:`${r.op}: the request did not reach ${t.origin}`,cause:b})}let u=r.answers[p.status]??(r.ownResponse&&p.status>=200&&p.status<300?"raw":void 0);if(u===void 0)throw p.status>=200&&p.status<300?(await p.body?.cancel(),new j({op:r.op,status:p.status,code:"undeclared_answer",message:`${r.op}: the server answered ${p.status}, which its contract does not declare; the request may have taken effect`})):await tw(r.op,p);let h;switch(u){case"json":try{h=Ca(await p.json())}catch(b){throw new j({op:r.op,status:p.status,code:"invalid_json",message:`${r.op}: the answer is not JSON`,cause:b})}break;case"raw":h=p;break;default:await p.body?.cancel(),h=void 0}return{status:p.status,data:h,headers:p.headers}};return{async send(r){try{return await a(r,{})}catch(i){let o=s&&i instanceof j&&i.status===403?tp(i.body):null,l=r.body?.value;if(!o||r.body?.kind==="raw"&&!Vn(l))throw i;let d=i,p;try{p=await np(o,{signal:r.signal})}catch(u){throw r.signal?.aborted?u:new j({op:r.op,status:403,code:"pow_unsolved",message:`${r.op}: ${u instanceof Error?u.message:String(u)}`,requestId:d.requestId,body:d.body,cause:u})}return a(r,p)}}}}function Hb(e,t,n,s,a){return new Promise((r,i)=>{let o=s.signal;if(o?.aborted)return i(o.reason);let l=new e,d=()=>l.abort();o?.addEventListener("abort",d,{once:!0});let p=u=>{o?.removeEventListener("abort",d),u()};l.open(s.method??"GET",n.href),l.responseType="arraybuffer",new Headers(s.headers).forEach((u,h)=>l.setRequestHeader(h,u)),l.upload&&(l.upload.onprogress=u=>{u.lengthComputable&&a(u.loaded,u.total)}),l.onload=()=>p(()=>{if(l.responseURL!==""&&l.responseURL!==n.href){i(new j({op:t,status:0,code:"undeclared_redirect",message:`${t}: the server redirected; the answer is another address's, and was not read`}));return}if(l.status<200||l.status>599)return i(new TypeError(`${t}: answered ${l.status}`));let u=l.status===204||l.status===205||l.status===304;r(new Response(u?null:l.response,{status:l.status,statusText:l.statusText,headers:Yb(l.getAllResponseHeaders())}))}),l.onerror=()=>p(()=>i(new TypeError(`${t}: the request failed`))),l.onabort=()=>p(()=>i(o?.reason??new DOMException("The request was aborted","AbortError"))),l.send(s.body)})}function Yb(e){let t=new Headers;for(let n of e.split(/\r?\n/)){let s=n.indexOf(":");if(!(s<=0))try{t.append(n.slice(0,s).trim(),n.slice(s+1).trim())}catch{}}return t}function ja(e){let t;try{t=new URL(e)}catch{throw new TypeError(`baseUrl ${JSON.stringify(e)} is not an absolute URL`)}let n=t.hostname==="localhost"||t.hostname.endsWith(".localhost")||t.hostname==="127.0.0.1"||t.hostname==="[::1]";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw new TypeError(`baseUrl must be https (http only for a loopback address): ${t.origin}`);if(t.username||t.password||t.search||t.hash)throw new TypeError("baseUrl carries no user, query or fragment");if(t.pathname!=="/"&&t.pathname!=="")throw new TypeError("baseUrl is the project origin alone, with no path");return t}function Xb(e,t){if(!t.path.startsWith("/")||t.path.startsWith("//"))throw new j({op:t.op,status:0,code:"invalid_path",message:`${t.op}: path ${JSON.stringify(t.path)} is not absolute`});let n=new URL(t.path+ew(t.query),e);if(n.origin!==e.origin)throw new j({op:t.op,status:0,code:"invalid_path",message:`${t.op}: path leaves the project's origin`});return n}async function Zb(e,t,n){if(e.auth.length===0)return;let s=e.auth.some(i=>i.includes("userJWT"))?await t.accessToken?.():void 0,a=i=>{switch(i){case"ApiKeyAuth":return!!(t.publishableKey??t.secretKey);case"SecretKeyAuth":case"AdminAuth":return!!(t.secretKey??t.token);case"PalbaseBearer":return!!t.token;case"userJWT":return!!s&&!t.token;default:return!1}},r=e.auth.filter(i=>i.every(a)).reduce((i,o)=>i===void 0||o.length>i.length?o:i,void 0);if(!r)throw new j({op:e.op,status:0,code:"missing_credential",message:`${e.op} needs one of: ${e.auth.map(i=>i.length===0?"nothing":i.join(" + ")).join(" | ")}`});for(let i of r)i==="ApiKeyAuth"?n.set("apikey",t.publishableKey??t.secretKey):(i==="SecretKeyAuth"||i==="AdminAuth"||i==="PalbaseBearer")&&t.token?n.set("authorization",`Bearer ${t.token}`):i==="SecretKeyAuth"||i==="AdminAuth"?n.set("apikey",t.secretKey):i==="userJWT"&&n.set("authorization",`Bearer ${s}`)}function Qb(e,t){switch(e){case"json":return JSON.stringify(t);case"form":{let n=new URLSearchParams;for(let[s,a]of Object.entries(t))wi(n,s,a);return n}case"raw":return t}}function Vn(e){return!!(e==null||typeof e=="string"||e instanceof URLSearchParams||e instanceof Blob||e instanceof FormData||e instanceof ArrayBuffer||ArrayBuffer.isView(e))}function ew(e){if(!e)return"";let t=new URLSearchParams;for(let[s,a]of Object.entries(e))wi(t,s,a);let n=t.toString();return n===""?"":`?${n}`}function wi(e,t,n){if(n!=null){if(Array.isArray(n)){for(let s of n)wi(e,t,s);return}if(n instanceof Date){e.append(t,n.toISOString());return}e.append(t,typeof n=="object"?JSON.stringify(n):String(n))}}async function tw(e,t){let n=await nw(t,Vb).catch(()=>""),s;try{s=n===""?void 0:JSON.parse(n)}catch{s=n}let a=sw(s)?s:void 0,r=Number(t.headers.get("retry-after")),i=t.status>=300&&t.status<400;return new j({op:e,status:t.status,code:a?.error??(i?"undeclared_redirect":`http_${t.status}`),message:a?.error_description??(i?`${e}: the server redirected (${t.status}); the request was not followed, its credentials stay here`:`${e}: ${t.status} ${t.statusText}`.trim()),requestId:a?.request_id??t.headers.get("x-request-id")??void 0,retryAfter:a?.retry_after??(Number.isFinite(r)&&r>0?r:void 0),body:s})}async function nw(e,t){if(!e.body)return"";let n=e.body.getReader(),s=[],a=0;for(;a<t;){let{done:o,value:l}=await n.read();if(o)break;s.push(l),a+=l.byteLength}await n.cancel().catch(()=>{});let r=new Uint8Array(Math.min(a,t)),i=0;for(let o of s){let l=Math.min(o.byteLength,r.byteLength-i);if(r.set(o.subarray(0,l),i),i+=l,i>=r.byteLength)break}return new TextDecoder().decode(r)}function sw(e){return typeof e=="object"&&e!==null&&typeof e.error=="string"}var Oa={project:{type:"string",short:"P",value:"project",description:"the project (name or id; org/project when the name is in two organizations)"},env:{type:"string",short:"E",value:"env",description:"the project's environment (slug or ref)"},json:{type:"boolean",description:"print the answer as JSON"},"non-interactive":{type:"boolean",description:"never ask: refuse what would need a question"},yes:{type:"boolean",short:"y",description:"confirm a change to a production environment without asking"},debug:{type:"boolean",description:"print each request the command sends"},help:{type:"boolean",short:"h",description:"show help"}};var y=class extends Error{exitCode;hint;code;reported;constructor(t,n,s={}){super(n),this.name="CliError",this.code=t,this.hint=s.hint,this.exitCode=s.exitCode??1,this.reported=s.reported??!1}};function m(e,t){return new y("usage",e,{hint:t,exitCode:2})}var Ma=class{io;json;constructor(t,n){this.io=t,this.json=n}result(t,n){this.json?this.io.stdout(`${JSON.stringify(t,null,2)}
4
4
  `):n()}line(t=""){this.io.stdout(`${K(t)}
5
5
  `)}note(t){this.io.stderr(`${K(t)}
@@ -9,7 +9,7 @@ var Lb=Object.defineProperty;var We=(e,t)=>{for(var n in t)Lb(e,n,{get:t[n],enum
9
9
  `);let i;try{i=await this.io.fetch(t,{method:n.method,headers:{...n.headers,...a},body:n.body,signal:n.signal,...s?{}:{duplex:"half"},redirect:"manual"})}catch(l){throw n.signal?.aborted?l:new y("network_error",`${new URL(t).origin} could not be reached: ${l instanceof Error?l.message:String(l)}`)}if(this.debug&&this.io.stderr(`< ${i.status} ${i.headers.get("x-request-id")??""}
10
10
  `),i.status!==503||!s||r>=fw||!mw.has(await ki(i)??""))return i;await i.body?.cancel();let o=Number(i.headers.get("retry-after")??"");await this.io.sleep((Number.isFinite(o)&&o>0?Math.min(o,30):1)*1e3)}}async exchange(t,n,s){let a=await this.token(),r=await this.send(t,n,s,a.value);if(r.status===401&&a.renewable){let i=await this.renew(await this.currentSession());r=await this.send(t,n,s,i.access_token)}return{res:r,renewable:a.renewable}}async answered(t,n,s,a){if(s.status===204)return;let r=await s.text(),i=r===""?void 0:yw(r);if(s.ok)return i;let o=i??{};throw s.status===401?o.error==="token_not_for_machines"?bp(new URL(this.cloud.studioApi).host):a?Hn():this.tokenRefused("Studio"):new j({op:`${t} ${n}`,status:s.status,code:o.error??`http_${s.status}`,message:o.error_description??`Studio answered ${s.status}`,requestId:o.request_id??s.headers.get("x-request-id")??void 0,body:i})}tokenRefused(t){return(this.io.env.PALBASE_TOKEN??"").startsWith("pbe_")?new y("token_refused",`${t} refused PALBASE_TOKEN, an environment's token: it ended (a few hours at most), it was revoked, or what minted it no longer holds`,{hint:"mint another: palbase env:tokens:create --scope <scope> (an admin), or with the environment's secret key (Studio's POST /environments/{ref}/tokens)"}):(this.io.env.PALBASE_TOKEN??"").startsWith("pbu_")?new y("token_refused",`${t} refused PALBASE_TOKEN, an account token: it was revoked, it ended, or, in this organization, the one who made it left it`,{hint:"its maker makes a new one: palbase account:tokens:create <name>"}):new y("token_refused",`${t} refused PALBASE_TOKEN: it was revoked, it ended, or the one who made it no longer holds its role`,{hint:"an admin makes a new one: palbase tokens:create <name>"})}async token(){let t=this.io.env.PALBASE_TOKEN;return t&&t!==""?{value:t,renewable:!1}:{value:(await this.currentSession()).access_token,renewable:!0}}async renew(t){let n=await hp(this.io,this.cloud,t);return await this.state.writeSession(n),this.session=n,n}async send(t,n,s,a){let r=`${this.cloud.studioApi}${hw}${n}`;this.debug&&this.io.stderr(`> ${t} ${r}
11
11
  `);try{let i=await this.io.fetch(r,{method:t,headers:{...a===void 0?{}:{authorization:`Bearer ${a}`},accept:"application/json",...s===void 0?{}:{"content-type":"application/json"}},body:s===void 0?void 0:JSON.stringify(s),redirect:"manual"});return this.debug&&this.io.stderr(`< ${i.status} ${i.headers.get("x-request-id")??""}
12
- `),i}catch(i){throw new y("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${i instanceof Error?i.message:String(i)}`)}}};function bp(e){return new y("token_not_for_machines",`the platform's edge refused PALBASE_TOKEN at ${e}, taking it for a machine's address (token_not_for_machines): the token itself was not refused`,{hint:"nothing is wrong with the token: run this again in a few minutes, and tell Palbase if it lasts"})}async function ki(e){let t=await e.clone().json().catch(()=>null),n=t&&typeof t=="object"?t.error:void 0;return typeof n=="string"&&/^(console|token|edge)_/.test(n)?n:void 0}function yw(e){try{return JSON.parse(e)}catch{return e}}var wp={version:"0.33.0"};var Na=wp.version;async function $p(e,t,n){let s=new Map(n.map(i=>[i.name,i])),a=e.includes("--json"),r=e.includes("--debug");try{let{name:i,at:o}=vw(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${Na}
12
+ `),i}catch(i){throw new y("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${i instanceof Error?i.message:String(i)}`)}}};function bp(e){return new y("token_not_for_machines",`the platform's edge refused PALBASE_TOKEN at ${e}, taking it for a machine's address (token_not_for_machines): the token itself was not refused`,{hint:"nothing is wrong with the token: run this again in a few minutes, and tell Palbase if it lasts"})}async function ki(e){let t=await e.clone().json().catch(()=>null),n=t&&typeof t=="object"?t.error:void 0;return typeof n=="string"&&/^(console|token|edge)_/.test(n)?n:void 0}function yw(e){try{return JSON.parse(e)}catch{return e}}var wp={version:"0.34.0"};var Na=wp.version;async function $p(e,t,n){let s=new Map(n.map(i=>[i.name,i])),a=e.includes("--json"),r=e.includes("--debug");try{let{name:i,at:o}=vw(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${Na}
13
13
  `),0):(t.stdout(Sp(n)),e.length===0||e.includes("--help")||e.includes("-h")?0:2);if(i==="help"){let S=e[o+1],$=S===void 0?void 0:s.get(S);if(S!==void 0&&!$)throw vp(S,n);return t.stdout($?kp($):Sp(n)),0}let l=s.get(i);if(!l)throw vp(i,n);let d=[...e.slice(0,o),...e.slice(o+1)];if(d.includes("--help")||d.includes("-h"))return t.stdout(kp(l)),0;let{flags:p,positionals:u}=_w(l,d),h=l.args??{min:0,max:0};if(u.length<h.min)throw m(`${l.name} needs ${h.min===1?"an argument":`${h.min} arguments`}`,$i(l));if(u.length>h.max)throw m(`${l.name} takes ${h.max===0?"no argument":`at most ${h.max}`}: ${JSON.stringify(u.slice(h.max))} is left over`,$i(l));let b={project:p.project,env:p.env,json:p.json===!0,nonInteractive:p["non-interactive"]===!0,yes:p.yes===!0,debug:p.debug===!0},w=lp(t.env),_=new xa(up(t.configDir,w)),T={io:t,state:_,out:new Ma(t,b.json),global:b,args:u,flags:p,interactive:t.interactive&&!b.nonInteractive,cloud:w,studio:new Da(t,w,_,b.debug)};return await l.run(T)??0}catch(i){return kw(i,t,a,r)}}function vw(e){let t=new Set;for(let[n,s]of Object.entries(Oa))s.type==="string"&&(t.add(`--${n}`),s.short&&t.add(`-${s.short}`));for(let n=0;n<e.length;n++){let s=e[n];if(s==="--")return{at:-1};if(t.has(s)){n++;continue}if(!s.startsWith("-"))return{name:s,at:n}}return{at:-1}}function _w(e,t){let n={...Oa,...e.flags??{}},s={};for(let[a,r]of Object.entries(n))s[a]={type:r.type,...r.short?{short:r.short}:{},...r.multiple?{multiple:!0}:{}};try{let{values:a,positionals:r}=ww({args:t,options:s,strict:!0,allowPositionals:!0});return{flags:a,positionals:r}}catch(a){throw m(a instanceof Error?a.message.replace(/^.*?: /,""):String(a),`palbase help ${e.name}`)}}function kw(e,t,n,s){let a="error",r,i,o=1,l;return e instanceof y?(a=e.code,r=e.message,i=e.hint,o=e.exitCode):e instanceof j?(a=e.code,r=e.message,l=e.requestId,e.code==="token_out_of_scope"&&t.env.PALBASE_TOKEN&&(r=`${e.op} refused PALBASE_TOKEN: ${e.message}`,i="this is the token's reach, fixed when it was minted: a person's yes does not change it \u2014 mint a token whose scopes name this call, or have a person run it signed in")):r=e instanceof Error?e.message:String(e),n&&e instanceof y&&e.reported||(n?t.stdout(`${JSON.stringify({error:a,message:r,...i?{hint:i}:{},...l?{request_id:l}:{}},null,2)}
14
14
  `):(t.stderr(`error: ${K(r)}${l?` (request ${K(l)})`:""}
15
15
  `),i&&t.stderr(` ${K(i)}
@@ -28,7 +28,7 @@ var Lb=Object.defineProperty;var We=(e,t)=>{for(var n in t)Lb(e,n,{get:t[n],enum
28
28
  /${r}
29
29
  `),"added"}var Qp={name:"run",summary:"run a command with the environment's address and your own key in its environment, nothing written to disk",usage:"[--secrets development|production] -- <command> [args\u2026]",details:["palbase run -- bun dev, palbase run -- npm test: the app reads the project's secrets at boot (PALBASE_SECRETS)."],args:{min:1,max:1e3},flags:{secrets:ar},scope:"environment",async run(e){let t=await f(e),n=nr(e);(t.env_slug===Ue||n==="production")&&await v(e,t,`run ${e.args[0]} with a key of ${t.env_slug}`,sr(t,n));let s=await tr(e,t);e.out.note(`${N(t)}: ${e.args.join(" ")}`);let a=await e.io.run(e.args[0],e.args.slice(1),{cwd:e.io.cwd,env:{PALBASE_URL:s.url,PALBASE_PROJECT_ID:s.projectId,PALBASE_SECRET_KEY:s.key.key,PALBASE_SECRETS:n},inherit:!0});if(a.code===127)throw new y("not_found",`${e.args[0]}: no such program`);return a.code}};var Lv=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/,ed=20;async function Dv(e,t,n,s){if(n.length===0)return{};let a=n.map(i=>({email:`${i.toLowerCase()}.${s}@test.invalid`,password:td(18).toString("base64url")})),r=await g(e,t,"POST","/admin/test-users",{users:a,with_tokens:!0},C()).catch(i=>{throw i instanceof j&&i.code==="token_out_of_scope"&&vn(e.io)?new y("token_out_of_scope",`PALBASE_TOKEN, an environment's token, may not make test users: its scopes do not name auth:test-users (${i.message})`,{hint:"a token's scopes are fixed when it is minted, and a person's yes does not widen them: mint one with --scope auth:test-users (palbase env:tokens:create, an admin)"}):i});if(r.users.length!==n.length)throw new y("unexpected_answer",`auth made ${r.users.length} test users of the ${n.length} asked for`);return Object.fromEntries(n.map((i,o)=>{let l=r.users[o];return[i,{id:l.user_id,email:l.email??a[o].email,password:a[o].password,...l.access_token?{accessToken:l.access_token}:{}}]}))}async function Nv(e,t,n){let s=[];for(let a of Object.values(n))try{await g(e,t,"DELETE",`/admin/test-users/${k(a.id)}`)}catch(r){r.status!==404&&s.push(a.id)}return s}async function qv(e,t,n){if(e.out.note(`${t}'s test command, as the platform holds it: ${Kv(n)}`),e.global.yes)return;if(!e.interactive)throw new y("confirmation_needed",`palbase test would run ${t}'s test command on this computer \u2014 a setting on the platform, not code of this checkout`,{hint:"read it above, then confirm with -y"});let s=(await e.io.prompt("Run it on this computer? [y/N] ")).trim().toLowerCase();if(s!=="y"&&s!=="yes")throw new y("cancelled","nothing was run")}function Kv(e){return A(e).replace(/\p{Cf}/gu,t=>`\\u${t.codePointAt(0).toString(16).padStart(4,"0")}`)}function Fi(e){let t=[...new Set(re(e)??[])];for(let s of t)if(!Lv.test(s))throw m(`${JSON.stringify(s)} is no test user's name: letters, digits, _ and -, 64 at most`);let n=new Map;for(let s of t){let a=n.get(s.toLowerCase());if(a!==void 0)throw m(`${JSON.stringify(a)} and ${JSON.stringify(s)} are one test user (its address is in lower case): name it once`);n.set(s.toLowerCase(),s)}if(t.length>ed)throw m(`--users names ${t.length}; a run makes ${ed} at most`);return t}var nd={SIGHUP:1,SIGINT:2,SIGTERM:15};function Gv(){let e,t=Object.keys(nd).map(n=>[n,()=>{e??=n}]);for(let[n,s]of t)process.on(n,s);return{got:()=>e,release:()=>{for(let[n,s]of t)process.off(n,s)}}}var sd={name:"test",summary:"run the backend's tests as the platform does before a build goes out: test users made for the run and deleted after, PALBASE_TEST_* in the command's environment",usage:"[--machine <name>] [--users <names>] [--base-url <url>] [--secrets development|production] [-- <command> [args\u2026]]",details:["palbase test --machine backend --base-url http://localhost:8080: the machine's own test command and test users, as the platform runs them before each build of it rolls out.",'palbase test --users alice,bob --base-url http://localhost:8080 -- bun test: api.signInAs("alice") acts as a test user made for this run.',"The command gets what palbase run gives it, and PALBASE_TEST_AUTH_URL (the environment's address), PALBASE_TEST_IDENTITIES (the run's test users, each with its token) and, with --base-url, PALBASE_TEST_BASE_URL (where the app under test answers; without it HTTP tests skip themselves).","The test users are deleted when the command ends, however it ends \u2014 Ctrl-C too: the command is stopped, they are deleted, and palbase test ends as the signal would have.","With an environment's token (PALBASE_TOKEN, scope auth:test-users) the test users are made and deleted with the token, and no key is made: the command gets no PALBASE_SECRET_KEY, so test the app where it is deployed \u2014 palbase test --users alice,bob --base-url https://<ref>.app.palbase.studio -- bun test ./tests (the machine named backend; https://<name>-<ref>.app.palbase.studio for another, as machines:list shows)."],args:{min:0,max:1e3},flags:{machine:{type:"string",value:"name",description:"a machine whose test command (when no command is given) and test users (when --users is not) are run"},users:{type:"string",value:"names",description:"the test users to make for the run, comma-separated"},"base-url":{type:"string",value:"url",description:"where the app under test answers (http://localhost:8080)"},secrets:ar},scope:"environment",async run(e){let t=Fi(e.flags.users),n=e.flags["base-url"];if(n!==void 0&&!/^https?:\/\/[^\s/]+/.test(n))throw m(`--base-url is an http(s) address, not ${JSON.stringify(n)}`);let s=e.flags.machine;if(e.args.length===0&&s===void 0)throw m("which tests? give their command after --, or --machine <name> to run its own","palbase test -- bun test");let a=await f(e),r=e.args.length>0?{cmd:e.args[0],args:e.args.slice(1)}:void 0;if(s!==void 0){let w=await g(e,a,"GET",`/machines/${k(s)}`);if(e.flags.users===void 0&&(t=w.test_users??[]),r===void 0){if(!w.test_command)throw m(`the machine ${s} runs no tests`,`give their command after --, or set it: palbase machines:update ${s} --test-command "bun test"`);await qv(e,s,w.test_command),r={cmd:"sh",args:["-c",w.test_command]}}}let i=r,o=vn(e.io);if(o&&e.flags.secrets!==void 0)throw m("--secrets names the secrets an app started here reads with your key, and an environment's token makes no key: leave it out");let l=nr(e);o?(a.env_slug===Ue&&t.length>0&&await v(e,a,`make ${t.length} test user(s) there for ${i.cmd}, deleted when it ends`),e.out.note(`PALBASE_TOKEN is an environment's token: no key is made, and ${i.cmd} gets no PALBASE_SECRET_KEY \u2014 ${n!==void 0?`it tests the app at ${n}`:"with no --base-url the HTTP tests skip themselves: give the deployed machine's address (palbase machines:list shows it)"}`)):(a.env_slug===Ue||l==="production")&&await v(e,a,`run ${i.cmd} with a key of ${a.env_slug}${t.length?` and ${t.length} test user(s) made there`:""}`,sr(a,l));let d=o?{...await Bi(e,a),key:void 0}:await tr(e,a),p=td(4).toString("hex"),u=t.length>0?Gv():void 0,h=0;try{let w=await Dv(e,a,t,p);t.length>0&&e.out.note(`${N(a)}: test users ${t.join(", ")} made for this run (${p})`);try{if(u?.got()===void 0){let _=await e.io.run(i.cmd,i.args,{cwd:e.io.cwd,env:{PALBASE_URL:d.url,PALBASE_PROJECT_ID:d.projectId,...d.key?{PALBASE_SECRET_KEY:d.key.key,PALBASE_SECRETS:l}:{},PALBASE_TEST_AUTH_URL:d.url,PALBASE_TEST_IDENTITIES:JSON.stringify(w),...n!==void 0?{PALBASE_TEST_BASE_URL:n}:{}},inherit:!0});if(_.code===127)throw new y("not_found",`${i.cmd}: no such program`);h=_.code}}finally{if(t.length>0){let _=await Nv(e,a,w);_.length>0?e.out.warn(`test users not deleted: ${_.join(", ")} \u2014 palbase auth:test-users:delete <id> deletes one`):e.out.note(`the run's ${t.length} test user(s) deleted`)}}}finally{u?.release()}let b=u?.got();return b===void 0?h:128+nd[b]}};var ad={name:"auth:settings:get",summary:"the environment's auth settings: passwords, email confirmation, sessions, tokens, passkeys, risk",scope:"environment",async run(e){let t=await f(e),{body:n}=await qe(e,t,"/admin/auth-settings");e.out.result(n,()=>Yn(e,n))}},rd={name:"auth:settings:set",summary:'change auth settings: key=value, a value read as JSON when it is JSON (12, true, ["https://\u2026"])',usage:"<key=value>\u2026",details:[`Text needs no quotes (site_url=https://shop.example); quote text that reads as JSON: from_name='"123"'.`,"The change is made on the revision read: one made beside it in the meantime (Studio, another terminal) is refused, not overwritten.","palbase auth:settings:get lists the keys."],args:{min:1,max:50},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args),{body:s,etag:a}=await qe(e,t,"/admin/auth-settings"),r=Qt(s,n),i=Dt(s,r);if(i.length===0)return ze(e,"the auth settings",s);await v(e,t,`set the auth settings ${i.join(", ")}`);let o=await g(e,t,"PUT","/admin/auth-settings",Object.fromEntries(i.map(l=>[l,r[l]])),Re(a));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${Te(s[l])} \u2192 ${Te(o[l])}`)})}},Qn=["google","apple","github","microsoft"],Uv={google:"web_client_id",apple:"services_id",microsoft:"application_id",github:"oauth_client_id"},zv=new Set(["organizations","consumers","common"]),id={name:"auth:providers:list",summary:"the sign-in methods: email, phone and the social providers, whether each is on, their clients and stored secrets (by name)",scope:"environment",async run(e){let t=await f(e),[n,s]=await Promise.all([g(e,t,"GET","/admin/providers"),g(e,t,"GET","/admin/social-auth")]),a=[...n.providers.map(r=>({provider:r.id,on:r.enabled,detail:r.settings?`codes by ${r.settings.sender??"sms"}, ${r.settings.otp_ttl_seconds}s, ${r.settings.otp_max_attempts} tries, sign-up ${r.settings.allow_signups?"on":"off"}`:"",secrets:""})),...Qn.map(r=>{let i=s.providers[r]??{enabled:!1,browser_clients:[],native_clients:[]},o=[...i.browser_clients.map(l=>`${l.key} (${l.platform})`),...i.native_clients.map(l=>`${l.key} (${l.platform}, native)`)];return{provider:r,on:i.enabled,detail:o.length===0?"no client":o.join(", "),secrets:s.credentials.filter(l=>l.provider===r).map(l=>`${l.key} ${l.configured?"set":"not set"}`).join(", ")}})];e.out.result({providers:n.providers,social:s.providers,credentials:s.credentials},()=>e.out.table(a,[{header:"provider",value:r=>r.provider},{header:"on",value:r=>r.on?"yes":"no"},{header:"clients / settings",value:r=>r.detail},{header:"secrets",value:r=>r.secrets}]))}},od={name:"auth:providers:set",summary:"turn a sign-in provider on or off and set it up: its client, its secret (from stdin), phone codes",usage:"<provider> [--enable|--disable] [setting=value\u2026] [--client-id <id>] [--secret-stdin] \u2026",details:["email is always on. phone: --enable/--disable, and its code settings as setting=value (allow_signups, otp_ttl_seconds, otp_max_attempts, sender, sms_second_factor).","google, apple, github, microsoft: --client-id sets the browser client --client names (default web), made when the provider has none of that name.","--secret-stdin reads the client's secret from stdin (Apple: the .p8 signing key, with --team-id and --key-id) and keeps it under --credential (default: the provider's name). It is never taken from the command line and never printed.",`--clients-file replaces the provider's clients with the file's {"browser_clients": [\u2026], "native_clients": [\u2026]} (native apps, several clients).`],args:{min:1,max:20},flags:{...Gs,client:{type:"string",value:"key",description:"the browser client --client-id, --return-uri and the secret go to (default web)"},"client-id":{type:"string",value:"id",description:"the provider's id of your app (Google web client id, Apple services id, Microsoft application id, GitHub client id)"},"return-uri":{type:"string",multiple:!0,value:"url",description:"where your app is sent back after sign-in (repeat it; replaces the list)"},application:{type:"string",value:"key",description:"the application the client is for (default app)"},variant:{type:"string",value:"name",description:"the build variant the client is for (default release)"},directory:{type:"string",value:"tenant",description:"microsoft: a tenant id, organizations, consumers or common (default common)"},"secret-stdin":{type:"boolean",description:"read the client's secret (Apple: the .p8 key) from stdin"},credential:{type:"string",value:"key",description:"the name the secret is kept under (default: the provider's)"},"team-id":{type:"string",value:"id",description:"apple: your team's id, with --secret-stdin"},"key-id":{type:"string",value:"id",description:"apple: the signing key's id, with --secret-stdin"},"clients-file":{type:"string",value:"path",description:"replace the provider's clients with the file's (JSON)"}},scope:"environment",async run(e){let t=e.args[0],n=await f(e);if(t==="email")throw new y("always_on","email sign-in is always on, and has no settings of its own here",{hint:"its policy is in palbase auth:settings:set"});if(t==="phone")return Bv(e,n,e.args.slice(1));if(!Qn.includes(t))throw m(`no provider ${JSON.stringify(t)}`,`the providers: email, phone, ${Qn.join(", ")}`);if(e.args.length>1)throw m(`${t} takes flags, not settings: palbase help auth:providers:set`);await Jv(e,n,t)}};async function Bv(e,t,n){let s=Xn(e),a=Zt(n);if(s===void 0&&a.length===0)throw m("nothing to change: --enable, --disable or setting=value","palbase help auth:providers:set");let r=await g(e,t,"GET","/admin/providers/phone/settings"),i=Qt(r,a),o=Dt(r,i);if(s===void 0&&o.length===0)return ze(e,"the phone settings",r);await v(e,t,`phone sign-in: ${[...s===void 0?[]:[s?"turn it on":"turn it off"],...o.map(p=>`set ${p}`)].join(", ")}`);let l=o.length>0?await g(e,t,"PUT","/admin/providers/phone/settings",i):r,d=s===void 0?void 0:await g(e,t,"POST","/admin/providers/phone",{enabled:s});e.out.result({provider:"phone",...d?{enabled:d.enabled}:{},settings:l},()=>{d&&e.out.line(`phone sign-in is ${d.enabled?"on":"off"}.`);for(let p of o)e.out.line(`${p}: ${Te(r[p])} \u2192 ${Te(l[p])}`)})}function Fv(e){if(zv.has(e))return{mode:e};if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return{mode:"tenant",tenant_id:e};throw m("--directory is a tenant id, organizations, consumers or common")}async function Jv(e,t,n){let s=e.flags,a=Xn(e),r=await qe(e,t,"/admin/social-auth"),i=r.etag,o=r.body.providers[n]??{enabled:!1,browser_clients:[],native_clients:[]},l={},d=[],p=s.credential??n,u;if(s["secret-stdin"]===!0){if(n==="apple"&&(s["team-id"]===void 0||s["key-id"]===void 0))throw m("Apple's signing key goes with --team-id and --key-id");u=await Me(e,n==="apple"?"Apple's .p8 signing key":`${n}'s client secret`),d.push(`keep its secret as ${p}`)}let h=["client","client-id","return-uri","application","variant","directory","credential"].filter(T=>s[T]!==void 0);if(s["clients-file"]!==void 0){if(h.length>0)throw m(`--clients-file and --${h[0]} are one or the other`);let T=ue(await be(e,s["clients-file"],"the clients"),"the clients file");if(typeof T!="object"||T===null||T.browser_clients===void 0&&T.native_clients===void 0)throw m('the clients file is {"browser_clients": [\u2026], "native_clients": [\u2026]}');l.browser_clients=T.browser_clients??o.browser_clients,l.native_clients=T.native_clients??o.native_clients,d.push(`set its clients from ${s["clients-file"]}`)}else if(h.length>0||u!==void 0){let T=s.client??"web",S=[...o.browser_clients],$=S.findIndex(P=>P.key===T),R=u!==void 0&&h.every(P=>P==="credential");if($>=0||!R){let P=$>=0?{...S[$]}:{key:T,enabled:!0,application_key:"app",platform:"web",variant:"release",return_uris:[]},L=Uv[n];if(s["client-id"]!==void 0&&(P[L]=s["client-id"]),!P[L])throw m(`the ${n} client ${T} is new: give it --client-id`);if(s["return-uri"]!==void 0&&(P.return_uris=s["return-uri"]),s.application!==void 0&&(P.application_key=s.application),s.variant!==void 0&&(P.variant=s.variant),n==="microsoft")P.directory=s.directory!==void 0?Fv(s.directory):P.directory??{mode:"common"};else if(s.directory!==void 0)throw m("--directory is a Microsoft client's");(u!==void 0||s.credential!==void 0||!P.credential_ref)&&(P.credential_ref=p),S[$>=0?$:S.length]=P,l.browser_clients=S,d.push(`${$>=0?"change":"add"} its browser client ${T}`)}}if(a!==void 0&&(l.enabled=a,d.push(a?"turn it on":"turn it off")),d.length===0)throw m("nothing to change: give a flag","palbase help auth:providers:set");await v(e,t,`${n} sign-in: ${d.join(", ")}`);let b=r.body;if(u!==void 0){let T=n==="apple"?{provider:n,kind:"apple_signing_key",team_id:s["team-id"],key_id:s["key-id"],private_key:u}:{provider:n,kind:"oauth_client_secret",client_secret:u};i=(await Ne(e,t,"PUT",`/admin/social-auth/credentials/${k(p)}`,T,Re(i))).etag??i}Object.keys(l).length>0?b=await g(e,t,"PATCH",`/admin/social-auth/providers/${k(n)}`,l,Re(i)):b=await g(e,t,"GET","/admin/social-auth");let w=b.providers[n],_=b.credentials.filter(T=>T.provider===n);e.out.result({provider:n,...w,credentials:_},()=>{e.out.line(`${n} sign-in is ${w.enabled?"on":"off"}: ${w.browser_clients.length} browser client(s), ${w.native_clients.length} native.`);for(let T of _)e.out.line(` secret ${T.key}: ${T.configured?"set":"not set"}`)})}var ld={name:"auth:providers:secret:delete",summary:"delete a sign-in provider's stored secret by its name; one a client still uses is refused",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await f(e),{body:s,etag:a}=await qe(e,n,"/admin/social-auth");if(!s.credentials.some(r=>r.key===t))throw m(`the environment holds no provider secret ${JSON.stringify(t)}`,"palbase auth:providers:list names them");await v(e,n,`delete the provider secret ${t}`),await g(e,n,"DELETE",`/admin/social-auth/credentials/${k(t)}`,void 0,Re(a)),e.out.result({deleted:t},()=>e.out.line(`Deleted the provider secret ${t}.`))}},pd={name:"auth:providers:disable",summary:"turn a sign-in provider off (phone, google, apple, github, microsoft); its clients and secrets stay",usage:"<provider>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await f(e);if(t==="email")throw new y("always_on","email sign-in cannot be turned off");if(t==="phone"){await v(e,n,"turn phone sign-in off");let r=await g(e,n,"POST","/admin/providers/phone",{enabled:!1});e.out.result(r,()=>e.out.line("phone sign-in is off."));return}if(!Qn.includes(t))throw m(`no provider ${JSON.stringify(t)}`,`the providers: phone, ${Qn.join(", ")}`);let{etag:s}=await qe(e,n,"/admin/social-auth");await v(e,n,`turn ${t} sign-in off`);let a=await g(e,n,"PATCH",`/admin/social-auth/providers/${k(t)}`,{enabled:!1},Re(s));e.out.result({provider:t,...a.providers[t]},()=>e.out.line(`${t} sign-in is off; its clients and secrets stay.`))}},dd={name:"auth:roles:list",summary:"the environment's roles: their permissions, which one new users get, how many users hold each",scope:"environment",async run(e){let t=await f(e),{roles:n}=await g(e,t,"GET","/admin/roles");e.out.result({roles:n},()=>{n.length===0&&e.out.line("No role yet: palbase auth:roles:set <name> --permissions \u2026 makes one."),e.out.table(n,[{header:"role",value:s=>`${s.name}${s.is_default?" (default)":""}`},{header:"permissions",value:s=>s.permissions.join(", ")},{header:"users",value:s=>String(s.user_count??0)},{header:"description",value:s=>s.description}])})}},ud={name:"auth:roles:set",summary:"make a role, or change one: its permissions (the list is replaced), description, whether new users get it",usage:"<name> [--permissions <a.b,c.d>] [--description <text>] [--default yes|no]",args:{min:1,max:1},flags:{permissions:{type:"string",multiple:!0,value:"list",description:'resource.action permissions, comma-separated or repeated; replaces the list ("" for none)'},description:{type:"string",value:"text",description:"what the role is for"},default:{type:"string",value:"yes|no",description:"whether new users get it (one role at most)"}},scope:"environment",async run(e){let t=await f(e),n=e.args[0],{roles:s}=await g(e,t,"GET","/admin/roles"),a=s.find(p=>p.name===n),r=re(e.flags.permissions),i=en(e.flags.default,"default");if(a&&r===void 0&&e.flags.description===void 0&&i===void 0)throw m("nothing to change: give a flag","palbase help auth:roles:set");let o={permissions:r??a?.permissions??[],description:e.flags.description??a?.description??"",is_default:i??a?.is_default??!1};await v(e,t,`${a?"change":"make"} the role ${n}${a?.user_count?` (${a.user_count} users hold it)`:""}`);let l=await g(e,t,"PUT",`/admin/roles/${k(n)}`,o),d=l.roles.find(p=>p.name===n);e.out.result(d??l,()=>e.out.line(`${a?"Changed":"Made"} the role ${n}: ${(d?.permissions??o.permissions).join(", ")||"no permission"}${d?.is_default?", given to new users":""}.`))}},cd={name:"auth:roles:delete",summary:"delete a role; one users hold is deleted only with --force, and is taken from them",usage:"<name> [--force]",args:{min:1,max:1},flags:{force:{type:"boolean",description:"delete it although users hold it"}},scope:"environment",async run(e){let t=await f(e),n=e.args[0];await v(e,t,`delete the role ${n}${e.flags.force===!0?", taking it from the users who hold it":""}`),await g(e,t,"DELETE",`/admin/roles/${k(n)}${e.flags.force===!0?"?confirm=true":""}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the role ${n}.`))}},gd={event:{type:"string",value:"event",description:"before.user.create, before.login, before.token.issue, \u2026 (the module lists them)"},url:{type:"string",value:"url",description:"the https endpoint auth calls and waits for"},"failure-mode":{type:"string",value:"deny|allow",description:"what auth does when the hook does not answer in time (default deny)"},timeout:{type:"string",value:"ms",description:"how long auth waits, 100\u20135000 ms (default 5000)"}},hd={name:"auth:hooks:list",summary:"the blocking hooks: what auth calls, and waits for, before a sign-up, a sign-in, a token",scope:"environment",async run(e){let t=await f(e),{hooks:n}=await g(e,t,"GET","/admin/hooks");e.out.result({hooks:n},()=>{n.length===0&&e.out.line("No hook: palbase auth:hooks:create --event \u2026 --url \u2026 registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"event",value:s=>s.event},{header:"url",value:s=>s.url},{header:"on failure",value:s=>s.failure_mode},{header:"timeout",value:s=>`${s.timeout_ms} ms`},{header:"on",value:s=>s.enabled?"yes":"no"}])})}},md={name:"auth:hooks:create",summary:"register a blocking hook; its signing secret is printed this once",usage:"--event <event> --url <url> [--failure-mode deny|allow] [--timeout <ms>] [--disable]",flags:{...gd,disable:{type:"boolean",description:"register it turned off"}},scope:"environment",async run(e){let t=await f(e);if(e.flags.event===void 0||e.flags.url===void 0)throw m("a hook needs --event and --url");let n=Oi({event:e.flags.event,url:e.flags.url,failure_mode:e.flags["failure-mode"],timeout_ms:te(e.flags.timeout,"timeout"),...e.flags.disable===!0?{enabled:!1}:{}});await v(e,t,`register a hook on ${String(n.event)} calling ${String(n.url)}`);let s=await g(e,t,"POST","/admin/hooks",n,C());e.out.result(s,()=>{e.out.line(`Registered ${s.id}: ${s.event} \u2192 ${s.url}.`),s.secret&&e.out.line(`signing secret (shown this once): ${s.secret}`)})}},fd={name:"auth:hooks:update",summary:"change a blocking hook: its event, endpoint, timeout, failure mode, on or off (its secret stays)",usage:"<id> [--event \u2026] [--url \u2026] [--failure-mode \u2026] [--timeout \u2026] [--enable|--disable]",args:{min:1,max:1},flags:{...gd,...Gs},scope:"environment",async run(e){let t=await f(e),{hooks:n}=await g(e,t,"GET","/admin/hooks"),s=n.find(o=>o.id===e.args[0]);if(!s)throw new y("no_such_hook",`no hook ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:hooks:list shows them"});let a={event:e.flags.event??s.event,url:e.flags.url??s.url,failure_mode:e.flags["failure-mode"]??s.failure_mode,timeout_ms:te(e.flags.timeout,"timeout")??s.timeout_ms,enabled:Xn(e)??s.enabled},r=Dt(Wv(s),a);if(r.length===0)throw m("nothing to change: give a flag","palbase help auth:hooks:update");await v(e,t,`change the hook ${s.id}: ${r.join(", ")}`);let i=await g(e,t,"PUT",`/admin/hooks/${k(s.id)}`,a);e.out.result(i,()=>e.out.line(`${i.id}: ${i.event} \u2192 ${i.url}, ${i.failure_mode} on failure, ${i.timeout_ms} ms, ${i.enabled?"on":"off"}.`))}};function Wv(e){return{event:e.event,url:e.url,failure_mode:e.failure_mode,timeout_ms:e.timeout_ms,enabled:e.enabled}}var yd={name:"auth:hooks:delete",summary:"delete a blocking hook; auth stops calling it",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`delete the hook ${e.args[0]}`),await g(e,t,"DELETE",`/admin/hooks/${k(e.args[0])}`),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the hook ${e.args[0]}.`))}},bd={name:"auth:oauth-clients:list",summary:"the OAuth clients of the environment's OpenID provider (apps that sign in with your users' accounts)",scope:"environment",async run(e){let t=await f(e),{clients:n}=await g(e,t,"GET","/admin/oauth-clients");e.out.result({clients:n},()=>{n.length===0&&e.out.line("No OAuth client: palbase auth:oauth-clients:create <name> registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"client id",value:s=>s.client_id},{header:"name",value:s=>s.client_name},{header:"kind",value:s=>s.is_public?"public":s.token_endpoint_auth_method},{header:"grants",value:s=>s.grant_types.join(", ")},{header:"redirect uris",value:s=>s.redirect_uris.join(", ")}])})}},wd={name:"auth:oauth-clients:create",summary:"register an OAuth client; a confidential client's secret is printed this once",usage:"<name> [--public] [--redirect-uri <url>\u2026] [--grant <type>\u2026] [--scope <scope>\u2026] [--client-id <id>]",args:{min:1,max:1},flags:{public:{type:"boolean",description:"a public client (a CLI, a mobile app): it has no secret"},"redirect-uri":{type:"string",multiple:!0,value:"url",description:"where it may send the browser back (repeat it)"},grant:{type:"string",multiple:!0,value:"type",description:"authorization_code (default), refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code"},scope:{type:"string",multiple:!0,value:"scope",description:"what it may ask for (default openid, profile, email)"},"client-id":{type:"string",value:"id",description:"a client_id to use as it is (default: a random one)"}},scope:"environment",async run(e){let t=await f(e),n=Oi({client_name:e.args[0],client_id:e.flags["client-id"],is_public:e.flags.public===!0?!0:void 0,redirect_uris:e.flags["redirect-uri"],grant_types:re(e.flags.grant),scopes:re(e.flags.scope)});await v(e,t,`register the OAuth client ${e.args[0]}`);let s=await g(e,t,"POST","/admin/oauth-clients",n,C());e.out.result(s,()=>{e.out.line(`Registered ${s.client_name}: client_id ${s.client_id} (${s.id}).`),s.client_secret&&e.out.line(`client secret (shown this once): ${s.client_secret}`)})}},vd={name:"auth:oauth-clients:delete",summary:"delete an OAuth client (by its id or its client_id)",usage:"<id|client-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),{clients:n}=await g(e,t,"GET","/admin/oauth-clients"),s=n.find(a=>a.id===e.args[0]||a.client_id===e.args[0]);if(!s)throw new y("no_such_client",`no OAuth client ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:oauth-clients:list shows them"});await v(e,t,`delete the OAuth client ${s.client_name} (${s.client_id})`),await g(e,t,"DELETE",`/admin/oauth-clients/${k(s.id)}`),e.out.result({deleted:s.id},()=>e.out.line(`Deleted the OAuth client ${s.client_name}.`))}};function Vv(e){return{...e.client_id.startsWith("oidc_")?{}:{client_id:e.client_id},is_public:e.is_public,grant_types:[...e.grant_types].sort(),redirect_uris:e.redirect_uris,post_logout_redirect_uris:e.post_logout_redirect_uris,response_types:e.response_types,scopes:e.scopes,token_endpoint_auth_method:e.token_endpoint_auth_method,id_token_lifetime_secs:e.id_token_lifetime_secs,dpop_bound_access_tokens:e.dpop_bound_access_tokens,...e.jwks?{jwks:e.jwks}:{}}}var _d={name:"auth",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([g(e,t,"GET","/admin/auth-settings"),g(e,t,"GET","/admin/providers"),g(e,t,"GET","/admin/social-auth"),g(e,t,"GET","/admin/roles"),g(e,t,"GET","/admin/hooks"),g(e,t,"GET","/admin/oauth-clients")]),l={};for(let[p,u]of Object.entries(n))l[`settings.${p}`]=u;let d=s.providers.find(p=>p.id==="phone");d&&(l["phone.enabled"]=d.enabled,d.settings&&(l["phone.settings"]=d.settings));for(let p of Qn){let u=a.providers[p];u&&(l[`social.${p}`]={enabled:u.enabled,browser_clients:u.browser_clients,native_clients:u.native_clients})}for(let p of a.credentials)l[`secrets.${p.key}`]={provider:p.provider,kind:p.kind,value:p.configured?"set":"not set"};for(let p of r.roles)l[`roles.${p.name}`]={description:p.description,is_default:p.is_default,permissions:[...p.permissions].sort()};for(let p of i.hooks)l[`hooks.${p.event} ${p.url}`]={failure_mode:p.failure_mode,timeout_ms:p.timeout_ms,enabled:p.enabled};for(let p of o.clients)l[`oauth_clients.${p.client_name}`]=Vv(p);return l},async copy(e,t,n,s){await Hv(e,t,pe(n,"settings."),s);for(let r of pe(n,"phone."))r.name==="enabled"&&await V(s,`turn phone sign-in ${r.from?"on":"off"}`,async()=>{await g(e,t,"POST","/admin/providers/phone",{enabled:r.from===!0})}),r.name==="settings"&&r.from!==void 0&&await V(s,"set the phone code settings",async()=>{await g(e,t,"PUT","/admin/providers/phone/settings",r.from)});await Yv(e,t,pe(n,"social."),pe(n,"secrets."),s);let a=pe(n,"roles.").sort((r,i)=>+(r.from?.is_default===!0)-+(i.from?.is_default===!0));for(let r of a)r.from===void 0?await V(s,`delete the role ${r.name}`,async()=>{await g(e,t,"DELETE",`/admin/roles/${k(r.name)}`)}):await V(s,`set the role ${r.name}`,async()=>{await g(e,t,"PUT",`/admin/roles/${k(r.name)}`,r.from)});await Xv(e,t,pe(n,"hooks."),s),await Zv(e,t,pe(n,"oauth_clients."),s)}};async function Hv(e,t,n,s){let a=n.filter(i=>i.from!==void 0);for(let i of n.filter(o=>o.from===void 0))s.left(`auth settings ${i.name}: set in the target only, and a setting is not taken away (set it with auth:settings:set)`);if(a.length===0)return;let{etag:r}=await qe(e,t,"/admin/auth-settings");await V(s,`set the auth settings ${a.map(i=>i.name).join(", ")}`,async()=>{await g(e,t,"PUT","/admin/auth-settings",Object.fromEntries(a.map(i=>[i.name,i.from])),Re(r))})}async function Yv(e,t,n,s,a){for(let l of s){let d=l.from;d===void 0?a.left(`secret ${l.name}: only in the target, left as it is (delete it with palbase auth:providers:secret:delete ${l.name})`):a.left(`secret ${l.name} (${d.provider}): secrets are not copied \u2014 set it with palbase auth:providers:set ${d.provider} --secret-stdin --credential ${l.name}`)}if(n.length===0)return;let r=await qe(e,t,"/admin/social-auth"),i=r.etag,o=new Set(r.body.credentials.filter(l=>l.configured).map(l=>l.key));for(let l of n){let d=l.from,p=[...new Set(d.browser_clients.map(u=>u.credential_ref).filter(u=>typeof u=="string"&&!o.has(u)))];if(p.length>0){a.left(`${l.name} sign-in: its clients use the secret ${p.join(", ")}, which the target does not hold \u2014 set it first (auth:providers:set ${l.name} --secret-stdin --credential ${p[0]})`);continue}await V(a,`set ${l.name} sign-in (${d.enabled?"on":"off"}, ${d.browser_clients.length+d.native_clients.length} client(s))`,async()=>{i=(await Ne(e,t,"PATCH",`/admin/social-auth/providers/${k(l.name)}`,d,Re(i))).etag??i})}}async function Xv(e,t,n,s){if(n.length===0)return;let{hooks:a}=await g(e,t,"GET","/admin/hooks");for(let r of n){let i=r.name.indexOf(" "),o=r.name.slice(0,i),l=r.name.slice(i+1),d=a.find(p=>p.event===o&&p.url===l);if(r.from===void 0)d&&await V(s,`delete the hook ${o} \u2192 ${l}`,async()=>{await g(e,t,"DELETE",`/admin/hooks/${k(d.id)}`)});else if(d)await V(s,`change the hook ${o} \u2192 ${l}`,async()=>{await g(e,t,"PUT",`/admin/hooks/${k(d.id)}`,{event:o,url:l,...r.from})});else{let p;await V(s,`register the hook ${o} \u2192 ${l}`,async()=>{p=(await g(e,t,"POST","/admin/hooks",{event:o,url:l,...r.from},C())).secret}),p&&s.shown(`the hook ${o} \u2192 ${l} signs with`,p)}}}async function Zv(e,t,n,s){if(n.length===0)return;let{clients:a}=await g(e,t,"GET","/admin/oauth-clients");for(let r of n){let i=a.find(l=>l.client_name===r.name),o=r.from;if(o===void 0)i&&await V(s,`delete the OAuth client ${r.name}`,async()=>{await g(e,t,"DELETE",`/admin/oauth-clients/${k(i.id)}`)});else if(i)if(!Oe(o.is_public,i.is_public)||o.client_id!==void 0&&o.client_id!==i.client_id)s.left(`OAuth client ${r.name}: its client_id or kind (public or not) differs, which a client cannot change \u2014 delete it in the target and run the copy again`);else{let{client_id:l,is_public:d,...p}=o;await V(s,`change the OAuth client ${r.name}`,async()=>{await g(e,t,"PATCH",`/admin/oauth-clients/${k(i.id)}`,p)})}else{let l;await V(s,`register the OAuth client ${r.name}`,async()=>{l=await g(e,t,"POST","/admin/oauth-clients",{client_name:r.name,...o},C())}),l?.client_secret&&s.shown(`the OAuth client ${r.name} (${l.client_id}) has the secret`,l.client_secret)}}}var kd=["bool","number","string","object"];async function sn(e,t){return(await g(e,t,"GET","/flags/templates/capabilities")).publish_enabled}async function nn(e,t,n){let s=await qe(e,t,"/flags/templates/draft");if(s.body.draft||!n)return{draft:s.body.draft,etag:s.etag};let a=await qe(e,t,"/flags/templates/active"),r=await Ne(e,t,"POST","/flags/templates/draft",{},Re(a.etag));return{draft:r.body.draft,etag:r.etag}}function Sd(e){return{value_type:e.value_type,value:e.value,...e.description?{description:e.description}:{},...e.variants&&e.variants.length>0?{variants:e.variants}:{},...e.conditions&&e.conditions.length>0?{conditions:e.conditions}:{}}}function Qv(e){return typeof e=="boolean"?"bool":typeof e=="number"?"number":typeof e=="string"?"string":"object"}function $d(e,t){switch(t){case"bool":if(e==="true"||e==="false")return e==="true";throw m(`a bool flag's value is true or false, not ${JSON.stringify(e)}`);case"number":{let n=Number(e);if(e.trim()===""||!Number.isFinite(n))throw m(`a number flag's value is a number, not ${JSON.stringify(e)}`);return n}case"string":{let n=St(e);return typeof n=="string"?n:e}default:{let n=St(e);if(typeof n!="object"||n===null)throw m(`an object flag's value is a JSON object or list, not ${JSON.stringify(e)}`);return n}}}function e_(e,t){let n=e.lastIndexOf("=>");if(n<=0)throw m(`a condition is "<expression> => <value>", not ${JSON.stringify(e)}`,`--condition "client.platform == 'ios' => true"`);return{when:e.slice(0,n).trim(),value:$d(e.slice(n+2).trim(),t)}}var Pd={name:"flags:list",summary:"the environment's flags as they are live: type, default, targeting (and the draft's state when it publishes)",scope:"environment",async run(e){let t=await f(e),n=await sn(e,t),s,a,r;if(n){let[i,o]=await Promise.all([g(e,t,"GET","/flags/templates/active"),nn(e,t,!1)]);s=Object.entries(i.definitions).map(([l,d])=>({key:l,...d})),a=i.version,r=o.draft}else s=await g(e,t,"GET","/flags/system");s.sort((i,o)=>i.key<o.key?-1:1),e.out.result({publishing:n,...a?{version:a}:{},flags:s,...r?{draft:{revision:r.revision,base_version:r.base_version}}:{}},()=>{n&&e.out.line(`Version ${a} is live${r?`; a draft (from ${r.base_version}) is in progress: palbase flags:publish shows and publishes it`:""}.`),s.length===0&&e.out.line("No flag yet: palbase flags:set <key> <value> makes one."),e.out.table(s,[{header:"flag",value:i=>i.key},{header:"type",value:i=>i.value_type},{header:"value",value:i=>JSON.stringify(i.value)},{header:"targeting",value:i=>i.conditions?.length?`${i.conditions.length} condition(s)`:""},{header:"description",value:i=>i.description??""}])})}},Ad={name:"flags:set",summary:"define a flag or change its default and targeting (into the draft when the environment publishes)",usage:'<key> <value> [--type bool|number|string|object] [--condition "<expression> => <value>"\u2026] [--description <text>]',details:["The value is read as the flag's type: true/false, a number, text, or a JSON object; a new flag's type is the value's unless --type says it.","--condition replaces the targeting (first match wins, 8 at most); --clear-conditions takes it away; without either it stays.","An environment that publishes takes the change into its shared draft (palbase flags:publish makes it live); one that does not, live at once."],args:{min:2,max:2},flags:{type:{type:"string",value:"type",description:"bool, number, string or object"},condition:{type:"string",multiple:!0,value:"rule",description:'"<expression> => <value>" (repeat it, in order)'},"clear-conditions":{type:"boolean",description:"take its targeting away"},variants:{type:"string",value:"list",description:"a string flag's allowed values, comma-separated"},description:{type:"string",value:"text",description:"what the flag is for"}},scope:"environment",async run(e){let t=await f(e),[n,s]=e.args,a=e.flags.type;if(a!==void 0&&!kd.includes(a))throw m(`--type is one of ${kd.join(", ")}`);if(e.flags.condition!==void 0&&e.flags["clear-conditions"]===!0)throw m("--condition and --clear-conditions are one or the other");let r=await sn(e,t),i,o;r?(o=await nn(e,t,!1),i=o.draft?.definitions[n]??(await g(e,t,"GET","/flags/templates/active")).definitions[n]):i=(await g(e,t,"GET","/flags/system")).find(u=>u.key===n);let l=a??i?.value_type??Qv(St(s)),d=e.flags["clear-conditions"]===!0?[]:e.flags.condition!==void 0?e.flags.condition.map(u=>e_(u,l)):i?.value_type===l?i.conditions??[]:[],p=Sd({value_type:l,value:$d(s,l),description:e.flags.description??i?.description,variants:re(e.flags.variants)??(i?.value_type===l?i.variants:void 0),conditions:d});if(await v(e,t,r?`put ${n} = ${Te(p.value)} into the flags draft`:`set the flag ${n} = ${Te(p.value)}, live at once`),r){o?.draft||(o=await nn(e,t,!0));let u=await Ne(e,t,"PUT",`/flags/templates/draft/parameters/${k(n)}`,p,Re(o.etag));e.out.result({key:n,...p,draft:{revision:u.body.draft?.revision}},()=>e.out.line(`${n} = ${JSON.stringify(p.value)} is in the draft: palbase flags:publish makes it live.`))}else{let u=await g(e,t,"PUT",`/flags/system/${k(n)}`,p);e.out.result(u,()=>e.out.line(`${n} = ${JSON.stringify(u.value)} (${u.value_type}) is live.`))}}},Rd={name:"flags:delete",summary:"delete a flag, with every user's own value of it (from the draft when the environment publishes)",usage:"<key>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0],s=await sn(e,t);if(await v(e,t,s?`take ${n} out of the flags draft`:`delete the flag ${n} and every user's value of it, live at once`),s){let a=await nn(e,t,!0);await g(e,t,"DELETE",`/flags/templates/draft/parameters/${k(n)}`,void 0,Re(a.etag)),e.out.result({deleted:n,draft:!0},()=>e.out.line(`${n} is out of the draft: palbase flags:publish makes it so.`))}else await g(e,t,"DELETE",`/flags/system/${k(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the flag ${n}.`))}};function Ji(){return new y("publishing_off","this environment's flags are not published through drafts: flags:set and flags:delete change them live",{hint:"versions, publishing and rollback are for an environment that publishes"})}function t_(e,t){t.changes.length===0&&e.out.line("The draft changes nothing.");for(let n of t.changes){let s=n.override_count>0?` (${n.override_count} users' own values)`:"";n.kind==="added"?e.out.line(` + ${n.key} = ${JSON.stringify(n.after?.value)}`):n.kind==="removed"?e.out.line(` - ${n.key}${s}`):e.out.line(` ~ ${n.key}: ${n.changed_fields.join(", ")}${n.changed_fields.includes("value")?` \u2192 ${JSON.stringify(n.after?.value)}`:""}${s}`)}for(let n of t.issues)e.out.line(` ! ${n.key?`${n.key}: `:""}${n.message}`)}async function Wi(e,t,n,s){return g(e,t,"POST","/flags/templates/publish",s===""?{}:{note:s},{...Re(n),...C()})}var Td={name:"flags:publish",summary:"show the flags draft's changes against the live version, then publish it as the next version",usage:"[-m <note>]",flags:{note:{type:"string",short:"m",value:"note",description:"why: kept with the version"}},scope:"environment",async run(e){let t=await f(e);if(!await sn(e,t))throw Ji();if(!(await nn(e,t,!1)).draft)throw new y("no_draft","there is no draft to publish",{hint:"palbase flags:set puts a change into one"});let s=await qe(e,t,"/flags/templates/draft/review");if(e.out.json||t_(e,s.body),!s.body.can_publish)throw new y("cannot_publish",`the draft cannot be published: ${s.body.issues.map(r=>r.message).join("; ")}`);if(s.body.changes.length===0){e.out.result({unchanged:!0,review:s.body},()=>e.out.line("Nothing to publish."));return}await v(e,t,`publish the flags draft (${s.body.changes.length} change(s)) as the version after ${s.body.active_version}`);let a=await Wi(e,t,s.etag,e.flags.note??"");e.out.result({...a,review:s.body},()=>e.out.line(a.unchanged?"The draft changed nothing; nothing was published.":`Published version ${a.template.version}.`))}},Ed={name:"flags:publishing:enable",summary:"turn publishing on for the environment's flags: changes go into a shared draft, live once published \u2014 for good",details:["What is live now becomes version 1. From then on flags:set and flags:delete change the draft, flags:publish makes it live as the next version, and flags:rollback brings an earlier one back. It cannot be turned off."],scope:"environment",async run(e){let t=await f(e);if(await sn(e,t)){e.out.result({publishing:!0,changed:!1},()=>e.out.line("The environment's flags are published through drafts already."));return}await v(e,t,"turn publishing on for the flags \u2014 what is live becomes version 1, and it cannot be turned off");let n=await g(e,t,"POST","/flags/templates/activation",{});e.out.result({publishing:!0,changed:!0,version:n.version},()=>e.out.line(`Publishing is on: what was live is version ${n.version}; flags:set changes the draft, flags:publish makes it live.`))}},Cd={name:"flags:versions",summary:"the published versions of the flags, newest first, and which one is live",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await f(e);if(!await sn(e,t))throw Ji();let n=Number(e.flags.limit??20);if(!Number.isInteger(n)||n<=0)throw m("--limit is a whole number");let s=await g(e,t,"GET",`/flags/templates/versions?limit=${n}`);e.out.result(s,()=>e.out.table(s.versions,[{header:"",value:a=>a.version===s.active_version?"*":""},{header:"version",value:a=>a.version},{header:"kind",value:a=>`${a.kind}${a.restored_from?` of ${a.restored_from}`:""}`},{header:"published",value:a=>a.created_at},{header:"by",value:a=>a.actor},{header:"note",value:a=>a.note}]))}},jd={name:"flags:rollback",summary:"make an earlier version of the flags live again (published as the next version)",usage:"<version> [-m <note>]",args:{min:1,max:1},flags:{note:{type:"string",short:"m",value:"note",description:"why (default: rollback to <version>)"}},scope:"environment",async run(e){let t=await f(e),n=e.args[0];if(!await sn(e,t))throw Ji();if((await nn(e,t,!1)).draft)throw new y("draft_in_progress","a draft is in progress: publish it (flags:publish) before a rollback");let s=await qe(e,t,"/flags/templates/active");await v(e,t,`make version ${n} of the flags live again (now ${s.body.version})`);let a=await Ne(e,t,"POST",`/flags/templates/restore/${k(n)}`,{},Re(s.etag)),r=await Wi(e,t,a.etag,e.flags.note??`rollback to ${n}`);e.out.result(r,()=>e.out.line(r.unchanged?`Version ${n} is what is live already.`:`Version ${n} is live again, as version ${r.template.version}.`))}},Od={name:"flags",async read(e,t){let n=await g(e,t,"GET","/flags/system"),s={};for(let a of n)s[`flags.${a.key}`]=Sd(a);return s},async copy(e,t,n,s){let a=pe(n,"flags.");if(a.length===0)return;if(!await sn(e,t)){for(let i of a)i.from===void 0?await V(s,`delete the flag ${i.name}`,async()=>{await g(e,t,"DELETE",`/flags/system/${k(i.name)}`)}):await V(s,`set the flag ${i.name}`,async()=>{await g(e,t,"PUT",`/flags/system/${k(i.name)}`,i.from)});return}if((await nn(e,t,!1)).draft){s.left("flags: the target has a draft in progress \u2014 publish it (flags:publish) and copy again");return}let r=(await nn(e,t,!0)).etag;for(let i of a)await V(s,`${i.from===void 0?"take":"put"} the flag ${i.name} ${i.from===void 0?"out of":"into"} the draft`,async()=>{let o=`/flags/templates/draft/parameters/${k(i.name)}`;r=(i.from===void 0?await Ne(e,t,"DELETE",o,void 0,Re(r)):await Ne(e,t,"PUT",o,i.from,Re(r))).etag??r});await V(s,"publish the flags draft",async()=>{await Wi(e,t,r,"config:copy")})}};var Md=["email","sms","verify","whatsapp","push"],Vi=["email","sms","whatsapp"],xd={name:"notify:providers:list",summary:"the environment's own providers, by channel and the order they are tried (never their credentials)",scope:"environment",async run(e){let t=await f(e),{providers:n}=await g(e,t,"GET","/notify/providers");e.out.result({providers:n},()=>{n.length===0&&e.out.line("No provider of the environment's own: email goes through the platform's sender; texts need one (notify:providers:set)."),e.out.table(n,[{header:"channel",value:s=>s.channel},{header:"provider",value:s=>s.provider},{header:"on",value:s=>s.active?"yes":"no"},{header:"priority",value:s=>String(s.priority)},{header:"credentials",value:()=>"set"}])})}},Id={name:"notify:providers:set",summary:"bring a provider for a channel: its credentials are piped in as JSON, checked as the sender will use them, never shown",usage:"<channel> <provider> [--priority <n>] [--enable|--disable] < credentials.json",details:["email: acs {connection_string, from_email}, ses {region, access_key_id, secret_access_key, from_domain}, sendgrid {api_key, from_domain}, smtp {host, port, username, password, from_email, use_starttls}.","sms: twilio {account_sid, auth_token, from_number}, vonage {api_key, api_secret, from}, acs {connection_string, from_number}. verify: twilio_verify {account_sid, auth_token, service_sid}.","whatsapp: meta {phone_number_id, access_token, api_version, app_secret, verify_token}. push: fcm (the service account JSON), apns {private_key, team_id, key_id, bundle_id, is_production}, expo {access_token}.","The credentials are read from stdin only: never from the command line, where the shell would keep them."],args:{min:2,max:2},flags:{priority:{type:"string",value:"n",description:"lower is tried first"},...Gs},scope:"environment",async run(e){let[t,n]=e.args;if(!Md.includes(t))throw m(`the channels: ${Md.join(", ")}`);let s=await f(e),a=ue(await Me(e,`${n}'s credentials (JSON)`),"the credentials");if(typeof a!="object"||a===null||Array.isArray(a))throw m("the credentials are a JSON object");let r=Xn(e),i=te(e.flags.priority,"priority");await v(e,s,`send ${t} through ${n}`);let o=await g(e,s,"PUT",`/notify/providers/${k(t)}/${k(n)}`,{credentials:a,...r!==void 0?{active:r}:{},...i!==void 0?{priority:i}:{}},C());e.out.result(o,()=>e.out.line(`${o.channel} goes through ${o.provider} (${o.active?"on":"off"}, priority ${o.priority}); its credentials are kept sealed.`))}},Ld={name:"notify:providers:remove",summary:"forget a provider and its credentials",usage:"<channel> <provider>",args:{min:2,max:2},scope:"environment",async run(e){let[t,n]=e.args,s=await f(e);await v(e,s,`forget the ${t} provider ${n} and its credentials`),await g(e,s,"DELETE",`/notify/providers/${k(t)}/${k(n)}`,void 0,C()),e.out.result({removed:{channel:t,provider:n}},()=>e.out.line(`${t} no longer goes through ${n}.`))}},Yi={channel:{type:"string",value:"channel",description:"email (default), sms or whatsapp"},locale:{type:"string",value:"locale",description:"the template's locale: en, tr, pt-BR"}};function Ys(e){let t=e.flags.channel??"email";if(!Vi.includes(t))throw m(`--channel is one of ${Vi.join(", ")}`);return t}function Xi(e,t){let n=e.flags.locale;if(n===void 0)throw m("which locale? pass --locale <locale>");return`/notify/templates/${Ys(e)}/${k(t)}/${k(n)}`}function n_(e,t){if(e==="sms"){let s=s_(t);return typeof s=="object"&&s!==null&&typeof s.body=="string"?{body:s.body}:{body:t.replace(/\n$/,"")}}let n=ue(t,`the ${e} template`);if(e==="email"){for(let s of["subject","html_body","text_body"])if(typeof n[s]!="string")throw m(`an email template is {"subject", "html_body", "text_body"}: ${s} is missing`);return{subject:n.subject,html_body:n.html_body,text_body:n.text_body}}if(typeof n.name!="string"||typeof n.language!="string")throw m('a WhatsApp template is {"name", "language", "variables", "code_button"}: the template approved on your Meta account');return{name:n.name,language:n.language,...n.variables!==void 0?{variables:n.variables}:{},...n.code_button!==void 0?{code_button:n.code_button}:{}}}function s_(e){try{return JSON.parse(e)}catch{return e}}var Dd={name:"notify:templates:list",summary:"the templates messages render from: the platform's defaults and the ones the environment overrides",usage:"[--channel email|sms|whatsapp]",flags:{channel:{type:"string",value:"channel",description:"only this channel's (default: all three)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.channel===void 0?Vi:[Ys(e)],s=await Promise.all(n.map(r=>g(e,t,"GET",`/notify/templates/${r}`))),a=n.flatMap((r,i)=>s[i].templates.map(o=>({channel:r,...o})));e.out.result({templates:a},()=>e.out.table(a,[{header:"channel",value:r=>r.channel},{header:"slug",value:r=>r.slug},{header:"locale",value:r=>r.locale},{header:"yours",value:r=>"customized"in r?r.customized?"yes":"default":"yes"},{header:"subject / text / template",value:r=>"subject"in r?r.subject:"body"in r?r.body.split(`
30
30
  `)[0].slice(0,60):`${r.name} (${r.language})`}]))}},Nd={name:"notify:templates:get",summary:"a template as it renders now in a locale (yours, or the platform's default); --json gives the document templates:set takes",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:Yi,scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",Xi(e,e.args[0]));e.out.result(n,()=>{e.out.line(`${String(n.slug)} (${String(n.locale)}) \u2014 ${n.customized===!1?"the platform's default":"yours"}`),Array.isArray(n.variables)&&n.variables.length>0&&e.out.line(`variables: ${n.variables.join(", ")}`);for(let s of["subject","name","language"])typeof n[s]=="string"&&e.out.line(`${s}: ${String(n[s])}`);for(let s of["text_body","body","html_body"])if(typeof n[s]=="string"){e.out.line(),e.out.line(`\u2500\u2500 ${s} \u2500\u2500`);for(let a of n[s].split(`
31
- `))e.out.line(a)}})}},qd={name:"notify:templates:set",summary:"override a template in a locale, from a file or stdin (email: {subject, html_body, text_body} as JSON)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp] [--file <path>]",details:['email: {"subject", "html_body", "text_body"}; sms: the text, or {"body"}; whatsapp: {"name", "language", "variables", "code_button"} of a template approved on your Meta account.',"notify:templates:get \u2026 --json > t.json gives a document to edit and set back. Handlebars: {{name}}, {{#if}}, {{#each}}."],args:{min:1,max:1},flags:{...Yi,file:{type:"string",value:"path",description:"the template (default: stdin)"}},scope:"environment",async run(e){let t=await f(e),n=Ys(e),s=Xi(e,e.args[0]),a=n_(n,await be(e,e.flags.file,"the template"));await v(e,t,`override the ${n} template ${e.args[0]} (${e.flags.locale})`);let r=await g(e,t,"PUT",s,a,C());e.out.result(r,()=>e.out.line(`The ${n} template ${e.args[0]} (${e.flags.locale}) is yours now${typeof r.subject=="string"?`: "${r.subject}"`:""}.`))}},Kd={name:"notify:templates:delete",summary:"remove an override: the platform's default renders again (a WhatsApp mapping is forgotten)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:Yi,scope:"environment",async run(e){let t=await f(e),n=Xi(e,e.args[0]);await v(e,t,`remove the ${Ys(e)} template ${e.args[0]} (${e.flags.locale})`),await g(e,t,"DELETE",n,void 0,C()),e.out.result({deleted:{channel:Ys(e),slug:e.args[0],locale:e.flags.locale}},()=>e.out.line(`Removed the override of ${e.args[0]} (${e.flags.locale}).`))}};async function Hi(e,t){let n=[],s;do{let a=await g(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${k(s)}`:""}`);n.push(...a.topics),s=a.next_cursor}while(s);return n}var Gd={name:"notify:topics:list",summary:"the topics users follow, how many follow each, whether they follow it from the app",scope:"environment",async run(e){let t=await f(e),n=await Hi(e,t);e.out.result({topics:n},()=>{n.length===0&&e.out.line("No topic: palbase notify:topics:create <name> makes one."),e.out.table(n,[{header:"topic",value:s=>s.name},{header:"followers",value:s=>String(s.subscribers)},{header:"from the app",value:s=>s.self_subscribe?"yes":"no: the project adds them"}])})}},Ud={name:"notify:topics:create",summary:"make a topic users can follow; a push to it reaches its followers",usage:"<name> [--self-subscribe yes|no]",args:{min:1,max:1},flags:{"self-subscribe":{type:"string",value:"yes|no",description:"users follow it from the app (default no: the project adds them)"}},scope:"environment",async run(e){let t=await f(e),n=en(e.flags["self-subscribe"],"self-subscribe");await v(e,t,`make the topic ${e.args[0]}`);let s=await g(e,t,"POST","/notify/topics",{name:e.args[0],...n!==void 0?{self_subscribe:n}:{}},C());e.out.result(s,()=>e.out.line(`Made the topic ${s.name}${s.self_subscribe?", followed from the app":""}.`))}},zd={name:"notify:topics:delete",summary:"delete a topic and who follows it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`delete the topic ${e.args[0]} and who follows it`),await g(e,t,"DELETE",`/notify/topics/${k(e.args[0])}`,void 0,C()),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the topic ${e.args[0]}.`))}},Bd={name:"notify:settings:get",summary:"the environment's message settings: the default locale, who email is from, where texts may go",scope:"environment",async run(e){let t=await f(e),{body:n}=await qe(e,t,"/notify/settings");e.out.result(n,()=>Yn(e,n))}},Fd={name:"notify:settings:set",summary:'change message settings: key=value (from_email=\u2026, sms_countries=["+90","+44"], mx_check=true)',usage:"<key=value>\u2026",details:["The change is made on the revision read: one made beside it in the meantime is refused, not overwritten."],args:{min:1,max:20},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args),{body:s,etag:a}=await qe(e,t,"/notify/settings"),r=Qt(s,n),i=Dt(s,r);if(i.length===0)return ze(e,"the message settings",s);await v(e,t,`set the message settings ${i.join(", ")}`);let o=await g(e,t,"PUT","/notify/settings",r,{...Re(a),...C()});e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${Te(s[l])} \u2192 ${Te(o[l])}`)})}},a_=new Set(["sms_countries","sms_blocked_countries"]);function r_(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Jd={name:"notify",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([g(e,t,"GET","/notify/settings"),g(e,t,"GET","/notify/providers"),g(e,t,"GET","/notify/templates/email"),g(e,t,"GET","/notify/templates/sms"),g(e,t,"GET","/notify/templates/whatsapp"),Hi(e,t)]),l={};for(let[u,h]of Object.entries(r_(n)))l[`settings.${u}`]=h;for(let u of s.providers)l[`providers.${u.channel}/${u.provider}`]={active:u.active,priority:u.priority,credentials:"set"};let d=a.templates.filter(u=>u.customized),p=await Promise.all(d.map(u=>g(e,t,"GET",`/notify/templates/email/${k(u.slug)}/${k(u.locale)}`)));for(let u of p)l[`templates.email/${u.slug}/${u.locale}`]={subject:u.subject,html_body:u.html_body,text_body:u.text_body};for(let u of r.templates.filter(h=>h.customized))l[`templates.sms/${u.slug}/${u.locale}`]={body:u.body};for(let u of i.templates)l[`templates.whatsapp/${u.slug}/${u.locale}`]={name:u.name,language:u.language,variables:u.variables,code_button:u.code_button};for(let u of o)l[`topics.${u.name}`]={self_subscribe:u.self_subscribe};return l},async copy(e,t,n,s){let a=pe(n,"settings.");if(a.length>0){let{body:o,etag:l}=await qe(e,t,"/notify/settings"),d={...o};for(let p of a)d[p.name]=a_.has(p.name)&&Array.isArray(p.from)&&p.from.length===0?null:p.from;await V(s,`set the message settings ${a.map(p=>p.name).join(", ")}`,async()=>{await g(e,t,"PUT","/notify/settings",d,{...Re(l),...C()})})}for(let o of pe(n,"providers.")){let[l,d]=o.name.split("/");o.from===void 0?s.left(`the ${l} provider ${d}: only in the target, left as it is (it holds credentials)`):s.left(`the ${l} provider ${d}: credentials are not copied \u2014 set them with palbase notify:providers:set ${l} ${d} < credentials.json`)}for(let o of pe(n,"templates.")){let l=`/notify/templates/${o.name.split("/").map(k).join("/")}`;o.from===void 0?await V(s,`remove the template override ${o.name}`,async()=>{await g(e,t,"DELETE",l,void 0,C())}):await V(s,`override the template ${o.name}`,async()=>{await g(e,t,"PUT",l,o.from,C())})}let r=pe(n,"topics."),i=r.some(o=>o.from===void 0)?new Map((await Hi(e,t)).map(o=>[o.name,o.subscribers])):new Map;for(let o of r){let l=`/notify/topics/${k(o.name)}`,d=o.from?.self_subscribe;if(o.from===void 0){let p=i.get(o.name)??0;p>0?s.left(`the topic ${o.name}: only in the target, and ${p} users follow it \u2014 delete it with notify:topics:delete`):await V(s,`delete the topic ${o.name}`,async()=>{await g(e,t,"DELETE",l,void 0,C())})}else o.change==="only_in_from"?await V(s,`make the topic ${o.name}`,async()=>{await g(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:d},C())}):await V(s,`set the topic ${o.name} ${d?"followed from the app":"followed by the project's adding"}`,async()=>{await g(e,t,"PATCH",l,{self_subscribe:d},C())})}}};var es=["max_connections","max_events_per_second","max_joins_per_second","max_channels_per_connection"];function i_(e){if(e.kind==="owner")return"the user it names: everything";let t=[e.publish?"publish":"",e.state_read?"read state":"",e.state_write?"write state":""].filter(n=>n!=="");return`any signed-in user: subscribe${t.length>0?`, ${t.join(", ")}`:""}`}function Zi(e){return Object.fromEntries(es.map(t=>[t,e[t]??null]))}var Wd={name:"realtime:channels:get",summary:"the channel declaration, in the order patterns are matched: who may join each, publish, read and write its state",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/realtime/channels");e.out.result(n,()=>{n.channels.length===0&&e.out.line("No channel is declared: a channel no pattern matches belongs to no one."),e.out.table(n.channels,[{header:"pattern",value:s=>s.pattern},{header:"kind",value:s=>s.kind},{header:"who",value:s=>i_(s)}])})}},Vd={name:"realtime:channels:set",summary:'replace the channel declaration with a JSON file or stdin ({"channels": [\u2026]} or the list)',usage:"[--file <path>]",details:['Each channel: {"pattern": "room:{id}", "kind": "public", "publish": true, "state_read": true, "state_write": false}, or {"pattern": "user:{uid}", "kind": "owner"}.',"realtime:channels:get --json gives the document to edit. Open sockets are decided again within a heartbeat."],flags:{file:{type:"string",value:"path",description:"the declaration (default: stdin)"}},scope:"environment",async run(e){let t=await f(e),n=ue(await be(e,e.flags.file,"the channel declaration"),"the channel declaration"),s=Array.isArray(n)?n:n.channels;if(!Array.isArray(s))throw m('the declaration is {"channels": [\u2026]} or a list of channels');let a=await g(e,t,"GET","/realtime/channels");if(Oe(a.channels,s))return ze(e,"the channel declaration",a);await v(e,t,`declare ${s.length} channel pattern(s) (now ${a.channels.length})`);let r=await g(e,t,"PUT","/realtime/channels",{channels:s});e.out.result(r,()=>e.out.line(r.channels.length===0?"No channel is declared now.":`Declared ${r.channels.length} channel pattern(s): ${r.channels.map(i=>i.pattern).join(", ")}.`))}},Hd={name:"realtime:limits:get",summary:"the limits the environment holds its clients to (null: the platform's), and the platform's",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/realtime/limits");e.out.result(n,()=>{let s=n.platform??{},a=Math.max(...es.map(r=>r.length));for(let r of es)e.out.line(`${r.padEnd(a)} ${n[r]===void 0||n[r]===null?"the platform's":Te(n[r])}${s[r]!==void 0?` (platform ${s[r]===0?"no ceiling":Te(s[r])})`:""}`)})}},Yd={name:"realtime:limits:set",summary:"change realtime limits: key=value (max_connections=500), null for the platform's",usage:"<key=value>\u2026",details:[`The keys: ${es.join(", ")}. A value above the platform's has no effect beyond it.`],args:{min:1,max:4},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args);for(let o of n)if(o.path.length!==1||!es.includes(o.path[0]))throw m(`no limit ${JSON.stringify(o.path.join("."))}`,`the limits: ${es.join(", ")}`);let s=Zi(await g(e,t,"GET","/realtime/limits")),a=Qt(s,n),r=Dt(s,a);if(r.length===0)return ze(e,"the realtime limits",s);await v(e,t,`set the realtime limits ${r.join(", ")}`);let i=await g(e,t,"PUT","/realtime/limits",a);e.out.result({limits:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${Te(s[o]??"the platform's")} \u2192 ${Te(i[o]??"the platform's")}`)})}},Xd={name:"realtime",async read(e,t){let[n,s]=await Promise.all([g(e,t,"GET","/realtime/channels"),g(e,t,"GET","/realtime/limits")]),a={channels:n.channels};for(let[r,i]of Object.entries(Zi(s)))a[`limits.${r}`]=i;return a},async copy(e,t,n,s){let a=n.find(i=>i.key==="channels");a&&await V(s,"declare the channels",async()=>{await g(e,t,"PUT","/realtime/channels",{channels:a.from??[]})});let r=pe(n,"limits.");if(r.length>0){let i=Zi(await g(e,t,"GET","/realtime/limits"));for(let o of r)i[o.name]=o.from??null;await V(s,`set the realtime limits ${r.map(o=>o.name).join(", ")}`,async()=>{await g(e,t,"PUT","/realtime/limits",i)})}}},Zd={name:"analytics:settings:get",summary:"how long analytics keeps events and console records, the share of performance samples kept, whether bodies are kept",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/analytics/settings");e.out.result(n,()=>Yn(e,n))}},Qd={name:"analytics:settings:set",summary:"change analytics settings: key=value (retention_days=30, perf_sample_pct=10, console_bodies=false)",usage:"<key=value>\u2026",details:["A shorter retention removes what is older within minutes."],args:{min:1,max:4},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args),s=await g(e,t,"GET","/analytics/settings"),a=Qt(s,n),r=Dt(s,a);if(r.length===0)return ze(e,"the analytics settings",s);await v(e,t,`set the analytics settings ${r.join(", ")}`);let i=await g(e,t,"PUT","/analytics/settings",a);e.out.result({settings:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${Te(s[o])} \u2192 ${Te(i[o])}`)})}},eu={name:"analytics",async read(e,t){let n=await g(e,t,"GET","/analytics/settings");return Object.fromEntries(Object.entries(n).map(([s,a])=>[`settings.${s}`,a]))},async copy(e,t,n,s){let a=pe(n,"settings.");if(a.length===0)return;let r=await g(e,t,"GET","/analytics/settings");for(let i of a)i.from!==void 0&&(r[i.name]=i.from);await V(s,`set the analytics settings ${a.map(i=>i.name).join(", ")}`,async()=>{await g(e,t,"PUT","/analytics/settings",r)})}};function o_(e){return{source:{repository:e.source.repository.toLowerCase(),branch:e.source.branch,path:e.source.path??"",context:e.source.context??""},size:e.size,replicas:e.replicas,port:e.port,health_path:e.health_path??"",env:e.env??{},secrets:e.secrets??"",database:e.database?{name:e.database.name,migrations:e.database.migrations===!0}:null,deploy_after_checks:e.deploy_after_checks===!0,deploy_checks:[...e.deploy_checks??[]].sort(),deploy_checks_app:e.deploy_checks_app??"github-actions"}}var tu={name:"machines",async read(e,t){let{machines:n}=await g(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.filter(s=>!s.preview_of).map(s=>[`machines.${s.name}`,o_(s)]))},async copy(e,t,n,s){for(let a of pe(n,"machines.")){if(a.change==="only_in_to"){s.left(`the machine ${a.name}: only the target has it \u2014 it serves on (palbase machines:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await V(s,`opened the machine ${a.name}, built from ${r.source.branch}'s head`,()=>g(e,t,"POST","/machines",{name:a.name,...r,database:r.database??void 0},C()));continue}let i=a.to,o=Object.fromEntries(Object.keys(r).filter(l=>!Oe(r[l],i[l])).map(l=>[l,l==="database"&&r[l]===null?{name:""}:r[l]]));await V(s,`changed the machine ${a.name}: ${Object.keys(o).join(", ")} (a new version rolls out beside the one serving)`,()=>g(e,t,"PATCH",`/machines/${k(a.name)}`,o))}}};function l_(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function p_(e,t,n,s){let a=await g(e,t,"POST","/databases",{name:n,...s},C());await e.state.keepDatabasePasswords(t.env_ref,a.name,{owner:a.credentials.owner.password,app:a.credentials.app.password})}var nu={name:"databases",async read(e,t){let{databases:n}=await g(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.filter(s=>s.size!=="preview").map(s=>[`databases.${s.name}`,l_(s)]))},async copy(e,t,n,s){for(let a of pe(n,"databases.")){if(a.change==="only_in_to"){s.left(`the database ${a.name}: only the target has it \u2014 its data stays (palbase databases:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await V(s,`opened the database ${a.name} (${r.size}; its passwords kept on this computer \u2014 palbase db:copy brings its data)`,()=>p_(e,t,a.name,r));continue}let i=a.to,o=Object.fromEntries(["size","allow","backup_days"].filter(d=>!Oe(r[d],i[d])).map(d=>[d,r[d]]));if(Object.keys(o).length===0)continue;let l=o.size===void 0?"":` (moving to ${o.size}, serving on)`;await V(s,`changed the database ${a.name}: ${Object.keys(o).join(", ")}${l}`,()=>g(e,t,"PATCH",`/databases/${k(a.name)}`,o))}}};var tt="/messaging/settings",d_=/^[A-Z][A-Z0-9_]{0,127}$/,eo=["retention_days","push_preview","deleted_user_messages","search_by_meaning","event_text"];function ts(e,t){switch(e){case"retention_days":return t===null?"off: messages are kept for good":`${String(t)} days: older messages are removed`;case"push_preview":return t===!0?"on: a push says what the message says":"off: a push says only that a message came";case"deleted_user_messages":return t==="delete"?"delete: a deleted user's messages go with them":"keep: a deleted user's messages stay, their writer anonymous";case"search_by_meaning":return t===!0?"on: messages are found by what they mean too, each embedded by the AI module (billed as AI use)":"off: messages are found by their words alone";case"event_text":return t===!0?"on: messaging.message.created says what the message says":"off: messaging.message.created says who wrote to whom, not what (the backend reads it with the secret key)";case"hook":return t?su(t):"none: anyone may reach anyone"}}function su(e){return`${A(e.url)} (waited for ${e.timeout_ms} ms; when it does not answer, ${e.on_error==="allow"?"everyone is allowed":"no one is: the app is told to try again"})`}function u_(e,t){let n=[...eo.map(a=>[a,t[a]]),["hook",t.hook]],s=Math.max(...n.map(([a])=>a.length));for(let[a,r]of n)e.out.line(`${a.padEnd(s)} ${ts(a,r)}`)}function Qi(e,t){if(e!==void 0){if(e==="on")return!0;if(e==="off")return!1;throw m(`--${t} is on or off`)}}function c_(e){if(e===void 0)return;if(e==="off")return null;let t=Number(e);if(!/^[0-9]+$/.test(String(e))||t<1||t>36500)throw m("--retention-days is a whole number of days from 1 to 36500, or off (kept for good)");return t}var g_={name:"messaging:settings:get",summary:"how the environment runs its conversations: how long messages are kept, whether a push says what one says, what becomes of a deleted user's messages, whether they are found by what they mean, whether a message's event says what it says, the hook",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",tt);e.out.result(n,()=>u_(e,n))}},h_={name:"messaging:settings:set",summary:"change how the environment runs its conversations: a setting left out is left as it is",usage:"[--retention-days <days>|off] [--push-preview on|off] [--deleted-user-messages keep|delete] [--search-by-meaning on|off] [--event-text on|off]",details:["A shorter retention removes the messages older than it, in every conversation. A deleted user's messages are kept or deleted as the setting is when the user is deleted.","Deleted, what of theirs had already gone from screens (seen once, its time up, vanish mode, taken back) stays its 14 days, sealed, for reports, then goes.","Search by meaning on: the app's search({ by: 'meaning' }) finds the messages nearest what a question means, no word of it needed. Each message's text (not a system message's, one seen a limited number of times, nor one of vanish mode) is embedded by the project's embedding model through the AI module and billed as the project's AI use; the messages written before are embedded in the background (seconds to minutes), a new one is found ~5 s after it is sent. Off: no message goes to a model, and the vectors are removed.","Event text on: messaging.message.created \u2014 told to the backend's webhooks and @On handlers that name it \u2014 carries the message's text (not a vanish-mode message's, one seen a limited number of times, nor one that goes after a time). The words leave the sealed store and stay on the event stream 7 days. Off: the backend reads the message with the secret key.","Only what changes is sent: a change made beside this one to another setting stays."],flags:{"retention-days":{type:"string",value:"days|off",description:"messages older than this many days are removed (1\u201336500); off: kept for good"},"push-preview":{type:"string",value:"on|off",description:"a push says what the message says (on), or only that one came (off)"},"deleted-user-messages":{type:"string",value:"keep|delete",description:"a deleted user's messages: kept, their writer anonymous, or deleted (what went from screens before stays its 14 days for reports)"},"search-by-meaning":{type:"string",value:"on|off",description:"messages are found by what they mean too (on: each embedded through the AI module, billed as AI use), or by their words alone (off)"},"event-text":{type:"string",value:"on|off",description:"messaging.message.created says what the message says (on), or who wrote to whom alone (off)"}},scope:"environment",async run(e){let t={},n=c_(e.flags["retention-days"]);n!==void 0&&(t.retention_days=n);let s=Qi(e.flags["push-preview"],"push-preview");s!==void 0&&(t.push_preview=s);let a=e.flags["deleted-user-messages"];if(a!==void 0){if(a!=="keep"&&a!=="delete")throw m("--deleted-user-messages is keep or delete");t.deleted_user_messages=a}let r=Qi(e.flags["search-by-meaning"],"search-by-meaning");r!==void 0&&(t.search_by_meaning=r);let i=Qi(e.flags["event-text"],"event-text");if(i!==void 0&&(t.event_text=i),Object.keys(t).length===0)throw m("nothing to change","pass --retention-days, --push-preview, --deleted-user-messages, --search-by-meaning or --event-text");let o=await f(e),l=await g(e,o,"GET",tt),d=eo.filter(u=>u in t&&!Oe(l[u],t[u]));if(d.length===0)return ze(e,"the messaging settings",l);await v(e,o,`set the messaging settings ${d.join(", ")}`);let p=await g(e,o,"PUT",tt,Object.fromEntries(d.map(u=>[u,t[u]])));e.out.result({settings:p,changed:d},()=>{for(let u of d)e.out.line(`${u}: ${ts(u,l[u])} \u2192 ${ts(u,p[u])}`)})}},au={"secret-to":{type:"string",value:"NAME",description:"keep the new signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}};function ru(e){let t=e.flags["secret-to"];if(t!==void 0&&!d_.test(t))throw m(`--secret-to ${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&t===void 0)throw m("--dev goes with --secret-to <NAME>");return t===void 0?void 0:{name:t,set:e.flags.dev===!0?"development":"production"}}async function iu(e,t,n,s,a){let{hook_secret:r,...i}=n;if(r===void 0){e.out.result({settings:i},()=>e.out.line(`${a}: ${ts("hook",i.hook)}.`));return}if(s!==void 0){let o=await g(e,t,"PUT",`/secrets/${s.set}/${s.name}`,{value:r});e.out.result({settings:i,secret_to:{set:s.set,name:s.name,version:o.version}},()=>e.out.line(`${a}: ${ts("hook",i.hook)}; its signing secret is ${s.name} (${s.set}, v${o.version}) in ${N(t)}.`));return}e.out.result(n,()=>{e.out.line(`${a}: ${ts("hook",i.hook)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}var m_={name:"messaging:hook:get",summary:"the hook asked whom one may reach: where it is, how long it is waited for, what is done when it does not answer (never its secret)",scope:"environment",async run(e){let t=await f(e),{hook:n}=await g(e,t,"GET",tt);e.out.result({hook:n},()=>e.out.line(n?su(n):"No hook: anyone may reach anyone. palbase messaging:hook:set --url <url> sets one."))}},f_={name:"messaging:hook:set",summary:"set the hook asked whom one may reach, or change it; a new hook's signing secret is printed this once, or kept in a secret with --secret-to",usage:"[--url <url>] [--timeout-ms <ms>] [--on-error allow|deny] [--secret-to <NAME> [--dev]]",details:[`Before a direct conversation opens, people are added to a group, someone makes or opens an open group, a space's open groups are listed or one is joined, the hook is POSTed {"event": "messaging.can_message", "action": "direct" | "group_add" | "open_group" | "list_open" | "join", "from_user_id", "to_user_ids", "conversation_id", "space"}, signed as Standard Webhooks sign (webhook-id, webhook-timestamp, webhook-signature), and answers 200 {"allowed_user_ids": [\u2026]}: whom of to_user_ids from_user_id may reach \u2014 for open_group, list_open and join, to_user_ids is [from_user_id], and naming them lets them in. space is the conversation's (absent: none).`,"A new hook takes --url (an https URL on the public internet). Its signing secret is printed this once, or kept in the environment's secrets with --secret-to (production; --dev: development) and printed nowhere. A hook changed keeps its secret: palbase messaging:hook:secret:rotate makes a new one."],flags:{url:{type:"string",value:"url",description:"where the hook is asked"},"timeout-ms":{type:"string",value:"ms",description:"how long it is waited for: 100 to 5000 (a new hook: 2000)"},"on-error":{type:"string",value:"allow|deny",description:"when it does not answer: allow everyone, or deny and the app is told to try again (a new hook: deny)"},...au},scope:"environment",async run(e){let t=e.flags.url;if(t!==void 0&&t.trim()==="")throw m("--url is where the hook is asked: an https URL");let n;if(e.flags["timeout-ms"]!==void 0&&(n=Number(e.flags["timeout-ms"]),!/^[0-9]+$/.test(String(e.flags["timeout-ms"]))||n<100||n>5e3))throw m("--timeout-ms is a whole number from 100 to 5000");let s=e.flags["on-error"];if(s!==void 0&&s!=="allow"&&s!=="deny")throw m("--on-error is allow or deny");if(t===void 0&&n===void 0&&s===void 0)throw m("nothing to change","pass --url, --timeout-ms or --on-error");let a=ru(e),r=await f(e),i=await g(e,r,"GET",tt),o=i.hook;if(!o&&t===void 0)throw m("the environment has no hook yet: --url names where it is asked");if(o&&a!==void 0)throw m("the hook has its signing secret, and a change keeps it: --secret-to goes with a new hook","palbase messaging:hook:secret:rotate --secret-to <NAME> makes a new secret and keeps it");let l={url:t??o.url},d=n??o?.timeout_ms;d!==void 0&&(l.timeout_ms=d);let p=s??o?.on_error;if(p!==void 0&&(l.on_error=p),o&&Oe(o,{...o,...l}))return ze(e,"the hook",i);let u=o?["url","timeout_ms","on_error"].filter(b=>l[b]!==o[b]):[];await v(e,r,o?`change the messaging hook: ${u.join(", ")}`:`set the messaging hook ${l.url}${a?`, its secret kept in ${a.name} (${a.set})`:""}`);let h=await g(e,r,"PUT",tt,{hook:l});await iu(e,r,h,a,o?"Changed the hook":"Set the hook")}},y_={name:"messaging:hook:remove",summary:"remove the hook: anyone may reach anyone, and its signing secret is forgotten",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",tt);if(!n.hook){e.out.result({settings:n,changed:[]},()=>e.out.line("Nothing to change: the environment has no hook."));return}await v(e,t,`remove the messaging hook ${n.hook.url}: anyone may reach anyone, and its signing secret is forgotten`);let s=await g(e,t,"PUT",tt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may reach anyone."))}},b_={name:"messaging:hook:secret:rotate",summary:"make the hook's signing secret anew: printed this once, or kept in a secret with --secret-to; the old one signs nothing more",usage:"[--secret-to <NAME> [--dev]]",details:["The hook's calls are signed with the new secret alone from now: the endpoint verifies them with it (a machine reads a secret kept with --secret-to at its next start)."],flags:au,scope:"environment",async run(e){let t=ru(e),n=await f(e);await v(e,n,`make the messaging hook's signing secret anew; the old one signs nothing more${t?`; the new one kept in ${t.name} (${t.set})`:""}`);let s=await g(e,n,"POST",`${tt}/hook-secret`);await iu(e,n,s,t,"Made the signing secret anew for the hook")}},ou={name:"messaging",async read(e,t){let n=await g(e,t,"GET",tt),s={};for(let a of eo)s[`settings.${a}`]=n[a];return n.hook&&(s.hook={url:n.hook.url,timeout_ms:n.hook.timeout_ms,on_error:n.hook.on_error}),s},async copy(e,t,n,s){let a=pe(n,"settings.");a.length>0&&await V(s,`set the messaging settings ${a.map(l=>l.name).join(", ")}`,async()=>{await g(e,t,"PUT",tt,Object.fromEntries(a.map(l=>[l.name,l.from])))});let r=n.find(l=>l.key==="hook");if(!r)return;if(r.from===void 0){await V(s,`remove the hook ${r.to.url}`,async()=>{await g(e,t,"PUT",tt,{hook:null})});return}let i=r.from,o;await V(s,`${r.change==="differs"?"change":"set"} the hook ${i.url}`,async()=>{o=(await g(e,t,"PUT",tt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},lu=[g_,h_,m_,f_,y_,b_];var nt="/calls/settings",w_=/^[A-Z][A-Z0-9_]{0,127}$/,no=["max_participants","calls_per_hour"];function ns(e,t){switch(e){case"max_participants":return`${String(t)}: a call holds at most this many, people and bots together`;case"calls_per_hour":return`${String(t)}: one person may start at most this many calls in an hour`;case"hook":return t?pu(t):"none: anyone may call anyone (blocks aside)"}}function pu(e){return`${A(e.url)} (waited for ${e.timeout_ms??2e3} ms; when it does not answer, ${e.on_error==="allow"?"everyone is allowed":"no one is: the call is refused"})`}function v_(e,t){let n=[...no.map(a=>[a,t[a]]),["hook",t.hook]],s=Math.max(...n.map(([a])=>a.length));for(let[a,r]of n)e.out.line(`${a.padEnd(s)} ${ns(a,r)}`)}function to(e,t,n,s){if(e===void 0)return;let a=Number(e);if(!/^[0-9]+$/.test(String(e))||a<n||a>s)throw m(`--${t} is a whole number from ${n} to ${s}`);return a}var __={name:"calls:settings:get",summary:"how the environment runs its calls: how many a call holds, how many calls a person may start in an hour, the hook",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",nt);e.out.result(n,()=>v_(e,n))}},k_={name:"calls:settings:set",summary:"change how the environment runs its calls: a setting left out is left as it is",usage:"[--max-participants <n>] [--calls-per-hour <n>]",details:["A call holds at most --max-participants, people and the project's bots together, as the setting was when the call was made: a change holds for the calls made after it.","Every call's media, one to one or in a group, goes through the platform's media server: no device learns another's address.","A person who has started --calls-per-hour calls in the last hour is refused another (too_many_calls).","Only what changes is sent: a change made beside this one to another setting stays."],flags:{"max-participants":{type:"string",value:"n",description:"how many a call holds, people and bots together (2\u2013100)"},"calls-per-hour":{type:"string",value:"n",description:"how many calls one person may start in an hour (1\u20131000)"}},scope:"environment",async run(e){let t={},n=to(e.flags["max-participants"],"max-participants",2,100);n!==void 0&&(t.max_participants=n);let s=to(e.flags["calls-per-hour"],"calls-per-hour",1,1e3);if(s!==void 0&&(t.calls_per_hour=s),Object.keys(t).length===0)throw m("nothing to change","pass --max-participants or --calls-per-hour");let a=await f(e),r=await g(e,a,"GET",nt),i=no.filter(l=>l in t&&!Oe(r[l],t[l]));if(i.length===0)return ze(e,"the call settings",r);await v(e,a,`set the call settings ${i.join(", ")}`);let o=await g(e,a,"PUT",nt,Object.fromEntries(i.map(l=>[l,t[l]])));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${ns(l,r[l])} \u2192 ${ns(l,o[l])}`)})}},du={"secret-to":{type:"string",value:"NAME",description:"keep the new signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}};function uu(e){let t=e.flags["secret-to"];if(t!==void 0&&!w_.test(t))throw m(`--secret-to ${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&t===void 0)throw m("--dev goes with --secret-to <NAME>");return t===void 0?void 0:{name:t,set:e.flags.dev===!0?"development":"production"}}async function cu(e,t,n,s,a){let{hook_secret:r,...i}=n;if(r===void 0){e.out.result({settings:i},()=>e.out.line(`${a}: ${ns("hook",i.hook)}.`));return}if(s!==void 0){let o=await g(e,t,"PUT",`/secrets/${s.set}/${s.name}`,{value:r});e.out.result({settings:i,secret_to:{set:s.set,name:s.name,version:o.version}},()=>e.out.line(`${a}: ${ns("hook",i.hook)}; its signing secret is ${s.name} (${s.set}, v${o.version}) in ${N(t)}.`));return}e.out.result(n,()=>{e.out.line(`${a}: ${ns("hook",i.hook)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}var S_={name:"calls:hook:get",summary:"the hook asked whom one may call: where it is, how long it is waited for, what is done when it does not answer (never its secret)",scope:"environment",async run(e){let t=await f(e),{hook:n}=await g(e,t,"GET",nt);e.out.result({hook:n},()=>e.out.line(n?pu(n):"No hook: anyone may call anyone (blocks aside). palbase calls:hook:set --url <url> sets one."))}},$_={name:"calls:hook:set",summary:"set the hook asked whom one may call, or change it; a new hook's signing secret is printed this once, or kept in a secret with --secret-to",usage:"[--url <url>] [--timeout-ms <ms>] [--on-error allow|deny] [--secret-to <NAME> [--dev]]",details:['Before a call is made, or more people are asked into one, the hook is POSTed {"event": "calls.can_call", "action": "start" | "invite", "from_user_id", "to_user_ids", "video", "call_id", "conversation_id"}, signed as Standard Webhooks sign (webhook-id, webhook-timestamp, webhook-signature), and answers 200 {"allowed_user_ids": [\u2026]}: only those are called, and with none the call is refused.',"A new hook takes --url (a URL on the public internet). Its signing secret is printed this once, or kept in the environment's secrets with --secret-to (production; --dev: development) and printed nowhere. A hook changed keeps its secret: palbase calls:hook:secret:rotate makes a new one."],flags:{url:{type:"string",value:"url",description:"where the hook is asked"},"timeout-ms":{type:"string",value:"ms",description:"how long it is waited for: 100 to 5000 (a new hook: 2000)"},"on-error":{type:"string",value:"allow|deny",description:"when it does not answer: allow everyone, or deny and the call is refused (a new hook: deny)"},...du},scope:"environment",async run(e){let t=e.flags.url;if(t!==void 0&&t.trim()==="")throw m("--url is where the hook is asked: a URL");let n=to(e.flags["timeout-ms"],"timeout-ms",100,5e3),s=e.flags["on-error"];if(s!==void 0&&s!=="allow"&&s!=="deny")throw m("--on-error is allow or deny");if(t===void 0&&n===void 0&&s===void 0)throw m("nothing to change","pass --url, --timeout-ms or --on-error");let a=uu(e),r=await f(e),i=await g(e,r,"GET",nt),o=i.hook;if(!o&&t===void 0)throw m("the environment has no hook yet: --url names where it is asked");if(o&&a!==void 0)throw m("the hook has its signing secret, and a change keeps it: --secret-to goes with a new hook","palbase calls:hook:secret:rotate --secret-to <NAME> makes a new secret and keeps it");let l={url:t??o.url},d=n??o?.timeout_ms;d!==void 0&&(l.timeout_ms=d);let p=s??o?.on_error;if(p!==void 0&&(l.on_error=p),o&&Oe(o,{...o,...l}))return ze(e,"the hook",i);let u=o?["url","timeout_ms","on_error"].filter(b=>l[b]!==o[b]):[];await v(e,r,o?`change the calls hook: ${u.join(", ")}`:`set the calls hook ${l.url}${a?`, its secret kept in ${a.name} (${a.set})`:""}`);let h=await g(e,r,"PUT",nt,{hook:l});await cu(e,r,h,a,o?"Changed the hook":"Set the hook")}},P_={name:"calls:hook:remove",summary:"remove the hook: anyone may call anyone (blocks aside), and its signing secret is forgotten",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",nt);if(!n.hook){e.out.result({settings:n,changed:[]},()=>e.out.line("Nothing to change: the environment has no hook."));return}await v(e,t,`remove the calls hook ${n.hook.url}: anyone may call anyone, and its signing secret is forgotten`);let s=await g(e,t,"PUT",nt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may call anyone (blocks aside)."))}},A_={name:"calls:hook:secret:rotate",summary:"make the hook's signing secret anew: printed this once, or kept in a secret with --secret-to; the old one signs nothing more",usage:"[--secret-to <NAME> [--dev]]",details:["The hook's calls are signed with the new secret alone from now: the endpoint verifies them with it (a machine reads a secret kept with --secret-to at its next start)."],flags:du,scope:"environment",async run(e){let t=uu(e),n=await f(e);await v(e,n,`make the calls hook's signing secret anew; the old one signs nothing more${t?`; the new one kept in ${t.name} (${t.set})`:""}`);let s=await g(e,n,"POST",`${nt}/hook-secret`);await cu(e,n,s,t,"Made the signing secret anew for the hook")}},gu={name:"calls",async read(e,t){let n=await g(e,t,"GET",nt),s={};for(let a of no)s[`settings.${a}`]=n[a];return n.hook&&(s.hook={url:n.hook.url,timeout_ms:n.hook.timeout_ms,on_error:n.hook.on_error}),s},async copy(e,t,n,s){let a=pe(n,"settings.");a.length>0&&await V(s,`set the call settings ${a.map(l=>l.name).join(", ")}`,async()=>{await g(e,t,"PUT",nt,Object.fromEntries(a.map(l=>[l.name,l.from])))});let r=n.find(l=>l.key==="hook");if(!r)return;if(r.from===void 0){await V(s,`remove the hook ${r.to.url}`,async()=>{await g(e,t,"PUT",nt,{hook:null})});return}let i=r.from,o;await V(s,`${r.change==="differs"?"change":"set"} the hook ${i.url}`,async()=>{o=(await g(e,t,"PUT",nt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},hu=[__,k_,S_,$_,P_,A_];var R_=/^[a-z][a-z0-9-]{1,62}$/;function ss(e){if(!R_.test(e))throw m(`${JSON.stringify(e)} is not a bucket's name: lowercase letters, digits and dashes, starting with a letter (2\u201363)`);return`/storage/buckets/${k(e)}`}function ao(e){if(e==null)return"platform (5 GiB)";for(let[t,n]of[["GiB",2**30],["MiB",2**20],["KiB",2**10]])if(e>=n&&e%n===0)return`${e/n} ${t}`;return`${e} B`}function T_(e){if(e===""||e==="platform")return null;let t=/^(\d+(?:\.\d+)?)\s*(b|kb|kib|mb|mib|gb|gib)?$/i.exec(e.trim());if(!t)throw m(`--max-size ${JSON.stringify(e)} is not a size: 10MB, 512KiB, 1GB, a number of bytes, or platform`);let n=(t[2]??"b").toLowerCase(),s={b:1,kb:1e3,kib:2**10,mb:1e6,mib:2**20,gb:1e9,gib:2**30};return Math.round(Number(t[1])*s[n])}function so(e){return{public:e.public,file_size_limit:e.file_size_limit??null,allowed_mime_types:e.allowed_mime_types??[],variants:e.variants??[]}}async function E_(e,t,n){try{return await g(e,t,"GET",ss(n))}catch(s){if(s instanceof j&&s.status===404)return;throw s}}var mu={name:"storage:buckets:list",summary:"the environment's buckets: who may read each, the largest file and the types it takes",scope:"environment",async run(e){let t=await f(e),{buckets:n}=await g(e,t,"GET","/storage/buckets");e.out.result({buckets:n},()=>{n.length===0&&e.out.line("No bucket yet: palbase storage:buckets:set <name> makes one."),e.out.table(n,[{header:"bucket",value:s=>s.name},{header:"read by",value:s=>s.public?"anyone":"signed URLs, rules"},{header:"largest file",value:s=>ao(s.file_size_limit)},{header:"types",value:s=>s.allowed_mime_types.length===0?"any":s.allowed_mime_types.join(", ")},{header:"renditions",value:s=>s.variants.map(a=>a.name).join(", ")}])})}},fu={name:"storage:buckets:get",summary:"a bucket's declaration in full, with its renditions",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",ss(e.args[0]));e.out.result(n,()=>{e.out.line(n.name),e.out.line(` read by ${n.public?"anyone, at /storage/public/":"signed URLs and the rules"}`),e.out.line(` largest file ${ao(n.file_size_limit)}`),e.out.line(` types ${n.allowed_mime_types.length===0?"any":n.allowed_mime_types.join(", ")}`);for(let s of n.variants)e.out.line(` rendition ${s.name}: ${s.width}\xD7${s.height} ${s.fit}, ${s.format}${s.quality?` q${s.quality}`:""}`)})}},yu={name:"storage:buckets:set",summary:"make a bucket, or change one: public or private, the largest file, the content types, its renditions",usage:"<bucket> [--public|--private] [--max-size <size>] [--types <a/b,c/*>] [--variants-file <path>]",details:["What is not given is kept as it is (a new bucket: private, the platform's size, any type, no rendition)."],args:{min:1,max:1},flags:{public:{type:"boolean",description:"anyone may read its files at /storage/public/"},private:{type:"boolean",description:"its files are read with signed URLs and the rules"},"max-size":{type:"string",value:"size",description:"the largest file: 10MB, 512KiB, 1GB, bytes, or platform"},types:{type:"string",value:"list",description:'the content types it takes, comma-separated (image/* a family); "" for any'},"variants-file":{type:"string",value:"path",description:"its renditions, a JSON list of {name, width, height, fit, format, quality}"}},scope:"environment",async run(e){let t=await f(e),n=e.args[0];if(e.flags.public===!0&&e.flags.private===!0)throw m("--public and --private are one or the other");let s=await E_(e,t,n),a=so(s??{name:n,public:!1,allowed_mime_types:[],variants:[]});if(e.flags.public===!0&&(a.public=!0),e.flags.private===!0&&(a.public=!1),e.flags["max-size"]!==void 0&&(a.file_size_limit=T_(e.flags["max-size"])),e.flags.types!==void 0&&(a.allowed_mime_types=re(e.flags.types)??[]),e.flags["variants-file"]!==void 0){let i=ue(await be(e,e.flags["variants-file"],"the renditions"),"the renditions file"),o=Array.isArray(i)?i:i.variants;if(!Array.isArray(o))throw m("the renditions file is a JSON list of {name, width, height, fit, format, quality}");a.variants=o}if(s&&Oe(so(s),a)){e.out.result(s,()=>e.out.line(`Nothing to change: ${n} already is so.`));return}await v(e,t,`${s?"change":"make"} the bucket ${n} (${a.public?"public":"private"})`);let r=await g(e,t,"PUT",ss(n),a);e.out.result(r,()=>e.out.line(`${s?"Changed":"Made"} the bucket ${r.name}: ${r.public?"public":"private"}, files up to ${ao(r.file_size_limit)}, ${r.allowed_mime_types.length===0?"any type":r.allowed_mime_types.join(", ")}.`))}},bu={name:"storage:buckets:delete",summary:"delete an empty bucket (one that still holds files is refused)",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=ss(e.args[0]);await v(e,t,`delete the bucket ${e.args[0]}`),await g(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},wu={name:"storage",async read(e,t){let{buckets:n}=await g(e,t,"GET","/storage/buckets"),s={};for(let a of n){let r=so(a);s[`buckets.${a.name}`]={...r,allowed_mime_types:[...r.allowed_mime_types].sort()}}return s},async copy(e,t,n,s){for(let a of pe(n,"buckets."))a.from===void 0?await V(s,`delete the bucket ${a.name}`,async()=>{await g(e,t,"DELETE",ss(a.name))}):await V(s,`declare the bucket ${a.name}`,async()=>{await g(e,t,"PUT",ss(a.name),a.from)})}};var as=[_d,wu,Od,Jd,Xd,eu,ou,gu],ir=[nu,tu],vu={from:{type:"string",value:"env",description:"the environment compared from (slug or ref)"},to:{type:"string",value:"env",description:"the environment compared to (slug or ref)"},only:{type:"string",value:"modules",description:`only these, comma-separated: ${as.map(e=>e.name).join(",")} (the default), and ${ir.map(e=>e.name).join(",")} when named`}};async function _u(e){if(e.global.env!==void 0)throw m("the two environments are named with --from and --to, not -E");let t=e.flags.from,n=e.flags.to;if(t===void 0||n===void 0)throw m("name both environments: --from <env> --to <env>");let s=await De(e.studio),a;if(e.global.project!==void 0)a=_n(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new y("no_target","which project? none is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});a=s.filter(d=>d.project_id===l.target.project_id)}let r=l=>{let d=a.find(p=>p.slug===l||p.ref===l);if(!d)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(l)}`,{hint:`its environments: ${a.map(p=>p.slug).join(", ")}`});return $e(d)},i=r(t),o=r(n);if(i.env_ref===o.env_ref)throw m("--from and --to name the same environment");return{from:i,to:o}}function ku(e){let t=re(e.flags.only);if(t===void 0)return as;let n=[...as,...ir];for(let s of t)if(!n.some(a=>a.name===s))throw m(`no module ${JSON.stringify(s)}`,`the modules: ${n.map(a=>a.name).join(", ")}`);return n.filter(s=>t.includes(s.name))}function C_(e,t){let n=[...new Set([...Object.keys(e),...Object.keys(t)])].sort(),s=[];for(let a of n){let r=a in e,i=a in t;r&&!i?s.push({key:a,change:"only_in_from",from:e[a]}):!r&&i?s.push({key:a,change:"only_in_to",to:t[a]}):Oe(e[a],t[a])||s.push({key:a,change:"differs",from:e[a],to:t[a]})}return s}async function ro(e,t,n,s){let a=l=>Promise.all(t.map(d=>d.read(e,l))),[r,i]=await Promise.all([a(n),a(s)]),o=Object.fromEntries(t.map((l,d)=>[l.name,C_(r[d],i[d])]));return{from:n,to:s,modules:o,count:Object.values(o).reduce((l,d)=>l+d.length,0)}}function io(e){return{from:{env:e.from.env_slug,ref:e.from.env_ref},to:{env:e.to.env_slug,ref:e.to.env_ref},equal:e.count===0,differences:e.count,modules:e.modules}}function rr(e){let t=JSON.stringify(e)??"undefined";return t.length>140?`${t.slice(0,139)}\u2026`:t}function oo(e,t){let n=t.from.env_slug,s=t.to.env_slug;for(let[r,i]of Object.entries(t.modules))if(i.length!==0){e.out.line(`${r} (${i.length})`);for(let o of i)o.change==="differs"?e.out.line(` ~ ${o.key}: ${n} ${rr(o.from)} \xB7 ${s} ${rr(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${rr(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${rr(o.to)}`)}let a=Object.keys(t.modules).join(", ");e.out.line(t.count===0?`${n} and ${s} have the same settings (${a}).`:`${t.count} difference(s) between ${n} and ${s} (${a}).`)}var j_={name:"config:diff",summary:"how two environments' module settings differ (auth, storage, flags, notify, realtime, analytics, messaging, calls; machines, databases when named)",usage:"--from <env> --to <env> [--only <modules>]",details:["- a key only in --from, + only in --to, ~ in both with other values. Ids, times and versions are not compared; a secret is compared as set or not, never by its value.","Exit code 0 when they are the same, 1 when they differ. --json gives each difference with both values."],flags:vu,scope:"environment",async run(e){let{from:t,to:n}=await _u(e),s=await ro(e,ku(e),t,n);return e.out.result(io(s),()=>oo(e,s)),s.count===0?0:1}},O_={name:"config:copy",summary:"make one environment's module settings equal to another's (secrets excepted: their names are said)",usage:"--from <env> --to <env> [--only <modules>]",details:["It shows the differences, asks before changing a production environment (-y confirms), changes the target, and compares again.","A secret is never copied: a provider's credentials and a sign-in client's secret are listed by name for you to set. A new hook or OAuth client gets its own secret, printed this once.","What the target holds that its users depend on is not taken by a copy: a bucket with files, a role users hold, a topic users follow are left and said.","--only machines,databases copies what they are declared as \u2014 a machine's source, size, copies, settings, secrets' name, database; a database's size, who may connect, backups \u2014 never a database's data (palbase db:copy) nor a secret's value. A machine or database only the target has is left serving.","Exit code 0 when the two are the same afterwards, 1 when a difference remains."],flags:vu,scope:"environment",async run(e){let{from:t,to:n}=await _u(e);return lo(e,ku(e),t,n)}};async function lo(e,t,n,s,a={}){let r=await ro(e,t,n,s);if(r.count===0)return e.out.result({...a,copied:[],left:[],shown_once:[],after:io(r)},()=>e.out.line(`Nothing to copy: ${n.env_slug} and ${s.env_slug} have the same settings.`)),0;e.out.json||oo(e,r);let i=t.filter(u=>r.modules[u.name].length>0).map(u=>u.name);await v(e,s,`make its ${i.join(", ")} settings equal to ${n.env_slug}'s (${r.count} difference(s))`);let o=[],l=[],d=[];e.out.json||e.out.line();for(let u of t){let h=r.modules[u.name];if(h.length===0)continue;let b={done(w){o.push(`${u.name}: ${w}`),e.out.json||e.out.line(`copied ${u.name}: ${w}`)},left(w){l.push(`${u.name}: ${w}`),e.out.json||e.out.line(`left ${u.name}: ${w}`)},shown(w,_){d.push({what:`${u.name}: ${w}`,value:_}),e.out.json||e.out.line(` ${u.name}: ${w} ${_} (shown this once)`)}};await u.copy(e,s,h,b)}let p=await ro(e,t,n,s);return e.out.result({...a,copied:o,left:l,shown_once:d,after:io(p)},()=>{e.out.line(),e.out.line("Compared again:"),oo(e,p)}),p.count===0?0:1}var Su=[ad,rd,id,od,pd,ld,dd,ud,cd,hd,md,fd,yd,bd,wd,vd,mu,fu,yu,bu,Pd,Ad,Rd,Td,Cd,jd,Ed,xd,Id,Ld,Dd,Nd,qd,Kd,Gd,Ud,zd,Bd,Fd,Wd,Vd,Hd,Yd,Zd,Qd,...lu,...hu,j_,O_];async function po(e){return(await e.studio.get("/orgs")).organizations}async function mt(e,t){let n=await po(e);if(t===void 0){if(n.length===1)return n[0];throw m(n.length===0?"you are in no organization yet: make one with palbase orgs:create <name>":"which organization? pass --org <name>",`yours: ${n.map(a=>a.name).join(", ")}`)}let s=n.filter(a=>a.id===t||a.name.localeCompare(t,void 0,{sensitivity:"accent"})===0);if(s.length===0)throw new y("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new y("ambiguous_org",`${JSON.stringify(t)} names more than one of your organizations`,{hint:"pass its id"});return s[0]}var $u={name:"orgs:list",summary:"your organizations and your role in each",scope:"account",async run(e){let t=await po(e);e.out.result({organizations:t},()=>{t.length===0&&e.out.line("You are in no organization yet: palbase orgs:create <name> makes one."),e.out.table(t,[{header:"name",value:n=>n.name},{header:"role",value:n=>n.role},{header:"id",value:n=>n.id}])})}},Pu={name:"orgs:create",summary:"make an organization; you are its owner",usage:"<name>",details:["With an account token in PALBASE_TOKEN, the token's maker is its owner and the token its admin: one organization more the token reaches."],args:{min:1,max:1},scope:"account",async run(e){let t=await e.studio.post("/orgs",{name:e.args[0]});e.out.result(t,()=>e.out.line(`Made the organization ${K(t.name)} (${t.id}).`))}},Au={name:"orgs:members:list",summary:"an organization's members, and the invitations still open (for its admins)",flags:{org:{type:"string",value:"org",description:"the organization (name or id); needed when you are in more than one"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=await e.studio.get(`/orgs/${t.id}/members`);e.out.result(n,()=>{e.out.table(n.members,[{header:"email",value:s=>s.email??""},{header:"role",value:s=>s.role},{header:"user",value:s=>s.user_id}]),n.invitations.length>0&&(e.out.line(),e.out.line("Invited:"),e.out.table(n.invitations,[{header:"email",value:s=>s.email},{header:"role",value:s=>s.role},{header:"invitation",value:s=>s.id}]))})}},Ru={name:"orgs:members:invite",summary:"invite someone to an organization by email; they accept in Studio",usage:"<email> [--role admin|member]",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"admin or member (default member)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--role is admin or member");let s=await e.studio.post(`/orgs/${t.id}/invitations`,{email:e.args[0],role:n});e.out.result(s,()=>e.out.line(`Invited ${K(s.email)} to ${K(t.name)} as ${s.role}.`))}},Tu={name:"orgs:members:remove",summary:"take a member (by email or user id) or an open invitation out of an organization",usage:"<email|user-id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.args[0],s=await e.studio.get(`/orgs/${t.id}/members`),a=s.members.find(i=>i.user_id===n||i.email!==null&&i.email.toLowerCase()===n.toLowerCase());if(a){await e.studio.request("DELETE",`/orgs/${D("the organization's id",t.id)}/members/${D("the member's id",a.user_id)}`),e.out.result({removed:a},()=>e.out.line(`${K(a.email??a.user_id)} is no longer in ${K(t.name)}.`));return}let r=s.invitations.find(i=>i.id===n||i.email.toLowerCase()===n.toLowerCase());if(!r)throw new y("no_such_member",`${JSON.stringify(n)} is neither a member of ${t.name} nor invited to it`);await e.studio.request("DELETE",`/orgs/${D("the organization's id",t.id)}/invitations/${D("the invitation's id",r.id)}`),e.out.result({withdrawn:r},()=>e.out.line(`The invitation of ${K(r.email)} is withdrawn.`))}},Eu={name:"tokens:list",summary:"an organization's access tokens (PALBASE_TOKEN for CI and agents): name, role, when they end and were last used \u2014 never their values",flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`);e.out.result({tokens:n},()=>{n.length===0?e.out.line(`${K(t.name)} has no access token yet: palbase tokens:create <name> makes one.`):e.out.table(n,[{header:"name",value:s=>K(s.name)},{header:"role",value:s=>s.role},{header:"token",value:s=>`pbo_\u2026${s.hint}`},{header:"ends",value:s=>s.expires_at.slice(0,10)},{header:"last used",value:s=>s.last_used_at?.slice(0,16).replace("T"," ")??"never"},{header:"id",value:s=>s.id}])})}},Cu={name:"orgs:audit",summary:"who changed what at an organization's environments, through Studio or the CLI, newest first \u2014 its admins'",usage:"[--org <org>] [--limit <n>]",details:["A module keeps a change made through Studio as Studio's key's: this says whose it was. What was sent is not kept."],flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.limit===void 0?50:Number(e.flags.limit);if(!Number.isInteger(n)||n<1||n>1e4)throw m("--limit is a whole number from 1 to 10000");let s=[],a=null;do{let r=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{before:a}:{}}),i=await e.studio.get(`/orgs/${t.id}/audit?${r}`);s.push(...i.entries),a=i.next_before}while(a&&s.length<n);e.out.result({entries:s},()=>{s.length===0?e.out.line(`Nothing changed at ${K(t.name)}'s environments yet.`):e.out.table(s,[{header:"when",value:r=>r.at.slice(0,16).replace("T"," ")},{header:"who",value:r=>K(r.actor_name??r.actor)},{header:"environment",value:r=>K(r.environment_ref)},{header:"what",value:r=>K(`${r.method} ${r.path}`)},{header:"answer",value:r=>String(r.status)}])})}},ju={name:"tokens:create",summary:"make an access token of an organization for CI or an agent; its value is shown this once",usage:"<name> [--role member|admin] [--days <n>]",details:["Give it to the CI as the secret PALBASE_TOKEN: the CLI acts with it in place of a sign-in. It reaches its organization's projects and environments at its role, and nothing of its people or tokens.","It ends after --days (default 90, at most 365), and goes when it is revoked or when the one who made it leaves the organization."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"member (default) or admin: what it may change"},days:{type:"string",value:"n",description:"how many days it is good for (default 90)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--role is member or admin");let s=e.flags.days===void 0?90:Number(e.flags.days);if(!Number.isInteger(s)||s<1||s>365)throw m("--days is a whole number from 1 to 365");let a=await e.studio.post(`/orgs/${t.id}/tokens`,{name:e.args[0],role:n,expires_in_days:s});e.out.result(a,()=>{e.out.line(`Made the access token ${K(a.name)} of ${K(t.name)} (${a.role}, until ${a.expires_at.slice(0,10)}).`),e.out.line(`token (shown this once \u2014 keep it as the CI's secret PALBASE_TOKEN): ${a.token}`)})}},Ou={name:"tokens:revoke",summary:"revoke an organization's access token (by its name or id): refused from its next request on",usage:"<name|id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`),s=n.filter(i=>i.id===e.args[0]||i.name===e.args[0]);if(s.length===0)throw new y("no_such_token",`${K(t.name)} has no access token ${JSON.stringify(e.args[0])}`,{hint:"palbase tokens:list shows them"});if(s.length>1)throw new y("ambiguous_token",`${JSON.stringify(e.args[0])} names more than one token`,{hint:"pass its id"});let a=`/orgs/${D("the organization's id",t.id)}/tokens/${D("the token's id",s[0].id)}`,{endpoints:r}=await e.studio.get(`${a}/webhooks`);await e.studio.request("DELETE",a),e.out.result({revoked:s[0].id,kept_webhooks:r},()=>{if(e.out.line(`Revoked the access token ${K(s[0].name)} of ${K(t.name)}.`),r.length>0){e.out.line("These webhook endpoints it made stay, and keep receiving events (palbase webhooks:delete <id> -E <env> removes one):");for(let i of r)e.out.line(` ${A(i.url)} \u2014 ${A(i.environment_ref)} (${A(i.id)})`)}})}},Mu={name:"projects:list",summary:"every project of your organizations, with its environments",scope:"account",async run(e){let t=await De(e.studio),n=await e.state.targetFor(e.io.cwd),s=(await e.studio.get("/projects")).projects,a=new Map(t.map(r=>[r.organization_id,r.org_name]));for(let r of await po(e))a.set(r.id,r.name);e.out.result({projects:s.map(r=>({...r,organization_name:a.get(r.organization_id)??null}))},()=>{s.length===0&&e.out.line("No project yet: palbase projects:create <name> makes one.");let r=s.flatMap(i=>i.environments.length===0?[{org:a.get(i.organization_id)??"",project:i.name,env:"(none)",ref:"",here:""}]:i.environments.map(o=>({org:a.get(i.organization_id)??"",project:i.name,env:o.slug,ref:o.ref,here:n?.target.env_ref===o.ref?"*":""})));e.out.table(r,[{header:"",value:i=>i.here},{header:"organization",value:i=>i.org},{header:"project",value:i=>i.project},{header:"env",value:i=>i.env},{header:"ref",value:i=>i.ref}])})}},xu={name:"projects:create",summary:"make a project with its first environment (main), and print its keys",usage:"<name> [--org <org>] [--env-name <name>]",details:["The first environment is the project's production one: its slug is always main.","Its secret key is printed this once; keys:create makes more."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},"env-name":{type:"string",value:"name",description:"what the first environment is called on screen (default Production)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{project:n}=await e.studio.post(`/orgs/${t.id}/projects`,{name:e.args[0]}),s=await e.studio.post(`/projects/${n.id}/environments`,{name:e.flags["env-name"]??"Production"});e.out.result({project:n,environment:s.environment,keys:s.keys},()=>{e.out.line(`Made ${K(t.name)} \u203A ${K(n.name)} with its environment ${s.environment.slug} (ref ${s.environment.ref}).`),s.keys&&(e.out.line(`publishable key: ${s.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${s.keys.secret.key}`)),e.out.line(`palbase use ${n.name}/${s.environment.slug} chooses it for this directory.`)})}},Iu={name:"projects:delete",summary:"delete a project and every environment of it",usage:"<project> [-y]",details:["Its environments' data goes with them (the platform keeps it a week to bring back)."],args:{min:1,max:1},scope:"account",async run(e){let t=_n(await De(e.studio),e.args[0]),n=t[0];if(e.out.note(`${n.org_name} \u203A ${n.project_name}: delete the project and its environments ${t.map(s=>s.slug).join(", ")}`),!e.global.yes){if(!e.interactive)throw new y("confirmation_needed","deleting a project is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the project's name to delete it: ")).trim()!==n.project_name)throw new y("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/projects/${n.project_id}`),e.out.result({deleted:{project_id:n.project_id,environments:t.map(s=>s.ref)}},()=>e.out.line(`Deleted ${K(n.project_name)}.`))}},Lu={name:"envs:list",summary:"a project's environments (the project chosen here, or -P)",scope:"environment",async run(e){let t=await qt(e);e.out.result({environments:t},()=>e.out.table(t,[{header:"slug",value:n=>n.slug},{header:"name",value:n=>n.name},{header:"ref",value:n=>n.ref},{header:"your role",value:n=>n.role}]))}},Du={name:"envs:create",summary:"add an environment to a project (staging, a developer's own)",usage:"<slug> [--name <name>] [--from <env> [--with-cloud]]",details:["Each environment is a project of its own on the platform: its users, data, machines and keys are its own.","--from opens it with another environment's module settings (palbase config:copy: secrets are named, not copied); --with-cloud its machines and databases too \u2014 declared alike, built from the same branches, with no data (palbase db:copy brings it)."],args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called on screen (default: the slug)"},from:{type:"string",value:"env",description:"open it with this environment's settings (slug or ref)"},"with-cloud":{type:"boolean",description:"with --from: its machines and databases too (the new environment's GitHub link builds the machines)"}},scope:"environment",async run(e){let t=await M_(e),n=e.args[0],s=e.flags.from;if(e.flags["with-cloud"]===!0&&s===void 0)throw m("--with-cloud goes with --from <env>");let a=s===void 0?[]:(await De(e.studio)).filter(d=>d.project_id===t.project_id),r=s===void 0?void 0:a.find(d=>d.slug===s||d.ref===s);if(s!==void 0&&!r)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(s)}`,{hint:`its environments: ${a.map(d=>d.slug).join(", ")}`});let i=await e.studio.post(`/projects/${t.project_id}/environments`,{name:e.flags.name??n,slug:n}),o=()=>{e.out.line(`Made ${K(t.project_name)} \u203A ${i.environment.slug} (ref ${i.environment.ref}).`),i.keys&&(e.out.line(`publishable key: ${i.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${i.keys.secret.key}`))};if(!r){e.out.result(i,o);return}e.out.json||o();let l=$e({...r,ref:i.environment.ref,slug:i.environment.slug,name:i.environment.name});return e.out.json||e.out.line(),lo(e,e.flags["with-cloud"]===!0?[...as,...ir]:as,$e(r),l,{made:i})}},Nu={name:"envs:delete",summary:"delete an environment of a project, with its data",usage:"<slug>",args:{min:1,max:1},scope:"environment",async run(e){let n=(await qt(e)).find(r=>r.slug===e.args[0]||r.ref===e.args[0]);if(!n)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=$e(n);if(e.out.note(`${N(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new y("confirmation_needed","deleting an environment is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the environment's slug to delete it: ")).trim()!==n.slug)throw new y("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/environments/${n.ref}`);let a=await e.state.targetFor(e.io.cwd);a?.target.env_ref===n.ref&&await e.state.clearTarget(a.dir),e.out.result({deleted:n.ref},()=>e.out.line(`Deleted ${N(s)}.`))}},qu={name:"use",summary:"choose the environment this directory acts on (kept on this computer)",usage:"[<project>[/<env>] | <org>/<project>/<env>] [--clear]",details:["Commands run in this directory, or a directory under it, then act on that environment unless -P/-E say otherwise.","Nothing is written into the project: two checkouts of it can each act on their own environment.","With no argument and a person at the terminal, it lists your environments to pick from."],args:{min:0,max:1},flags:{clear:{type:"boolean",description:"forget the choice made for this directory"}},scope:"account",async run(e){if(e.flags.clear===!0){let n=await e.state.clearTarget(e.io.cwd);e.out.result({cleared:n},()=>e.out.line(n?"This directory acts on no environment now.":"No environment was chosen for this directory."));return}let t;if(e.args.length===0)t=await f(e);else{let n=await De(e.studio);t=$e(x_(n,e.args[0]))}await e.state.setTarget(e.io.cwd,t),e.out.result({target:t,dir:e.io.cwd},()=>e.out.line(`This directory acts on ${N(t)}.`))}},Ku={name:"status",summary:"the environment this directory acts on: who you are there, its machines and databases",scope:"environment",async run(e){let t=await f(e),n=await e.studio.get(`/environments/${t.env_ref}`),s=d=>ye(e.studio,t.env_ref,"GET",d).then(p=>p.body,p=>p instanceof j?null:Promise.reject(p)),[a,r]=await Promise.all([s("/machines?limit=100"),s("/databases?limit=100")]),i=a?.machines??[],o=r?.databases??[],l=await e.state.targetFor(e.io.cwd);e.out.result({target:t,role:n.role,address:`https://${t.env_ref}.${e.cloud.domain}`,machines:i,databases:o,chosen_for:l?.dir??null},()=>{e.out.line(N(t)),e.out.line(` ref ${t.env_ref} \xB7 your role ${n.role} \xB7 https://${t.env_ref}.${e.cloud.domain}`),l&&l.target.env_ref===t.env_ref&&e.out.line(` chosen for ${l.dir}`),e.out.line(` machines: ${i.length===0?"none":i.map(d=>`${d.name} (${d.size}, ${d.state})`).join(", ")}`),e.out.line(` databases: ${o.length===0?"none":o.map(d=>`${d.name} (${d.size}, ${d.state})`).join(", ")}`)})}},Gu={name:"open",summary:"open the environment in Studio (its overview, or a section: auth, storage, flags, \u2026)",usage:"[section]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0]?`/${e.args[0].replace(/^\/+/,"")}`:"",s=`${e.cloud.studioWeb}/projects/${t.project_id}/environments/${t.env_ref}${n}`,a=e.interactive&&await e.io.openBrowser(s);e.out.result({url:s,opened:a},()=>e.out.line(a?`Opened ${s}`:s))}},Uu={name:"keys:list",summary:"the environment's API keys (never their values)",scope:"environment",async run(e){let t=await f(e),{keys:n}=await e.studio.get(`/environments/${t.env_ref}/keys`);e.out.result({keys:n},()=>e.out.table(n,[{header:"id",value:s=>s.id},{header:"kind",value:s=>s.kind},{header:"label",value:s=>s.label},{header:"ends",value:s=>s.hint?`\u2026${s.hint}`:""},{header:"state",value:s=>s.revoked_at?`revoked ${s.revoked_at}`:s.expires_at?`until ${s.expires_at}`:"active"},{header:"created",value:s=>s.created_at}]))}},zu={name:"keys:create",summary:"make an API key of the environment; its value is shown this once",usage:"--kind publishable|secret [--label <label>]",flags:{kind:{type:"string",value:"kind",description:"publishable (ships in apps) or secret (servers only)"},label:{type:"string",value:"label",description:"what it is for, as Studio lists it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!=="publishable"&&t!=="secret")throw m("--kind is publishable or secret");let n=await f(e);await v(e,n,`make a ${t} key`);let s=await e.studio.post(`/environments/${n.env_ref}/keys`,{kind:t,label:e.flags.label??""});e.out.result(s,()=>{e.out.line(`${s.kind} key ${s.id}${s.kind==="secret"?" (shown this once)":""}:`),e.out.line(s.key)})}},Bu={name:"keys:revoke",summary:"revoke an API key of the environment; what holds it is refused from then on",usage:"<key-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=`/environments/${D("the environment's ref",t.env_ref)}/keys/${D("the key's id",e.args[0])}`;await v(e,t,`revoke the key ${e.args[0]}`),await e.studio.request("DELETE",n),e.out.result({revoked:e.args[0]},()=>e.out.line(`Revoked ${e.args[0]}.`))}};async function M_(e){return(await qt(e))[0]}async function qt(e){if(vn(e.io))return[await Ci(e)];let t=await De(e.studio);if(e.global.project!==void 0)return _n(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new y("no_target","no project is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});let s=t.filter(a=>a.project_id===n.target.project_id);if(s.length===0)throw new y("no_such_project",`the project chosen here (${n.target.project_name}) is not among yours any more`,{hint:"palbase use another"});return s}function x_(e,t){let n=t.split("/");if(n.length>3||n.some(r=>r===""))throw m(`${JSON.stringify(t)} is not project, project/env or org/project/env`);let s=n.length===3?[{project:`${n[0]}/${n[1]}`,env:n[2]}]:n.length===2?[{project:n[0],env:n[1]},{project:t}]:[{project:t}],a;for(let r of s){let i;try{i=_n(e,r.project)}catch(o){a=o;continue}if(r.env!==void 0){let o=i.find(l=>l.slug===r.env||l.ref===r.env);if(o)return o;a=new y("no_such_environment",`${i[0].project_name} has no environment ${JSON.stringify(r.env)}`,{hint:`its environments: ${i.map(l=>l.slug).join(", ")}`});continue}if(i.length===1)return i[0];throw new y("which_environment",`${i[0].project_name} has more than one environment`,{hint:`name one: ${i.map(o=>`${o.project_name}/${o.slug}`).join(", ")}`})}throw a}import{createReadStream as I_,openAsBlob as L_}from"node:fs";import{lstat as D_,readdir as N_,realpath as Fu,stat as q_}from"node:fs/promises";import{createHash as K_}from"node:crypto";import{join as G_,relative as Ju,resolve as U_,sep as Wu}from"node:path";var uo=2e4,z_=25<<20,B_=1<<30,F_=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,J_=6,Xs={previews:{type:"boolean",description:"build each pull request against its branch into a preview of its own, in this environment with its settings (turn it on in a preview environment, not production's)"},"no-previews":{type:"boolean",description:"preview no pull request from now on"},"preview-forks":{type:"boolean",description:"preview a fork's pull requests without asking (fork protection off): anyone's code runs with this environment's settings and secrets \u2014 a person's to do, signed in with palbase login"},"no-preview-forks":{type:"boolean",description:"a fork's pull request waits for an admin to authorize each push (fork protection, the default): previews:authorize"}};function Qs(e,t){e.flags.previews===!0&&(t.previews=!0),e.flags["no-previews"]===!0&&(t.previews=!1),e.flags["preview-forks"]===!0&&(t.preview_forks=!0),e.flags["no-preview-forks"]===!0&&(t.preview_forks=!1)}function or(e,t){if(e.out.line(`${t.host} ${t.state}${t.redirect_to?` \u2192 redirects to ${t.redirect_to} (${t.redirect_status??308})`:""}`),t.state!=="active"){e.out.line(` Add to ${t.host}'s DNS:`);for(let n of t.records){let s=n.type==="CNAME"&&t.apex?"ALIAS (or ANAME, or a flattened CNAME: the apex takes no CNAME)":n.type;e.out.line(` ${s} ${n.name} ${n.value}`)}for(let n of t.problems)e.out.line(` - ${n}`)}}function co(e,t,n,s){let a={name:`${t}:domains`,summary:`a ${e}'s own domains: whether each answers, and what its DNS still needs`,usage:`[--${e} <name>]`,flags:s,scope:"environment",async run(l){let d=await f(l),{domains:p}=await g(l,d,"GET",`${await n(l,d)}/domains`);l.out.result({domains:p},()=>{p.length===0&&l.out.line(`No domain yet: palbase ${t}:domains:add <host> adds one.`);for(let u of p)or(l,u)})}},r={name:`${t}:domains:add`,summary:`have a ${e} answer at a domain of your own too (www.example.com, or example.com \u2014 with its www)`,usage:`<host> [--${e} <name>] [--primary www|apex | --only] [--redirect-status 301|302|307|308]`,details:["It answers once its DNS points at the platform and says it is this environment's (the TXT record): both are printed.","At an apex (example.com) its www is added too, and one redirects to the other, as Vercel does: the apex to www.example.com unless --primary apex; --only adds the domain alone.","An apex takes an A record at the platform's address, which any DNS host has; a subdomain a CNAME. A domain is one environment's until it is removed there."],args:{min:1,max:1},flags:{...s,primary:{type:"string",value:"www|apex",description:"at an apex: the address visitors end up at, the other redirecting to it (default: www)"},only:{type:"boolean",description:"add the domain alone: no www, no redirect"},"redirect-status":{type:"string",value:"code",description:"the redirect's status: 301, 302, 307 or 308 (default: 308)"}},scope:"environment",async run(l){let d=l.args[0].trim().toLowerCase().replace(/\.$/,""),p=l.flags.primary??"www";if(p!=="www"&&p!=="apex")throw m("--primary is www or apex");if(l.flags.only===!0&&l.flags.primary!==void 0)throw m("--only adds the domain alone: no --primary");let u=Vu(l.flags["redirect-status"]),h=await f(l),b=await n(l,h),w=`www.${d}`,_=l.flags.only!==!0&&!d.startsWith("www.");await v(l,h,_?`add the domain ${d} \u2014 at an apex, with ${w}, ${p==="www"?`${d} redirecting to ${w}`:`${w} redirecting to ${d}`}`:`add the domain ${d}`);let T=await g(l,h,"POST",`${b}/domains`,{host:d});if(!_||!T.apex){l.out.result(T,()=>or(l,T));return}let S=P=>({redirect_to:P,...u!==void 0?{redirect_status:u}:{}}),$;p==="www"?($=await W_(l,h,b,{host:w}),T=await g(l,h,"PATCH",`${b}/domains/${D("the domain",d)}`,S(w))):$=await V_(l,h,b,w,S(d));let R=p==="www"?[$,T]:[T,$];l.out.result({domains:R},()=>R.forEach(P=>or(l,P)))}},i={name:`${t}:domains:redirect`,summary:`send a domain's visitors to another domain of the ${e}'s \u2014 the same path and query there \u2014 or, with --none, have it answer itself`,usage:`<host> (--to <host> [--status 301|302|307|308] | --none) [--${e} <name>]`,details:["It holds at once. A domain redirects to one that answers itself, and one others redirect to answers itself."],args:{min:1,max:1},flags:{...s,to:{type:"string",value:"host",description:`another domain of the ${e}'s`},status:{type:"string",value:"code",description:"301, 302, 307 or 308 (default: 308)"},none:{type:"boolean",description:"redirect nowhere: the domain answers itself"}},scope:"environment",async run(l){let d=l.args[0].trim().toLowerCase().replace(/\.$/,""),p=l.flags.to?.trim().toLowerCase().replace(/\.$/,"");if(p===void 0==(l.flags.none!==!0))throw m("name where it redirects (--to <host>), or --none");let u=Vu(l.flags.status);if(l.flags.none===!0&&u!==void 0)throw m("--status goes with --to");let h=await f(l),b=await n(l,h);await v(l,h,p?`redirect ${d} to ${p}`:`have ${d} answer itself`);let w=p?{redirect_to:p,...u!==void 0?{redirect_status:u}:{}}:{redirect_to:null},_=await g(l,h,"PATCH",`${b}/domains/${D("the domain",d)}`,w);l.out.result(_,()=>or(l,_))}},o={name:`${t}:domains:remove`,summary:`stop a ${e} answering at a domain: at once, and another environment may add it`,usage:`<host> [--${e} <name>]`,args:{min:1,max:1},flags:s,scope:"environment",async run(l){let d=await f(l),p=await n(l,d);await v(l,d,`remove the domain ${l.args[0]}`),await g(l,d,"DELETE",`${p}/domains/${D("the domain",l.args[0].toLowerCase())}`),l.out.result({removed:l.args[0]},()=>l.out.line(`${l.args[0]} answers no more.`))}};return[a,r,i,o]}function Vu(e){if(e===void 0)return;let t=Number(e);if(![301,302,307,308].includes(t))throw m("a redirect's status is 301, 302, 307 or 308");return t}async function W_(e,t,n,s){try{return await g(e,t,"POST",`${n}/domains`,s)}catch(a){if(!(a instanceof j&&a.code==="domain_exists"))throw a;let{domains:r}=await g(e,t,"GET",`${n}/domains`),i=r.find(o=>o.host===s.host);if(!i)throw a;return i}}async function V_(e,t,n,s,a){try{return await g(e,t,"POST",`${n}/domains`,{host:s,...a})}catch(r){if(!(r instanceof j&&r.code==="domain_exists"))throw r;return await g(e,t,"PATCH",`${n}/domains/${D("the domain",s)}`,a)}}function go(e,t,n,s){return{name:`${t}:previews:authorize`,summary:`build a fork's pull request's preview of a ${e}: its code runs with this environment's settings and secrets`,usage:`<pull request> [--${e} <name>]`,details:["A pull request from a fork waits for an admin of the environment (fork protection): look at what it changes first. The head shown is the one built; a push since asks again.","A person authorizes it, signed in with palbase login: an environment's token (PALBASE_TOKEN) cannot."],args:{min:1,max:1},flags:s,scope:"environment",async run(a){let r=Number(a.args[0].replace(/^#/,""));if(!Number.isInteger(r)||r<=0)throw m("the pull request is its number: 12, or #12");let i=await f(a),o=await n(a,i),{previews:l}=await g(a,i,"GET",`${o}/previews`),d=l.find(u=>u.pull_request===r&&u.state!=="closed");if(!d)throw new y("preview_not_found",`no open preview of pull request #${r}`,{hint:`palbase ${t}:previews lists them`});if(d.state!=="authorization_required")throw new y("preview_not_waiting",`#${r}'s preview waits for no authorization: it is ${d.state}`);await v(a,i,`build pull request #${r} at ${d.commit.slice(0,7)}, from a fork`,`Pull request #${r} is from a fork: its code at ${d.commit.slice(0,7)} runs with this environment's settings and secrets`);let p=await g(a,i,"POST",`${o}/previews/${r}/authorize`,{commit:d.commit});a.out.result(p,()=>a.out.line(`#${r} authorized at ${d.commit.slice(0,7)}: its preview is being built (palbase ${t}:previews).`))}}}function ho(e,t,n){t.length===0&&e.out.line(n);for(let s of t){let a=s.state==="authorization_required"?"waits for authorization (previews:authorize)":s.state==="idle"?"idle: no push for 3 days (previews:open opens it again)":s.state==="refused"?`refused: ${s.reason??""}`:s.state;e.out.line(`#${s.pull_request} ${a} ${s.commit.slice(0,7)}${s.fork?" fork":""} ${s.url}${s.machine?` (${s.machine})`:""}`)}}function Ee(e){if(!F_.test(e))throw m(`${JSON.stringify(e)} is not a site's name: lowercase letters, digits and dashes, starting with a letter`);return D("the site",e)}var Yu={repo:{type:"string",value:"owner/name",description:"build it from this GitHub repository on every push (default with --branch: this checkout's origin)"},branch:{type:"string",value:"branch",description:"the branch whose pushes are built"},path:{type:"string",value:"dir",description:"the site's directory in the repository (default: this directory's place in it)"},"install-command":{type:"string",value:"command",description:"what installs its packages (default: from its lockfile \u2014 pnpm, yarn, npm, bun)"},"build-command":{type:"string",value:"command",description:"what builds it (default: its package.json's build script)"},output:{type:"string",value:"dir",description:"the folder the build writes (default: what its builder writes \u2014 dist, build, out, \u2026)"},"build-env":{type:"string",multiple:!0,value:"K=V",description:"a setting the build runs with (repeat it): what its pages are sent (VITE_*), never a secret"}};async function Xu(e,t){let n={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||t){let r=await Ks(e.io,e.io.cwd),i=e.flags.repo??r?.repository,o=e.flags.branch??r?.branch;if(!i)throw m("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!o)throw m("which branch? none is checked out here","pass --branch <branch>");n.source={repository:i,branch:o,path:e.flags.path??(e.flags.repo===void 0?r?.path??"":"")}}let s={};e.flags["install-command"]!==void 0&&(s.install_command=e.flags["install-command"]),e.flags["build-command"]!==void 0&&(s.command=e.flags["build-command"]),e.flags.output!==void 0&&(s.output_dir=e.flags.output);let a=e.flags["build-env"];if(a){let r={};for(let i of a){let o=i.indexOf("=");if(o<=0)throw m(`a setting is K=V, not ${JSON.stringify(i)}`);r[i.slice(0,o)]=i.slice(o+1)}s.env=r}return Object.keys(s).length>0&&(n.build=s),n}function Zs(e){return e<1024?`${e} B`:e<1<<20?`${(e/1024).toFixed(1)} KiB`:`${(e/(1<<20)).toFixed(1)} MiB`}async function H_(e){let t=await Fu(e),n=[],s=async r=>{let i=await N_(r,{withFileTypes:!0});for(let o of i){if(o.name==="node_modules"||o.name.startsWith(".")&&!(o.name===".well-known"&&r===t))continue;let l=G_(r,o.name),d=await D_(l),p=l;if(d.isSymbolicLink()&&(p=await Fu(l),p!==t&&!p.startsWith(t+Wu)))throw new y("unsafe_path",`${Ju(t,l)} links outside ${e}`);let u=d.isSymbolicLink()?await q_(p):d;if(u.isDirectory()){await s(l);continue}if(!u.isFile())continue;let h="/"+Ju(t,l).split(Wu).join("/");if(/[\u0000-\u001f\u007f?#\\]/.test(h))throw new y("invalid_path",`${h} has a character a site's path cannot carry (?, #, \\ or a control character): rename it`);if(h.length>1024)throw new y("invalid_path",`${h.slice(0,80)}\u2026 is longer than a site's path may be (1024 bytes)`);if(u.size>z_)throw new y("file_too_large",`${h} is ${Zs(u.size)}: a site's file is 25 MiB at most (keep large media in storage)`);if(n.push({path:h,file:p,size:u.size,digest:""}),n.length>uo)throw new y("too_many_files",`${e} holds more than ${uo} files: a site holds ${uo} at most`)}};await s(t),n.sort((r,i)=>r.path<i.path?-1:r.path>i.path?1:0);let a=n.reduce((r,i)=>r+i.size,0);if(a>B_)throw new y("release_too_large",`${e} holds ${Zs(a)}: a release is 1 GiB at most`);for(let r of n)r.digest=await Y_(r.file);return n}function Y_(e){return new Promise((t,n)=>{let s=K_("sha256");I_(e).on("data",a=>s.update(a)).on("error",n).on("end",()=>t(s.digest("hex")))})}function X_(e){let t=e.filter(n=>/\.html?$/i.test(n.path));return t.length===1&&t[0].path==="/index.html"}async function Z_(e,t,n){try{return await g(e,t,"GET",`/sites/${Ee(n)}`)}catch(s){if(s instanceof j&&s.code==="site_not_found")return null;throw s}}async function Hu(e,t,n,s,a,r){let i=0,o=0,l=async()=>{for(;o<a.length;){let d=a[o++],p=r.get(d);if(!p)throw new y("internal",`the release asked for ${d}, which no file here is`);let u=await L_(p.file);await g(e,t,"PUT",`/sites/${Ee(n)}/releases/${D("the release",s)}/files/${d}`,new wn(u,"application/octet-stream")),i+=p.size}};return await Promise.all(Array.from({length:Math.min(J_,a.length)},l)),i}var Q_={name:"hosting:deploy",summary:"deploy a folder as a static site: only the files that changed are sent, and the site serves them at once",usage:"<folder> [--site <name>] [--spa]",details:["The folder is what your build wrote (dist, build, out, public): every file in it is served, at its path from the folder's root.","The site is opened on its first deploy; --spa then answers a path no file answers with /index.html (a single-page app).","A folder of one page, index.html, is taken for a single-page app when the site is opened."],args:{min:1,max:1},flags:{site:{type:"string",value:"name",description:"the site (default: web, which answers at the environment's ref)"},spa:{type:"boolean",description:"when the site is opened: answer every path no file answers with /index.html"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web";Ee(n);let s=U_(e.io.cwd,e.args[0]),a=await H_(s).catch(w=>{throw w instanceof y?w:new y("no_folder",`${e.args[0]}: ${w instanceof Error?w.message:String(w)}`)});if(a.length===0)throw new y("empty_folder",`${e.args[0]} holds no file to serve`);a.some(w=>w.path==="/index.html")||e.out.note(` ${e.args[0]} has no index.html: the site's root answers 404.`);let r=a.reduce((w,_)=>w+_.size,0);await v(e,t,`deploy ${a.length} files (${Zs(r)}) to the site ${n}`);let i=await Z_(e,t,n);if(!i){let w=e.flags.spa===!0||X_(a);i=await g(e,t,"POST","/sites",{name:n,spa:w}),e.out.note(` opened the site ${n}${w?" (a single-page app: every route is its index.html)":""}`)}let o={},l=new Map;for(let w of a)o[w.path]=w.digest,l.set(w.digest,w);let d=await g(e,t,"POST",`/sites/${Ee(n)}/releases`,{files:o}),p=d.missing??[];e.out.note(` release #${d.number}: ${a.length} files, ${p.length} to send${p.length<l.size?` (${l.size-p.length} kept from before)`:""}`);let u=await Hu(e,t,n,d.id,p,l),h;for(let w=0;w<3&&!h;w++)try{h=await g(e,t,"POST",`/sites/${Ee(n)}/releases/${D("the release",d.id)}/finish`)}catch(_){let T=_ instanceof j&&_.code==="files_missing"?_.body?.missing??[]:[];if(T.length===0)throw _;u+=await Hu(e,t,n,d.id,T,l)}if(!h)throw new y("release_unfinished",`the release #${d.number} could not be finished`,{hint:"deploy again"});i=await g(e,t,"GET",`/sites/${Ee(n)}`);let b={site:i.name,url:i.url,urls:i.urls,release:h,sent_bytes:u};e.out.result(b,()=>{e.out.line(`Deployed release #${h.number} of ${i.name} (${a.length} files, ${Zs(u)} sent).`);for(let w of i.urls)e.out.line(` ${w}`)})}},e0={name:"hosting:sites:list",summary:"the environment's static sites, where each answers and the release it serves",scope:"environment",async run(e){let t=await f(e),{sites:n}=await g(e,t,"GET","/sites");e.out.result({sites:n},()=>{n.length===0&&e.out.line("No site yet: palbase hosting:deploy <folder> opens one.");for(let s of n){let a=s.taken_down?`taken down: ${s.taken_down.reason}`:s.release?`release #${s.release.number}`:"nothing deployed";e.out.line(`${s.name} ${s.url} ${a}${s.spa?" (single-page app)":""}`)}})}},t0={name:"hosting:sites:create",summary:"open a static site \u2014 built from GitHub on every push with --repo/--branch, or deployed from a folder (hosting:deploy opens one too)",usage:"[name] [--spa] [--repo <owner/name> --branch <branch> --path <dir>] [--build-command \u2026] [--output <dir>] [--build-env K=V]\u2026 [--previews [--preview-forks]]",details:["With --branch (and --repo, or in a checkout of a GitHub repository), its branch's head is built at once and on every push: the folder its build writes is served.","With --previews, each pull request against the branch is built too and answers at <name>-pr<n>-<ref> until it closes; the pull request is told where.","The environment needs its GitHub link first: palbase github:connect."],args:{min:0,max:1},flags:{spa:{type:"boolean",description:"a single-page app: every path no file answers is its /index.html"},...Yu,previews:Xs.previews,"preview-forks":Xs["preview-forks"]},scope:"environment",async run(e){let t=await f(e),n=e.args[0]??"web";Ee(n);let{source:s,build:a}=await Xu(e,!1);await v(e,t,`open the site ${n}${s?`, built from ${s.repository}@${s.branch}`:""}`);let r={};Qs(e,r);let i=await g(e,t,"POST","/sites",{name:n,spa:e.flags.spa===!0,...s?{source:s}:{},...a?{build:a}:{},...r});e.out.result(i,()=>e.out.line(i.source?`Opened ${i.name}: ${i.url} \u2014 its first build is on its way (palbase hosting:builds --site ${i.name}).`:`Opened ${i.name}: ${i.url} (nothing deployed yet: palbase hosting:deploy <folder> --site ${i.name}).`))}},n0={name:"hosting:sites:update",summary:"change a site: whether it is a single-page app, where it is built from, what its build runs",usage:"<name> [--spa | --no-spa] [--repo \u2026 --branch \u2026 --path \u2026] [--no-source] [--build-command \u2026] [--output \u2026] [--build-env K=V]\u2026 [--previews | --no-previews] [--preview-forks | --no-preview-forks]",args:{min:1,max:1},flags:{spa:{type:"boolean",description:"a single-page app: every path no file answers is its /index.html"},"no-spa":{type:"boolean",description:"not a single-page app: a path no file answers is 404"},...Yu,"no-source":{type:"boolean",description:"build it from no repository any more: deploy folders alone"},...Xs},scope:"environment",async run(e){let t=await f(e),n=e.args[0],s={};e.flags.spa===!0&&(s.spa=!0),e.flags["no-spa"]===!0&&(s.spa=!1);let{source:a,build:r}=await Xu(e,!1);if(a&&(s.source=a),e.flags["no-source"]===!0&&(s.source={repository:""}),r&&(s.build=r),Qs(e,s),Object.keys(s).length===0)throw m("nothing to change: give a flag","palbase help hosting:sites:update");await v(e,t,`change the site ${n}`);let i=await g(e,t,"PATCH",`/sites/${Ee(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: ${i.url}${i.spa?" (single-page app)":""}${i.source?` \u2014 built from ${i.source.repository}@${i.source.branch}`:""}`))}},s0={name:"hosting:sites:delete",summary:"delete a site: its addresses serve nothing from now on",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0];await v(e,t,`delete the site ${n} \u2014 its addresses serve nothing from now on`),await g(e,t,"DELETE",`/sites/${Ee(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted ${n}.`))}},a0={name:"hosting:releases",summary:"a site's releases, newest first: the ten newest are kept to return to",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",{releases:s}=await g(e,t,"GET",`/sites/${Ee(n)}/releases`);e.out.result({releases:s},()=>{for(let a of s)e.out.line(`#${a.number} ${a.id} ${a.state}${a.serving?" serving":""} ${a.files} files ${Zs(a.bytes)} ${a.created_at}${a.commit?` ${a.commit.slice(0,7)}`:""}`)})}},r0={name:"hosting:rollback",summary:"serve an earlier release of a site again, at once (the one before the serving one, or a named one)",usage:"[--site <name>] [--to <release id | #number>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"},to:{type:"string",value:"release",description:"the release: its id, or its number (#3)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",s=e.flags.to;if(s!==void 0&&/^#?\d+$/.test(s)){let r=Number(s.replace("#","")),{releases:i}=await g(e,t,"GET",`/sites/${Ee(n)}/releases`),o=i.find(l=>l.number===r);if(!o)throw new y("release_not_found",`${n} keeps no release #${r}`,{hint:`palbase hosting:releases --site ${n}`});s=o.id}await v(e,t,`serve ${s?`the release ${e.flags.to}`:"the release before the serving one"} of ${n}`);let a=await g(e,t,"POST",`/sites/${Ee(n)}/rollback`,s?{release:s}:{});e.out.result(a,()=>e.out.line(`${a.name} serves release #${a.release?.number}: ${a.url}`))}},i0={name:"hosting:builds",summary:"a site's newest builds (a site built from GitHub), or one with its log",usage:"[build] [--site <name>] [--wait]",args:{min:0,max:1},flags:{site:{type:"string",value:"name",description:"the site (default: web)"},wait:{type:"boolean",description:"with a build: follow it until it ends"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",s=e.args[0];if(s===void 0){let{builds:r}=await g(e,t,"GET",`/sites/${Ee(n)}/builds`);e.out.result({builds:r},()=>{r.length===0&&e.out.line(`${n} has no build: it is built from GitHub with hosting:sites:create --repo \u2026 --branch \u2026.`);for(let i of r)e.out.line(`${i.id} ${i.commit.slice(0,7)} ${i.state}${i.reason?`: ${i.reason}`:""} ${i.message}`)});return}let a=await g(e,t,"GET",`/sites/${Ee(n)}/builds/${D("the build",s)}`);for(let r=0;e.flags.wait===!0&&(a.state==="queued"||a.state==="building")&&r<900;r++)await e.io.sleep(2e3),a=await g(e,t,"GET",`/sites/${Ee(n)}/builds/${D("the build",s)}`);if(e.out.result(a,()=>{e.out.line(`${a.id} ${a.commit.slice(0,7)} ${a.state}${a.reason?`: ${a.reason}`:""}`),a.log&&e.out.line(a.log)}),a.state==="failed")return 1}},o0={name:"hosting:build",summary:"build a site's branch head now and serve it, as a push does",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web";await v(e,t,`build ${n} from the head of its branch`);let s=await g(e,t,"POST",`/sites/${Ee(n)}/builds`);e.out.result(s,()=>e.out.line(`${s.name}: a build of ${s.source?.branch??"its branch"}'s head is on its way (palbase hosting:builds --site ${s.name}).`))}},l0={name:"hosting:previews",summary:"a site's pull request previews, newest first: where each answers, and whether it is built",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",{previews:s}=await g(e,t,"GET",`/sites/${Ee(n)}/previews`);e.out.result({previews:s},()=>ho(e,s,`${n} has no preview: a site built from GitHub previews its pull requests with hosting:sites:update ${n} --previews.`))}},p0=co("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),d0=go("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),Zu=[Q_,e0,t0,n0,s0,a0,r0,i0,o0,l0,d0,...p0];function ec(e){return`its last push (${A(e.commit.slice(0,7))}) did not roll out \u2014 ${A(ea(e.reason))}`}function u0(e){let t=`${e.running_version} serving`;if(e.version!==e.running_version){let s={waiting:"waiting to roll out",not_up:"not up",given_up:"given up on",migration:"held by its database migration",stopped:"not up (the machine is stopped)",failed:"not up (the machine cannot run)"}[e.state_reason_kind??""]??(e.state_reason?"not up":"rolling out");t+=`, ${e.version} ${s}`}return`${t}${e.build?` (build ${e.build.id}, ${e.build.commit.slice(0,7)})`:""}`}function ea(e){return e.split(/\r?\n/).map(t=>t.trim()).filter(t=>t!=="").join(" \xB7 ")}function c0(e){let t=e.openapi_path?`, from ${e.openapi_path}`:"";switch(e.contract_state){case"refused":return`refused: ${(e.contract_error??"its app wrote none").split(/\r?\n/).filter(s=>s.trim()!=="").map(A).join(`
31
+ `))e.out.line(a)}})}},qd={name:"notify:templates:set",summary:"override a template in a locale, from a file or stdin (email: {subject, html_body, text_body} as JSON)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp] [--file <path>]",details:['email: {"subject", "html_body", "text_body"}; sms: the text, or {"body"}; whatsapp: {"name", "language", "variables", "code_button"} of a template approved on your Meta account.',"notify:templates:get \u2026 --json > t.json gives a document to edit and set back. Handlebars: {{name}}, {{#if}}, {{#each}}."],args:{min:1,max:1},flags:{...Yi,file:{type:"string",value:"path",description:"the template (default: stdin)"}},scope:"environment",async run(e){let t=await f(e),n=Ys(e),s=Xi(e,e.args[0]),a=n_(n,await be(e,e.flags.file,"the template"));await v(e,t,`override the ${n} template ${e.args[0]} (${e.flags.locale})`);let r=await g(e,t,"PUT",s,a,C());e.out.result(r,()=>e.out.line(`The ${n} template ${e.args[0]} (${e.flags.locale}) is yours now${typeof r.subject=="string"?`: "${r.subject}"`:""}.`))}},Kd={name:"notify:templates:delete",summary:"remove an override: the platform's default renders again (a WhatsApp mapping is forgotten)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:Yi,scope:"environment",async run(e){let t=await f(e),n=Xi(e,e.args[0]);await v(e,t,`remove the ${Ys(e)} template ${e.args[0]} (${e.flags.locale})`),await g(e,t,"DELETE",n,void 0,C()),e.out.result({deleted:{channel:Ys(e),slug:e.args[0],locale:e.flags.locale}},()=>e.out.line(`Removed the override of ${e.args[0]} (${e.flags.locale}).`))}};async function Hi(e,t){let n=[],s;do{let a=await g(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${k(s)}`:""}`);n.push(...a.topics),s=a.next_cursor}while(s);return n}var Gd={name:"notify:topics:list",summary:"the topics users follow, how many follow each, whether they follow it from the app",scope:"environment",async run(e){let t=await f(e),n=await Hi(e,t);e.out.result({topics:n},()=>{n.length===0&&e.out.line("No topic: palbase notify:topics:create <name> makes one."),e.out.table(n,[{header:"topic",value:s=>s.name},{header:"followers",value:s=>String(s.subscribers)},{header:"from the app",value:s=>s.self_subscribe?"yes":"no: the project adds them"}])})}},Ud={name:"notify:topics:create",summary:"make a topic users can follow; a push to it reaches its followers",usage:"<name> [--self-subscribe yes|no]",args:{min:1,max:1},flags:{"self-subscribe":{type:"string",value:"yes|no",description:"users follow it from the app (default no: the project adds them)"}},scope:"environment",async run(e){let t=await f(e),n=en(e.flags["self-subscribe"],"self-subscribe");await v(e,t,`make the topic ${e.args[0]}`);let s=await g(e,t,"POST","/notify/topics",{name:e.args[0],...n!==void 0?{self_subscribe:n}:{}},C());e.out.result(s,()=>e.out.line(`Made the topic ${s.name}${s.self_subscribe?", followed from the app":""}.`))}},zd={name:"notify:topics:delete",summary:"delete a topic and who follows it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`delete the topic ${e.args[0]} and who follows it`),await g(e,t,"DELETE",`/notify/topics/${k(e.args[0])}`,void 0,C()),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the topic ${e.args[0]}.`))}},Bd={name:"notify:settings:get",summary:"the environment's message settings: the default locale, who email is from, where texts may go",scope:"environment",async run(e){let t=await f(e),{body:n}=await qe(e,t,"/notify/settings");e.out.result(n,()=>Yn(e,n))}},Fd={name:"notify:settings:set",summary:'change message settings: key=value (from_email=\u2026, sms_countries=["+90","+44"], mx_check=true)',usage:"<key=value>\u2026",details:["The change is made on the revision read: one made beside it in the meantime is refused, not overwritten."],args:{min:1,max:20},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args),{body:s,etag:a}=await qe(e,t,"/notify/settings"),r=Qt(s,n),i=Dt(s,r);if(i.length===0)return ze(e,"the message settings",s);await v(e,t,`set the message settings ${i.join(", ")}`);let o=await g(e,t,"PUT","/notify/settings",r,{...Re(a),...C()});e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${Te(s[l])} \u2192 ${Te(o[l])}`)})}},a_=new Set(["sms_countries","sms_blocked_countries"]);function r_(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Jd={name:"notify",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([g(e,t,"GET","/notify/settings"),g(e,t,"GET","/notify/providers"),g(e,t,"GET","/notify/templates/email"),g(e,t,"GET","/notify/templates/sms"),g(e,t,"GET","/notify/templates/whatsapp"),Hi(e,t)]),l={};for(let[u,h]of Object.entries(r_(n)))l[`settings.${u}`]=h;for(let u of s.providers)l[`providers.${u.channel}/${u.provider}`]={active:u.active,priority:u.priority,credentials:"set"};let d=a.templates.filter(u=>u.customized),p=await Promise.all(d.map(u=>g(e,t,"GET",`/notify/templates/email/${k(u.slug)}/${k(u.locale)}`)));for(let u of p)l[`templates.email/${u.slug}/${u.locale}`]={subject:u.subject,html_body:u.html_body,text_body:u.text_body};for(let u of r.templates.filter(h=>h.customized))l[`templates.sms/${u.slug}/${u.locale}`]={body:u.body};for(let u of i.templates)l[`templates.whatsapp/${u.slug}/${u.locale}`]={name:u.name,language:u.language,variables:u.variables,code_button:u.code_button};for(let u of o)l[`topics.${u.name}`]={self_subscribe:u.self_subscribe};return l},async copy(e,t,n,s){let a=pe(n,"settings.");if(a.length>0){let{body:o,etag:l}=await qe(e,t,"/notify/settings"),d={...o};for(let p of a)d[p.name]=a_.has(p.name)&&Array.isArray(p.from)&&p.from.length===0?null:p.from;await V(s,`set the message settings ${a.map(p=>p.name).join(", ")}`,async()=>{await g(e,t,"PUT","/notify/settings",d,{...Re(l),...C()})})}for(let o of pe(n,"providers.")){let[l,d]=o.name.split("/");o.from===void 0?s.left(`the ${l} provider ${d}: only in the target, left as it is (it holds credentials)`):s.left(`the ${l} provider ${d}: credentials are not copied \u2014 set them with palbase notify:providers:set ${l} ${d} < credentials.json`)}for(let o of pe(n,"templates.")){let l=`/notify/templates/${o.name.split("/").map(k).join("/")}`;o.from===void 0?await V(s,`remove the template override ${o.name}`,async()=>{await g(e,t,"DELETE",l,void 0,C())}):await V(s,`override the template ${o.name}`,async()=>{await g(e,t,"PUT",l,o.from,C())})}let r=pe(n,"topics."),i=r.some(o=>o.from===void 0)?new Map((await Hi(e,t)).map(o=>[o.name,o.subscribers])):new Map;for(let o of r){let l=`/notify/topics/${k(o.name)}`,d=o.from?.self_subscribe;if(o.from===void 0){let p=i.get(o.name)??0;p>0?s.left(`the topic ${o.name}: only in the target, and ${p} users follow it \u2014 delete it with notify:topics:delete`):await V(s,`delete the topic ${o.name}`,async()=>{await g(e,t,"DELETE",l,void 0,C())})}else o.change==="only_in_from"?await V(s,`make the topic ${o.name}`,async()=>{await g(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:d},C())}):await V(s,`set the topic ${o.name} ${d?"followed from the app":"followed by the project's adding"}`,async()=>{await g(e,t,"PATCH",l,{self_subscribe:d},C())})}}};var es=["max_connections","max_events_per_second","max_joins_per_second","max_channels_per_connection"];function i_(e){if(e.kind==="owner")return"the user it names: everything";let t=[e.publish?"publish":"",e.state_read?"read state":"",e.state_write?"write state":""].filter(n=>n!=="");return`any signed-in user: subscribe${t.length>0?`, ${t.join(", ")}`:""}`}function Zi(e){return Object.fromEntries(es.map(t=>[t,e[t]??null]))}var Wd={name:"realtime:channels:get",summary:"the channel declaration, in the order patterns are matched: who may join each, publish, read and write its state",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/realtime/channels");e.out.result(n,()=>{n.channels.length===0&&e.out.line("No channel is declared: a channel no pattern matches belongs to no one."),e.out.table(n.channels,[{header:"pattern",value:s=>s.pattern},{header:"kind",value:s=>s.kind},{header:"who",value:s=>i_(s)}])})}},Vd={name:"realtime:channels:set",summary:'replace the channel declaration with a JSON file or stdin ({"channels": [\u2026]} or the list)',usage:"[--file <path>]",details:['Each channel: {"pattern": "room:{id}", "kind": "public", "publish": true, "state_read": true, "state_write": false}, or {"pattern": "user:{uid}", "kind": "owner"}.',"realtime:channels:get --json gives the document to edit. Open sockets are decided again within a heartbeat."],flags:{file:{type:"string",value:"path",description:"the declaration (default: stdin)"}},scope:"environment",async run(e){let t=await f(e),n=ue(await be(e,e.flags.file,"the channel declaration"),"the channel declaration"),s=Array.isArray(n)?n:n.channels;if(!Array.isArray(s))throw m('the declaration is {"channels": [\u2026]} or a list of channels');let a=await g(e,t,"GET","/realtime/channels");if(Oe(a.channels,s))return ze(e,"the channel declaration",a);await v(e,t,`declare ${s.length} channel pattern(s) (now ${a.channels.length})`);let r=await g(e,t,"PUT","/realtime/channels",{channels:s});e.out.result(r,()=>e.out.line(r.channels.length===0?"No channel is declared now.":`Declared ${r.channels.length} channel pattern(s): ${r.channels.map(i=>i.pattern).join(", ")}.`))}},Hd={name:"realtime:limits:get",summary:"the limits the environment holds its clients to (null: the platform's), and the platform's",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/realtime/limits");e.out.result(n,()=>{let s=n.platform??{},a=Math.max(...es.map(r=>r.length));for(let r of es)e.out.line(`${r.padEnd(a)} ${n[r]===void 0||n[r]===null?"the platform's":Te(n[r])}${s[r]!==void 0?` (platform ${s[r]===0?"no ceiling":Te(s[r])})`:""}`)})}},Yd={name:"realtime:limits:set",summary:"change realtime limits: key=value (max_connections=500), null for the platform's",usage:"<key=value>\u2026",details:[`The keys: ${es.join(", ")}. A value above the platform's has no effect beyond it.`],args:{min:1,max:4},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args);for(let o of n)if(o.path.length!==1||!es.includes(o.path[0]))throw m(`no limit ${JSON.stringify(o.path.join("."))}`,`the limits: ${es.join(", ")}`);let s=Zi(await g(e,t,"GET","/realtime/limits")),a=Qt(s,n),r=Dt(s,a);if(r.length===0)return ze(e,"the realtime limits",s);await v(e,t,`set the realtime limits ${r.join(", ")}`);let i=await g(e,t,"PUT","/realtime/limits",a);e.out.result({limits:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${Te(s[o]??"the platform's")} \u2192 ${Te(i[o]??"the platform's")}`)})}},Xd={name:"realtime",async read(e,t){let[n,s]=await Promise.all([g(e,t,"GET","/realtime/channels"),g(e,t,"GET","/realtime/limits")]),a={channels:n.channels};for(let[r,i]of Object.entries(Zi(s)))a[`limits.${r}`]=i;return a},async copy(e,t,n,s){let a=n.find(i=>i.key==="channels");a&&await V(s,"declare the channels",async()=>{await g(e,t,"PUT","/realtime/channels",{channels:a.from??[]})});let r=pe(n,"limits.");if(r.length>0){let i=Zi(await g(e,t,"GET","/realtime/limits"));for(let o of r)i[o.name]=o.from??null;await V(s,`set the realtime limits ${r.map(o=>o.name).join(", ")}`,async()=>{await g(e,t,"PUT","/realtime/limits",i)})}}},Zd={name:"analytics:settings:get",summary:"how long analytics keeps events and console records, the share of performance samples kept, whether bodies are kept",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET","/analytics/settings");e.out.result(n,()=>Yn(e,n))}},Qd={name:"analytics:settings:set",summary:"change analytics settings: key=value (retention_days=30, perf_sample_pct=10, console_bodies=false)",usage:"<key=value>\u2026",details:["A shorter retention removes what is older within minutes."],args:{min:1,max:4},scope:"environment",async run(e){let t=await f(e),n=Zt(e.args),s=await g(e,t,"GET","/analytics/settings"),a=Qt(s,n),r=Dt(s,a);if(r.length===0)return ze(e,"the analytics settings",s);await v(e,t,`set the analytics settings ${r.join(", ")}`);let i=await g(e,t,"PUT","/analytics/settings",a);e.out.result({settings:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${Te(s[o])} \u2192 ${Te(i[o])}`)})}},eu={name:"analytics",async read(e,t){let n=await g(e,t,"GET","/analytics/settings");return Object.fromEntries(Object.entries(n).map(([s,a])=>[`settings.${s}`,a]))},async copy(e,t,n,s){let a=pe(n,"settings.");if(a.length===0)return;let r=await g(e,t,"GET","/analytics/settings");for(let i of a)i.from!==void 0&&(r[i.name]=i.from);await V(s,`set the analytics settings ${a.map(i=>i.name).join(", ")}`,async()=>{await g(e,t,"PUT","/analytics/settings",r)})}};function o_(e){return{source:{repository:e.source.repository.toLowerCase(),branch:e.source.branch,path:e.source.path??"",context:e.source.context??""},size:e.size,replicas:e.replicas,port:e.port,health_path:e.health_path??"",env:e.env??{},secrets:e.secrets??"",database:e.database?{name:e.database.name,migrations:e.database.migrations===!0}:null,deploy_after_checks:e.deploy_after_checks===!0,deploy_checks:[...e.deploy_checks??[]].sort(),deploy_checks_app:e.deploy_checks_app??"github-actions"}}var tu={name:"machines",async read(e,t){let{machines:n}=await g(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.filter(s=>!s.preview_of).map(s=>[`machines.${s.name}`,o_(s)]))},async copy(e,t,n,s){for(let a of pe(n,"machines.")){if(a.change==="only_in_to"){s.left(`the machine ${a.name}: only the target has it \u2014 it serves on (palbase machines:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await V(s,`opened the machine ${a.name}, built from ${r.source.branch}'s head`,()=>g(e,t,"POST","/machines",{name:a.name,...r,database:r.database??void 0},C()));continue}let i=a.to,o=Object.fromEntries(Object.keys(r).filter(l=>!Oe(r[l],i[l])).map(l=>[l,l==="database"&&r[l]===null?{name:""}:r[l]]));await V(s,`changed the machine ${a.name}: ${Object.keys(o).join(", ")} (a new version rolls out beside the one serving)`,()=>g(e,t,"PATCH",`/machines/${k(a.name)}`,o))}}};function l_(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function p_(e,t,n,s){let a=await g(e,t,"POST","/databases",{name:n,...s},C());await e.state.keepDatabasePasswords(t.env_ref,a.name,{owner:a.credentials.owner.password,app:a.credentials.app.password})}var nu={name:"databases",async read(e,t){let{databases:n}=await g(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.filter(s=>s.size!=="preview").map(s=>[`databases.${s.name}`,l_(s)]))},async copy(e,t,n,s){for(let a of pe(n,"databases.")){if(a.change==="only_in_to"){s.left(`the database ${a.name}: only the target has it \u2014 its data stays (palbase databases:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await V(s,`opened the database ${a.name} (${r.size}; its passwords kept on this computer \u2014 palbase db:copy brings its data)`,()=>p_(e,t,a.name,r));continue}let i=a.to,o=Object.fromEntries(["size","allow","backup_days"].filter(d=>!Oe(r[d],i[d])).map(d=>[d,r[d]]));if(Object.keys(o).length===0)continue;let l=o.size===void 0?"":` (moving to ${o.size}, serving on)`;await V(s,`changed the database ${a.name}: ${Object.keys(o).join(", ")}${l}`,()=>g(e,t,"PATCH",`/databases/${k(a.name)}`,o))}}};var tt="/messaging/settings",d_=/^[A-Z][A-Z0-9_]{0,127}$/,eo=["retention_days","push_preview","deleted_user_messages","search_by_meaning","event_text"];function ts(e,t){switch(e){case"retention_days":return t===null?"off: messages are kept for good":`${String(t)} days: older messages are removed`;case"push_preview":return t===!0?"on: a push says what the message says":"off: a push says only that a message came";case"deleted_user_messages":return t==="delete"?"delete: a deleted user's messages go with them":"keep: a deleted user's messages stay, their writer anonymous";case"search_by_meaning":return t===!0?"on: messages are found by what they mean too, each embedded by the AI module (billed as AI use)":"off: messages are found by their words alone";case"event_text":return t===!0?"on: messaging.message.created says what the message says":"off: messaging.message.created says who wrote to whom, not what (the backend reads it with the secret key)";case"hook":return t?su(t):"none: anyone may reach anyone"}}function su(e){return`${A(e.url)} (waited for ${e.timeout_ms} ms; when it does not answer, ${e.on_error==="allow"?"everyone is allowed":"no one is: the app is told to try again"})`}function u_(e,t){let n=[...eo.map(a=>[a,t[a]]),["hook",t.hook]],s=Math.max(...n.map(([a])=>a.length));for(let[a,r]of n)e.out.line(`${a.padEnd(s)} ${ts(a,r)}`)}function Qi(e,t){if(e!==void 0){if(e==="on")return!0;if(e==="off")return!1;throw m(`--${t} is on or off`)}}function c_(e){if(e===void 0)return;if(e==="off")return null;let t=Number(e);if(!/^[0-9]+$/.test(String(e))||t<1||t>36500)throw m("--retention-days is a whole number of days from 1 to 36500, or off (kept for good)");return t}var g_={name:"messaging:settings:get",summary:"how the environment runs its conversations: how long messages are kept, whether a push says what one says, what becomes of a deleted user's messages, whether they are found by what they mean, whether a message's event says what it says, the hook",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",tt);e.out.result(n,()=>u_(e,n))}},h_={name:"messaging:settings:set",summary:"change how the environment runs its conversations: a setting left out is left as it is",usage:"[--retention-days <days>|off] [--push-preview on|off] [--deleted-user-messages keep|delete] [--search-by-meaning on|off] [--event-text on|off]",details:["A shorter retention removes the messages older than it, in every conversation. A deleted user's messages are kept or deleted as the setting is when the user is deleted.","Deleted, what of theirs had already gone from screens (seen once, its time up, vanish mode, taken back) stays its 14 days, sealed, for reports, then goes.","Search by meaning on: the app's search({ by: 'meaning' }) finds the messages nearest what a question means, no word of it needed. Each message's text (not a system message's, one seen a limited number of times, nor one of vanish mode) is embedded by the project's embedding model through the AI module and billed as the project's AI use; the messages written before are embedded in the background (seconds to minutes), a new one is found ~5 s after it is sent. Off: no message goes to a model, and the vectors are removed.","Event text on: messaging.message.created \u2014 told to the backend's webhooks and @On handlers that name it \u2014 carries the message's text (not a vanish-mode message's, one seen a limited number of times, nor one that goes after a time). The words leave the sealed store and stay on the event stream 7 days. Off: the backend reads the message with the secret key.","Only what changes is sent: a change made beside this one to another setting stays."],flags:{"retention-days":{type:"string",value:"days|off",description:"messages older than this many days are removed (1\u201336500); off: kept for good"},"push-preview":{type:"string",value:"on|off",description:"a push says what the message says (on), or only that one came (off)"},"deleted-user-messages":{type:"string",value:"keep|delete",description:"a deleted user's messages: kept, their writer anonymous, or deleted (what went from screens before stays its 14 days for reports)"},"search-by-meaning":{type:"string",value:"on|off",description:"messages are found by what they mean too (on: each embedded through the AI module, billed as AI use), or by their words alone (off)"},"event-text":{type:"string",value:"on|off",description:"messaging.message.created says what the message says (on), or who wrote to whom alone (off)"}},scope:"environment",async run(e){let t={},n=c_(e.flags["retention-days"]);n!==void 0&&(t.retention_days=n);let s=Qi(e.flags["push-preview"],"push-preview");s!==void 0&&(t.push_preview=s);let a=e.flags["deleted-user-messages"];if(a!==void 0){if(a!=="keep"&&a!=="delete")throw m("--deleted-user-messages is keep or delete");t.deleted_user_messages=a}let r=Qi(e.flags["search-by-meaning"],"search-by-meaning");r!==void 0&&(t.search_by_meaning=r);let i=Qi(e.flags["event-text"],"event-text");if(i!==void 0&&(t.event_text=i),Object.keys(t).length===0)throw m("nothing to change","pass --retention-days, --push-preview, --deleted-user-messages, --search-by-meaning or --event-text");let o=await f(e),l=await g(e,o,"GET",tt),d=eo.filter(u=>u in t&&!Oe(l[u],t[u]));if(d.length===0)return ze(e,"the messaging settings",l);await v(e,o,`set the messaging settings ${d.join(", ")}`);let p=await g(e,o,"PUT",tt,Object.fromEntries(d.map(u=>[u,t[u]])));e.out.result({settings:p,changed:d},()=>{for(let u of d)e.out.line(`${u}: ${ts(u,l[u])} \u2192 ${ts(u,p[u])}`)})}},au={"secret-to":{type:"string",value:"NAME",description:"keep the new signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}};function ru(e){let t=e.flags["secret-to"];if(t!==void 0&&!d_.test(t))throw m(`--secret-to ${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&t===void 0)throw m("--dev goes with --secret-to <NAME>");return t===void 0?void 0:{name:t,set:e.flags.dev===!0?"development":"production"}}async function iu(e,t,n,s,a){let{hook_secret:r,...i}=n;if(r===void 0){e.out.result({settings:i},()=>e.out.line(`${a}: ${ts("hook",i.hook)}.`));return}if(s!==void 0){let o=await g(e,t,"PUT",`/secrets/${s.set}/${s.name}`,{value:r});e.out.result({settings:i,secret_to:{set:s.set,name:s.name,version:o.version}},()=>e.out.line(`${a}: ${ts("hook",i.hook)}; its signing secret is ${s.name} (${s.set}, v${o.version}) in ${N(t)}.`));return}e.out.result(n,()=>{e.out.line(`${a}: ${ts("hook",i.hook)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}var m_={name:"messaging:hook:get",summary:"the hook asked whom one may reach: where it is, how long it is waited for, what is done when it does not answer (never its secret)",scope:"environment",async run(e){let t=await f(e),{hook:n}=await g(e,t,"GET",tt);e.out.result({hook:n},()=>e.out.line(n?su(n):"No hook: anyone may reach anyone. palbase messaging:hook:set --url <url> sets one."))}},f_={name:"messaging:hook:set",summary:"set the hook asked whom one may reach, or change it; a new hook's signing secret is printed this once, or kept in a secret with --secret-to",usage:"[--url <url>] [--timeout-ms <ms>] [--on-error allow|deny] [--secret-to <NAME> [--dev]]",details:[`Before a direct conversation opens, people are added to a group, someone makes or opens an open group, a space's open groups are listed or one is joined, the hook is POSTed {"event": "messaging.can_message", "action": "direct" | "group_add" | "open_group" | "list_open" | "join", "from_user_id", "to_user_ids", "conversation_id", "space"}, signed as Standard Webhooks sign (webhook-id, webhook-timestamp, webhook-signature), and answers 200 {"allowed_user_ids": [\u2026]}: whom of to_user_ids from_user_id may reach \u2014 for open_group, list_open and join, to_user_ids is [from_user_id], and naming them lets them in. space is the conversation's (absent: none).`,"A new hook takes --url (an https URL on the public internet). Its signing secret is printed this once, or kept in the environment's secrets with --secret-to (production; --dev: development) and printed nowhere. A hook changed keeps its secret: palbase messaging:hook:secret:rotate makes a new one."],flags:{url:{type:"string",value:"url",description:"where the hook is asked"},"timeout-ms":{type:"string",value:"ms",description:"how long it is waited for: 100 to 5000 (a new hook: 2000)"},"on-error":{type:"string",value:"allow|deny",description:"when it does not answer: allow everyone, or deny and the app is told to try again (a new hook: deny)"},...au},scope:"environment",async run(e){let t=e.flags.url;if(t!==void 0&&t.trim()==="")throw m("--url is where the hook is asked: an https URL");let n;if(e.flags["timeout-ms"]!==void 0&&(n=Number(e.flags["timeout-ms"]),!/^[0-9]+$/.test(String(e.flags["timeout-ms"]))||n<100||n>5e3))throw m("--timeout-ms is a whole number from 100 to 5000");let s=e.flags["on-error"];if(s!==void 0&&s!=="allow"&&s!=="deny")throw m("--on-error is allow or deny");if(t===void 0&&n===void 0&&s===void 0)throw m("nothing to change","pass --url, --timeout-ms or --on-error");let a=ru(e),r=await f(e),i=await g(e,r,"GET",tt),o=i.hook;if(!o&&t===void 0)throw m("the environment has no hook yet: --url names where it is asked");if(o&&a!==void 0)throw m("the hook has its signing secret, and a change keeps it: --secret-to goes with a new hook","palbase messaging:hook:secret:rotate --secret-to <NAME> makes a new secret and keeps it");let l={url:t??o.url},d=n??o?.timeout_ms;d!==void 0&&(l.timeout_ms=d);let p=s??o?.on_error;if(p!==void 0&&(l.on_error=p),o&&Oe(o,{...o,...l}))return ze(e,"the hook",i);let u=o?["url","timeout_ms","on_error"].filter(b=>l[b]!==o[b]):[];await v(e,r,o?`change the messaging hook: ${u.join(", ")}`:`set the messaging hook ${l.url}${a?`, its secret kept in ${a.name} (${a.set})`:""}`);let h=await g(e,r,"PUT",tt,{hook:l});await iu(e,r,h,a,o?"Changed the hook":"Set the hook")}},y_={name:"messaging:hook:remove",summary:"remove the hook: anyone may reach anyone, and its signing secret is forgotten",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",tt);if(!n.hook){e.out.result({settings:n,changed:[]},()=>e.out.line("Nothing to change: the environment has no hook."));return}await v(e,t,`remove the messaging hook ${n.hook.url}: anyone may reach anyone, and its signing secret is forgotten`);let s=await g(e,t,"PUT",tt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may reach anyone."))}},b_={name:"messaging:hook:secret:rotate",summary:"make the hook's signing secret anew: printed this once, or kept in a secret with --secret-to; the old one signs nothing more",usage:"[--secret-to <NAME> [--dev]]",details:["The hook's calls are signed with the new secret alone from now: the endpoint verifies them with it (a machine reads a secret kept with --secret-to at its next start)."],flags:au,scope:"environment",async run(e){let t=ru(e),n=await f(e);await v(e,n,`make the messaging hook's signing secret anew; the old one signs nothing more${t?`; the new one kept in ${t.name} (${t.set})`:""}`);let s=await g(e,n,"POST",`${tt}/hook-secret`);await iu(e,n,s,t,"Made the signing secret anew for the hook")}},ou={name:"messaging",async read(e,t){let n=await g(e,t,"GET",tt),s={};for(let a of eo)s[`settings.${a}`]=n[a];return n.hook&&(s.hook={url:n.hook.url,timeout_ms:n.hook.timeout_ms,on_error:n.hook.on_error}),s},async copy(e,t,n,s){let a=pe(n,"settings.");a.length>0&&await V(s,`set the messaging settings ${a.map(l=>l.name).join(", ")}`,async()=>{await g(e,t,"PUT",tt,Object.fromEntries(a.map(l=>[l.name,l.from])))});let r=n.find(l=>l.key==="hook");if(!r)return;if(r.from===void 0){await V(s,`remove the hook ${r.to.url}`,async()=>{await g(e,t,"PUT",tt,{hook:null})});return}let i=r.from,o;await V(s,`${r.change==="differs"?"change":"set"} the hook ${i.url}`,async()=>{o=(await g(e,t,"PUT",tt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},lu=[g_,h_,m_,f_,y_,b_];var nt="/calls/settings",w_=/^[A-Z][A-Z0-9_]{0,127}$/,no=["max_participants","calls_per_hour"];function ns(e,t){switch(e){case"max_participants":return`${String(t)}: a call holds at most this many, people and bots together`;case"calls_per_hour":return`${String(t)}: one person may start at most this many calls in an hour`;case"hook":return t?pu(t):"none: anyone may call anyone (blocks aside)"}}function pu(e){return`${A(e.url)} (waited for ${e.timeout_ms??2e3} ms; when it does not answer, ${e.on_error==="allow"?"everyone is allowed":"no one is: the call is refused"})`}function v_(e,t){let n=[...no.map(a=>[a,t[a]]),["hook",t.hook]],s=Math.max(...n.map(([a])=>a.length));for(let[a,r]of n)e.out.line(`${a.padEnd(s)} ${ns(a,r)}`)}function to(e,t,n,s){if(e===void 0)return;let a=Number(e);if(!/^[0-9]+$/.test(String(e))||a<n||a>s)throw m(`--${t} is a whole number from ${n} to ${s}`);return a}var __={name:"calls:settings:get",summary:"how the environment runs its calls: how many a call holds, how many calls a person may start in an hour, the hook",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",nt);e.out.result(n,()=>v_(e,n))}},k_={name:"calls:settings:set",summary:"change how the environment runs its calls: a setting left out is left as it is",usage:"[--max-participants <n>] [--calls-per-hour <n>]",details:["A call holds at most --max-participants, people and the project's bots together, as the setting was when the call was made: a change holds for the calls made after it.","Every call's media, one to one or in a group, goes through the platform's media server: no device learns another's address.","A person who has started --calls-per-hour calls in the last hour is refused another (too_many_calls).","Only what changes is sent: a change made beside this one to another setting stays."],flags:{"max-participants":{type:"string",value:"n",description:"how many a call holds, people and bots together (2\u2013100)"},"calls-per-hour":{type:"string",value:"n",description:"how many calls one person may start in an hour (1\u20131000)"}},scope:"environment",async run(e){let t={},n=to(e.flags["max-participants"],"max-participants",2,100);n!==void 0&&(t.max_participants=n);let s=to(e.flags["calls-per-hour"],"calls-per-hour",1,1e3);if(s!==void 0&&(t.calls_per_hour=s),Object.keys(t).length===0)throw m("nothing to change","pass --max-participants or --calls-per-hour");let a=await f(e),r=await g(e,a,"GET",nt),i=no.filter(l=>l in t&&!Oe(r[l],t[l]));if(i.length===0)return ze(e,"the call settings",r);await v(e,a,`set the call settings ${i.join(", ")}`);let o=await g(e,a,"PUT",nt,Object.fromEntries(i.map(l=>[l,t[l]])));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${ns(l,r[l])} \u2192 ${ns(l,o[l])}`)})}},du={"secret-to":{type:"string",value:"NAME",description:"keep the new signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}};function uu(e){let t=e.flags["secret-to"];if(t!==void 0&&!w_.test(t))throw m(`--secret-to ${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&t===void 0)throw m("--dev goes with --secret-to <NAME>");return t===void 0?void 0:{name:t,set:e.flags.dev===!0?"development":"production"}}async function cu(e,t,n,s,a){let{hook_secret:r,...i}=n;if(r===void 0){e.out.result({settings:i},()=>e.out.line(`${a}: ${ns("hook",i.hook)}.`));return}if(s!==void 0){let o=await g(e,t,"PUT",`/secrets/${s.set}/${s.name}`,{value:r});e.out.result({settings:i,secret_to:{set:s.set,name:s.name,version:o.version}},()=>e.out.line(`${a}: ${ns("hook",i.hook)}; its signing secret is ${s.name} (${s.set}, v${o.version}) in ${N(t)}.`));return}e.out.result(n,()=>{e.out.line(`${a}: ${ns("hook",i.hook)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}var S_={name:"calls:hook:get",summary:"the hook asked whom one may call: where it is, how long it is waited for, what is done when it does not answer (never its secret)",scope:"environment",async run(e){let t=await f(e),{hook:n}=await g(e,t,"GET",nt);e.out.result({hook:n},()=>e.out.line(n?pu(n):"No hook: anyone may call anyone (blocks aside). palbase calls:hook:set --url <url> sets one."))}},$_={name:"calls:hook:set",summary:"set the hook asked whom one may call, or change it; a new hook's signing secret is printed this once, or kept in a secret with --secret-to",usage:"[--url <url>] [--timeout-ms <ms>] [--on-error allow|deny] [--secret-to <NAME> [--dev]]",details:['Before a call is made, or more people are asked into one, the hook is POSTed {"event": "calls.can_call", "action": "start" | "invite", "from_user_id", "to_user_ids", "video", "call_id", "conversation_id"}, signed as Standard Webhooks sign (webhook-id, webhook-timestamp, webhook-signature), and answers 200 {"allowed_user_ids": [\u2026]}: only those are called, and with none the call is refused.',"A new hook takes --url (a URL on the public internet). Its signing secret is printed this once, or kept in the environment's secrets with --secret-to (production; --dev: development) and printed nowhere. A hook changed keeps its secret: palbase calls:hook:secret:rotate makes a new one."],flags:{url:{type:"string",value:"url",description:"where the hook is asked"},"timeout-ms":{type:"string",value:"ms",description:"how long it is waited for: 100 to 5000 (a new hook: 2000)"},"on-error":{type:"string",value:"allow|deny",description:"when it does not answer: allow everyone, or deny and the call is refused (a new hook: deny)"},...du},scope:"environment",async run(e){let t=e.flags.url;if(t!==void 0&&t.trim()==="")throw m("--url is where the hook is asked: a URL");let n=to(e.flags["timeout-ms"],"timeout-ms",100,5e3),s=e.flags["on-error"];if(s!==void 0&&s!=="allow"&&s!=="deny")throw m("--on-error is allow or deny");if(t===void 0&&n===void 0&&s===void 0)throw m("nothing to change","pass --url, --timeout-ms or --on-error");let a=uu(e),r=await f(e),i=await g(e,r,"GET",nt),o=i.hook;if(!o&&t===void 0)throw m("the environment has no hook yet: --url names where it is asked");if(o&&a!==void 0)throw m("the hook has its signing secret, and a change keeps it: --secret-to goes with a new hook","palbase calls:hook:secret:rotate --secret-to <NAME> makes a new secret and keeps it");let l={url:t??o.url},d=n??o?.timeout_ms;d!==void 0&&(l.timeout_ms=d);let p=s??o?.on_error;if(p!==void 0&&(l.on_error=p),o&&Oe(o,{...o,...l}))return ze(e,"the hook",i);let u=o?["url","timeout_ms","on_error"].filter(b=>l[b]!==o[b]):[];await v(e,r,o?`change the calls hook: ${u.join(", ")}`:`set the calls hook ${l.url}${a?`, its secret kept in ${a.name} (${a.set})`:""}`);let h=await g(e,r,"PUT",nt,{hook:l});await cu(e,r,h,a,o?"Changed the hook":"Set the hook")}},P_={name:"calls:hook:remove",summary:"remove the hook: anyone may call anyone (blocks aside), and its signing secret is forgotten",scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",nt);if(!n.hook){e.out.result({settings:n,changed:[]},()=>e.out.line("Nothing to change: the environment has no hook."));return}await v(e,t,`remove the calls hook ${n.hook.url}: anyone may call anyone, and its signing secret is forgotten`);let s=await g(e,t,"PUT",nt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may call anyone (blocks aside)."))}},A_={name:"calls:hook:secret:rotate",summary:"make the hook's signing secret anew: printed this once, or kept in a secret with --secret-to; the old one signs nothing more",usage:"[--secret-to <NAME> [--dev]]",details:["The hook's calls are signed with the new secret alone from now: the endpoint verifies them with it (a machine reads a secret kept with --secret-to at its next start)."],flags:du,scope:"environment",async run(e){let t=uu(e),n=await f(e);await v(e,n,`make the calls hook's signing secret anew; the old one signs nothing more${t?`; the new one kept in ${t.name} (${t.set})`:""}`);let s=await g(e,n,"POST",`${nt}/hook-secret`);await cu(e,n,s,t,"Made the signing secret anew for the hook")}},gu={name:"calls",async read(e,t){let n=await g(e,t,"GET",nt),s={};for(let a of no)s[`settings.${a}`]=n[a];return n.hook&&(s.hook={url:n.hook.url,timeout_ms:n.hook.timeout_ms,on_error:n.hook.on_error}),s},async copy(e,t,n,s){let a=pe(n,"settings.");a.length>0&&await V(s,`set the call settings ${a.map(l=>l.name).join(", ")}`,async()=>{await g(e,t,"PUT",nt,Object.fromEntries(a.map(l=>[l.name,l.from])))});let r=n.find(l=>l.key==="hook");if(!r)return;if(r.from===void 0){await V(s,`remove the hook ${r.to.url}`,async()=>{await g(e,t,"PUT",nt,{hook:null})});return}let i=r.from,o;await V(s,`${r.change==="differs"?"change":"set"} the hook ${i.url}`,async()=>{o=(await g(e,t,"PUT",nt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},hu=[__,k_,S_,$_,P_,A_];var R_=/^[a-z][a-z0-9-]{1,62}$/;function ss(e){if(!R_.test(e))throw m(`${JSON.stringify(e)} is not a bucket's name: lowercase letters, digits and dashes, starting with a letter (2\u201363)`);return`/storage/buckets/${k(e)}`}function ao(e){if(e==null)return"platform (5 GiB)";for(let[t,n]of[["GiB",2**30],["MiB",2**20],["KiB",2**10]])if(e>=n&&e%n===0)return`${e/n} ${t}`;return`${e} B`}function T_(e){if(e===""||e==="platform")return null;let t=/^(\d+(?:\.\d+)?)\s*(b|kb|kib|mb|mib|gb|gib)?$/i.exec(e.trim());if(!t)throw m(`--max-size ${JSON.stringify(e)} is not a size: 10MB, 512KiB, 1GB, a number of bytes, or platform`);let n=(t[2]??"b").toLowerCase(),s={b:1,kb:1e3,kib:2**10,mb:1e6,mib:2**20,gb:1e9,gib:2**30};return Math.round(Number(t[1])*s[n])}function so(e){return{public:e.public,file_size_limit:e.file_size_limit??null,allowed_mime_types:e.allowed_mime_types??[],variants:e.variants??[]}}async function E_(e,t,n){try{return await g(e,t,"GET",ss(n))}catch(s){if(s instanceof j&&s.status===404)return;throw s}}var mu={name:"storage:buckets:list",summary:"the environment's buckets: who may read each, the largest file and the types it takes",scope:"environment",async run(e){let t=await f(e),{buckets:n}=await g(e,t,"GET","/storage/buckets");e.out.result({buckets:n},()=>{n.length===0&&e.out.line("No bucket yet: palbase storage:buckets:set <name> makes one."),e.out.table(n,[{header:"bucket",value:s=>s.name},{header:"read by",value:s=>s.public?"anyone":"signed URLs, rules"},{header:"largest file",value:s=>ao(s.file_size_limit)},{header:"types",value:s=>s.allowed_mime_types.length===0?"any":s.allowed_mime_types.join(", ")},{header:"renditions",value:s=>s.variants.map(a=>a.name).join(", ")}])})}},fu={name:"storage:buckets:get",summary:"a bucket's declaration in full, with its renditions",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=await g(e,t,"GET",ss(e.args[0]));e.out.result(n,()=>{e.out.line(n.name),e.out.line(` read by ${n.public?"anyone, at /storage/public/":"signed URLs and the rules"}`),e.out.line(` largest file ${ao(n.file_size_limit)}`),e.out.line(` types ${n.allowed_mime_types.length===0?"any":n.allowed_mime_types.join(", ")}`);for(let s of n.variants)e.out.line(` rendition ${s.name}: ${s.width}\xD7${s.height} ${s.fit}, ${s.format}${s.quality?` q${s.quality}`:""}`)})}},yu={name:"storage:buckets:set",summary:"make a bucket, or change one: public or private, the largest file, the content types, its renditions",usage:"<bucket> [--public|--private] [--max-size <size>] [--types <a/b,c/*>] [--variants-file <path>]",details:["What is not given is kept as it is (a new bucket: private, the platform's size, any type, no rendition)."],args:{min:1,max:1},flags:{public:{type:"boolean",description:"anyone may read its files at /storage/public/"},private:{type:"boolean",description:"its files are read with signed URLs and the rules"},"max-size":{type:"string",value:"size",description:"the largest file: 10MB, 512KiB, 1GB, bytes, or platform"},types:{type:"string",value:"list",description:'the content types it takes, comma-separated (image/* a family); "" for any'},"variants-file":{type:"string",value:"path",description:"its renditions, a JSON list of {name, width, height, fit, format, quality}"}},scope:"environment",async run(e){let t=await f(e),n=e.args[0];if(e.flags.public===!0&&e.flags.private===!0)throw m("--public and --private are one or the other");let s=await E_(e,t,n),a=so(s??{name:n,public:!1,allowed_mime_types:[],variants:[]});if(e.flags.public===!0&&(a.public=!0),e.flags.private===!0&&(a.public=!1),e.flags["max-size"]!==void 0&&(a.file_size_limit=T_(e.flags["max-size"])),e.flags.types!==void 0&&(a.allowed_mime_types=re(e.flags.types)??[]),e.flags["variants-file"]!==void 0){let i=ue(await be(e,e.flags["variants-file"],"the renditions"),"the renditions file"),o=Array.isArray(i)?i:i.variants;if(!Array.isArray(o))throw m("the renditions file is a JSON list of {name, width, height, fit, format, quality}");a.variants=o}if(s&&Oe(so(s),a)){e.out.result(s,()=>e.out.line(`Nothing to change: ${n} already is so.`));return}await v(e,t,`${s?"change":"make"} the bucket ${n} (${a.public?"public":"private"})`);let r=await g(e,t,"PUT",ss(n),a);e.out.result(r,()=>e.out.line(`${s?"Changed":"Made"} the bucket ${r.name}: ${r.public?"public":"private"}, files up to ${ao(r.file_size_limit)}, ${r.allowed_mime_types.length===0?"any type":r.allowed_mime_types.join(", ")}.`))}},bu={name:"storage:buckets:delete",summary:"delete an empty bucket (one that still holds files is refused)",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=ss(e.args[0]);await v(e,t,`delete the bucket ${e.args[0]}`),await g(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},wu={name:"storage",async read(e,t){let{buckets:n}=await g(e,t,"GET","/storage/buckets"),s={};for(let a of n){let r=so(a);s[`buckets.${a.name}`]={...r,allowed_mime_types:[...r.allowed_mime_types].sort()}}return s},async copy(e,t,n,s){for(let a of pe(n,"buckets."))a.from===void 0?await V(s,`delete the bucket ${a.name}`,async()=>{await g(e,t,"DELETE",ss(a.name))}):await V(s,`declare the bucket ${a.name}`,async()=>{await g(e,t,"PUT",ss(a.name),a.from)})}};var as=[_d,wu,Od,Jd,Xd,eu,ou,gu],ir=[nu,tu],vu={from:{type:"string",value:"env",description:"the environment compared from (slug or ref)"},to:{type:"string",value:"env",description:"the environment compared to (slug or ref)"},only:{type:"string",value:"modules",description:`only these, comma-separated: ${as.map(e=>e.name).join(",")} (the default), and ${ir.map(e=>e.name).join(",")} when named`}};async function _u(e){if(e.global.env!==void 0)throw m("the two environments are named with --from and --to, not -E");let t=e.flags.from,n=e.flags.to;if(t===void 0||n===void 0)throw m("name both environments: --from <env> --to <env>");let s=await De(e.studio),a;if(e.global.project!==void 0)a=_n(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new y("no_target","which project? none is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});a=s.filter(d=>d.project_id===l.target.project_id)}let r=l=>{let d=a.find(p=>p.slug===l||p.ref===l);if(!d)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(l)}`,{hint:`its environments: ${a.map(p=>p.slug).join(", ")}`});return $e(d)},i=r(t),o=r(n);if(i.env_ref===o.env_ref)throw m("--from and --to name the same environment");return{from:i,to:o}}function ku(e){let t=re(e.flags.only);if(t===void 0)return as;let n=[...as,...ir];for(let s of t)if(!n.some(a=>a.name===s))throw m(`no module ${JSON.stringify(s)}`,`the modules: ${n.map(a=>a.name).join(", ")}`);return n.filter(s=>t.includes(s.name))}function C_(e,t){let n=[...new Set([...Object.keys(e),...Object.keys(t)])].sort(),s=[];for(let a of n){let r=a in e,i=a in t;r&&!i?s.push({key:a,change:"only_in_from",from:e[a]}):!r&&i?s.push({key:a,change:"only_in_to",to:t[a]}):Oe(e[a],t[a])||s.push({key:a,change:"differs",from:e[a],to:t[a]})}return s}async function ro(e,t,n,s){let a=l=>Promise.all(t.map(d=>d.read(e,l))),[r,i]=await Promise.all([a(n),a(s)]),o=Object.fromEntries(t.map((l,d)=>[l.name,C_(r[d],i[d])]));return{from:n,to:s,modules:o,count:Object.values(o).reduce((l,d)=>l+d.length,0)}}function io(e){return{from:{env:e.from.env_slug,ref:e.from.env_ref},to:{env:e.to.env_slug,ref:e.to.env_ref},equal:e.count===0,differences:e.count,modules:e.modules}}function rr(e){let t=JSON.stringify(e)??"undefined";return t.length>140?`${t.slice(0,139)}\u2026`:t}function oo(e,t){let n=t.from.env_slug,s=t.to.env_slug;for(let[r,i]of Object.entries(t.modules))if(i.length!==0){e.out.line(`${r} (${i.length})`);for(let o of i)o.change==="differs"?e.out.line(` ~ ${o.key}: ${n} ${rr(o.from)} \xB7 ${s} ${rr(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${rr(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${rr(o.to)}`)}let a=Object.keys(t.modules).join(", ");e.out.line(t.count===0?`${n} and ${s} have the same settings (${a}).`:`${t.count} difference(s) between ${n} and ${s} (${a}).`)}var j_={name:"config:diff",summary:"how two environments' module settings differ (auth, storage, flags, notify, realtime, analytics, messaging, calls; machines, databases when named)",usage:"--from <env> --to <env> [--only <modules>]",details:["- a key only in --from, + only in --to, ~ in both with other values. Ids, times and versions are not compared; a secret is compared as set or not, never by its value.","Exit code 0 when they are the same, 1 when they differ. --json gives each difference with both values."],flags:vu,scope:"environment",async run(e){let{from:t,to:n}=await _u(e),s=await ro(e,ku(e),t,n);return e.out.result(io(s),()=>oo(e,s)),s.count===0?0:1}},O_={name:"config:copy",summary:"make one environment's module settings equal to another's (secrets excepted: their names are said)",usage:"--from <env> --to <env> [--only <modules>]",details:["It shows the differences, asks before changing a production environment (-y confirms), changes the target, and compares again.","A secret is never copied: a provider's credentials and a sign-in client's secret are listed by name for you to set. A new hook or OAuth client gets its own secret, printed this once.","What the target holds that its users depend on is not taken by a copy: a bucket with files, a role users hold, a topic users follow are left and said.","--only machines,databases copies what they are declared as \u2014 a machine's source, size, copies, settings, secrets' name, database; a database's size, who may connect, backups \u2014 never a database's data (palbase db:copy) nor a secret's value. A machine or database only the target has is left serving.","Exit code 0 when the two are the same afterwards, 1 when a difference remains."],flags:vu,scope:"environment",async run(e){let{from:t,to:n}=await _u(e);return lo(e,ku(e),t,n)}};async function lo(e,t,n,s,a={}){let r=await ro(e,t,n,s);if(r.count===0)return e.out.result({...a,copied:[],left:[],shown_once:[],after:io(r)},()=>e.out.line(`Nothing to copy: ${n.env_slug} and ${s.env_slug} have the same settings.`)),0;e.out.json||oo(e,r);let i=t.filter(u=>r.modules[u.name].length>0).map(u=>u.name);await v(e,s,`make its ${i.join(", ")} settings equal to ${n.env_slug}'s (${r.count} difference(s))`);let o=[],l=[],d=[];e.out.json||e.out.line();for(let u of t){let h=r.modules[u.name];if(h.length===0)continue;let b={done(w){o.push(`${u.name}: ${w}`),e.out.json||e.out.line(`copied ${u.name}: ${w}`)},left(w){l.push(`${u.name}: ${w}`),e.out.json||e.out.line(`left ${u.name}: ${w}`)},shown(w,_){d.push({what:`${u.name}: ${w}`,value:_}),e.out.json||e.out.line(` ${u.name}: ${w} ${_} (shown this once)`)}};await u.copy(e,s,h,b)}let p=await ro(e,t,n,s);return e.out.result({...a,copied:o,left:l,shown_once:d,after:io(p)},()=>{e.out.line(),e.out.line("Compared again:"),oo(e,p)}),p.count===0?0:1}var Su=[ad,rd,id,od,pd,ld,dd,ud,cd,hd,md,fd,yd,bd,wd,vd,mu,fu,yu,bu,Pd,Ad,Rd,Td,Cd,jd,Ed,xd,Id,Ld,Dd,Nd,qd,Kd,Gd,Ud,zd,Bd,Fd,Wd,Vd,Hd,Yd,Zd,Qd,...lu,...hu,j_,O_];async function po(e){return(await e.studio.get("/orgs")).organizations}async function mt(e,t){let n=await po(e);if(t===void 0){if(n.length===1)return n[0];throw m(n.length===0?"you are in no organization yet: make one with palbase orgs:create <name>":"which organization? pass --org <name>",`yours: ${n.map(a=>a.name).join(", ")}`)}let s=n.filter(a=>a.id===t||a.name.localeCompare(t,void 0,{sensitivity:"accent"})===0);if(s.length===0)throw new y("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new y("ambiguous_org",`${JSON.stringify(t)} names more than one of your organizations`,{hint:"pass its id"});return s[0]}var $u={name:"orgs:list",summary:"your organizations and your role in each",scope:"account",async run(e){let t=await po(e);e.out.result({organizations:t},()=>{t.length===0&&e.out.line("You are in no organization yet: palbase orgs:create <name> makes one."),e.out.table(t,[{header:"name",value:n=>n.name},{header:"role",value:n=>n.role},{header:"id",value:n=>n.id}])})}},Pu={name:"orgs:create",summary:"make an organization; you are its owner",usage:"<name>",details:["With an account token in PALBASE_TOKEN, the token's maker is its owner and the token its admin: one organization more the token reaches."],args:{min:1,max:1},scope:"account",async run(e){let t=await e.studio.post("/orgs",{name:e.args[0]});e.out.result(t,()=>e.out.line(`Made the organization ${K(t.name)} (${t.id}).`))}},Au={name:"orgs:members:list",summary:"an organization's members, and the invitations still open (for its admins)",flags:{org:{type:"string",value:"org",description:"the organization (name or id); needed when you are in more than one"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=await e.studio.get(`/orgs/${t.id}/members`);e.out.result(n,()=>{e.out.table(n.members,[{header:"email",value:s=>s.email??""},{header:"role",value:s=>s.role},{header:"user",value:s=>s.user_id}]),n.invitations.length>0&&(e.out.line(),e.out.line("Invited:"),e.out.table(n.invitations,[{header:"email",value:s=>s.email},{header:"role",value:s=>s.role},{header:"invitation",value:s=>s.id}]))})}},Ru={name:"orgs:members:invite",summary:"invite someone to an organization by email; they accept in Studio",usage:"<email> [--role admin|member]",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"admin or member (default member)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--role is admin or member");let s=await e.studio.post(`/orgs/${t.id}/invitations`,{email:e.args[0],role:n});e.out.result(s,()=>e.out.line(`Invited ${K(s.email)} to ${K(t.name)} as ${s.role}.`))}},Tu={name:"orgs:members:remove",summary:"take a member (by email or user id) or an open invitation out of an organization",usage:"<email|user-id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.args[0],s=await e.studio.get(`/orgs/${t.id}/members`),a=s.members.find(i=>i.user_id===n||i.email!==null&&i.email.toLowerCase()===n.toLowerCase());if(a){await e.studio.request("DELETE",`/orgs/${D("the organization's id",t.id)}/members/${D("the member's id",a.user_id)}`),e.out.result({removed:a},()=>e.out.line(`${K(a.email??a.user_id)} is no longer in ${K(t.name)}.`));return}let r=s.invitations.find(i=>i.id===n||i.email.toLowerCase()===n.toLowerCase());if(!r)throw new y("no_such_member",`${JSON.stringify(n)} is neither a member of ${t.name} nor invited to it`);await e.studio.request("DELETE",`/orgs/${D("the organization's id",t.id)}/invitations/${D("the invitation's id",r.id)}`),e.out.result({withdrawn:r},()=>e.out.line(`The invitation of ${K(r.email)} is withdrawn.`))}},Eu={name:"tokens:list",summary:"an organization's access tokens (PALBASE_TOKEN for CI and agents): name, role, when they end and were last used \u2014 never their values",flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`);e.out.result({tokens:n},()=>{n.length===0?e.out.line(`${K(t.name)} has no access token yet: palbase tokens:create <name> makes one.`):e.out.table(n,[{header:"name",value:s=>K(s.name)},{header:"role",value:s=>s.role},{header:"token",value:s=>`pbo_\u2026${s.hint}`},{header:"ends",value:s=>s.expires_at.slice(0,10)},{header:"last used",value:s=>s.last_used_at?.slice(0,16).replace("T"," ")??"never"},{header:"id",value:s=>s.id}])})}},Cu={name:"orgs:audit",summary:"who changed what at an organization's environments, through Studio or the CLI, newest first \u2014 its admins'",usage:"[--org <org>] [--limit <n>]",details:["A module keeps a change made through Studio as Studio's key's: this says whose it was. What was sent is not kept."],flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.limit===void 0?50:Number(e.flags.limit);if(!Number.isInteger(n)||n<1||n>1e4)throw m("--limit is a whole number from 1 to 10000");let s=[],a=null;do{let r=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{before:a}:{}}),i=await e.studio.get(`/orgs/${t.id}/audit?${r}`);s.push(...i.entries),a=i.next_before}while(a&&s.length<n);e.out.result({entries:s},()=>{s.length===0?e.out.line(`Nothing changed at ${K(t.name)}'s environments yet.`):e.out.table(s,[{header:"when",value:r=>r.at.slice(0,16).replace("T"," ")},{header:"who",value:r=>K(r.actor_name??r.actor)},{header:"environment",value:r=>K(r.environment_ref)},{header:"what",value:r=>K(`${r.method} ${r.path}`)},{header:"answer",value:r=>String(r.status)}])})}},ju={name:"tokens:create",summary:"make an access token of an organization for CI or an agent; its value is shown this once",usage:"<name> [--role member|admin] [--days <n>]",details:["Give it to the CI as the secret PALBASE_TOKEN: the CLI acts with it in place of a sign-in. It reaches its organization's projects and environments at its role, and nothing of its people or tokens.","It ends after --days (default 90, at most 365), and goes when it is revoked or when the one who made it leaves the organization."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"member (default) or admin: what it may change"},days:{type:"string",value:"n",description:"how many days it is good for (default 90)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--role is member or admin");let s=e.flags.days===void 0?90:Number(e.flags.days);if(!Number.isInteger(s)||s<1||s>365)throw m("--days is a whole number from 1 to 365");let a=await e.studio.post(`/orgs/${t.id}/tokens`,{name:e.args[0],role:n,expires_in_days:s});e.out.result(a,()=>{e.out.line(`Made the access token ${K(a.name)} of ${K(t.name)} (${a.role}, until ${a.expires_at.slice(0,10)}).`),e.out.line(`token (shown this once \u2014 keep it as the CI's secret PALBASE_TOKEN): ${a.token}`)})}},Ou={name:"tokens:revoke",summary:"revoke an organization's access token (by its name or id): refused from its next request on",usage:"<name|id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`),s=n.filter(i=>i.id===e.args[0]||i.name===e.args[0]);if(s.length===0)throw new y("no_such_token",`${K(t.name)} has no access token ${JSON.stringify(e.args[0])}`,{hint:"palbase tokens:list shows them"});if(s.length>1)throw new y("ambiguous_token",`${JSON.stringify(e.args[0])} names more than one token`,{hint:"pass its id"});let a=`/orgs/${D("the organization's id",t.id)}/tokens/${D("the token's id",s[0].id)}`,{endpoints:r}=await e.studio.get(`${a}/webhooks`);await e.studio.request("DELETE",a),e.out.result({revoked:s[0].id,kept_webhooks:r},()=>{if(e.out.line(`Revoked the access token ${K(s[0].name)} of ${K(t.name)}.`),r.length>0){e.out.line("These webhook endpoints it made stay, and keep receiving events (palbase webhooks:delete <id> -E <env> removes one):");for(let i of r)e.out.line(` ${A(i.url)} \u2014 ${A(i.environment_ref)} (${A(i.id)})`)}})}},Mu={name:"projects:list",summary:"every project of your organizations, with its environments",scope:"account",async run(e){let t=await De(e.studio),n=await e.state.targetFor(e.io.cwd),s=(await e.studio.get("/projects")).projects,a=new Map(t.map(r=>[r.organization_id,r.org_name]));for(let r of await po(e))a.set(r.id,r.name);e.out.result({projects:s.map(r=>({...r,organization_name:a.get(r.organization_id)??null}))},()=>{s.length===0&&e.out.line("No project yet: palbase projects:create <name> makes one.");let r=s.flatMap(i=>i.environments.length===0?[{org:a.get(i.organization_id)??"",project:i.name,env:"(none)",ref:"",here:""}]:i.environments.map(o=>({org:a.get(i.organization_id)??"",project:i.name,env:o.slug,ref:o.ref,here:n?.target.env_ref===o.ref?"*":""})));e.out.table(r,[{header:"",value:i=>i.here},{header:"organization",value:i=>i.org},{header:"project",value:i=>i.project},{header:"env",value:i=>i.env},{header:"ref",value:i=>i.ref}])})}},xu={name:"projects:create",summary:"make a project with its first environment (main), and print its keys",usage:"<name> [--org <org>] [--env-name <name>]",details:["The first environment is the project's production one: its slug is always main.","Its secret key is printed this once; keys:create makes more."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},"env-name":{type:"string",value:"name",description:"what the first environment is called on screen (default Production)"}},scope:"account",async run(e){let t=await mt(e,e.flags.org),{project:n}=await e.studio.post(`/orgs/${t.id}/projects`,{name:e.args[0]}),s=await e.studio.post(`/projects/${n.id}/environments`,{name:e.flags["env-name"]??"Production"});e.out.result({project:n,environment:s.environment,keys:s.keys},()=>{e.out.line(`Made ${K(t.name)} \u203A ${K(n.name)} with its environment ${s.environment.slug} (ref ${s.environment.ref}).`),s.keys&&(e.out.line(`publishable key: ${s.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${s.keys.secret.key}`)),e.out.line(`palbase use ${n.name}/${s.environment.slug} chooses it for this directory.`)})}},Iu={name:"projects:delete",summary:"delete a project and every environment of it",usage:"<project> [-y]",details:["Its environments' data goes with them (the platform keeps it a week to bring back)."],args:{min:1,max:1},scope:"account",async run(e){let t=_n(await De(e.studio),e.args[0]),n=t[0];if(e.out.note(`${n.org_name} \u203A ${n.project_name}: delete the project and its environments ${t.map(s=>s.slug).join(", ")}`),!e.global.yes){if(!e.interactive)throw new y("confirmation_needed","deleting a project is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the project's name to delete it: ")).trim()!==n.project_name)throw new y("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/projects/${n.project_id}`),e.out.result({deleted:{project_id:n.project_id,environments:t.map(s=>s.ref)}},()=>e.out.line(`Deleted ${K(n.project_name)}.`))}},Lu={name:"envs:list",summary:"a project's environments (the project chosen here, or -P)",scope:"environment",async run(e){let t=await qt(e);e.out.result({environments:t},()=>e.out.table(t,[{header:"slug",value:n=>n.slug},{header:"name",value:n=>n.name},{header:"ref",value:n=>n.ref},{header:"your role",value:n=>n.role}]))}},Du={name:"envs:create",summary:"add an environment to a project (staging, a developer's own)",usage:"<slug> [--name <name>] [--from <env> [--with-cloud]]",details:["Each environment is a project of its own on the platform: its users, data, machines and keys are its own.","--from opens it with another environment's module settings (palbase config:copy: secrets are named, not copied); --with-cloud its machines and databases too \u2014 declared alike, built from the same branches, with no data (palbase db:copy brings it)."],args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called on screen (default: the slug)"},from:{type:"string",value:"env",description:"open it with this environment's settings (slug or ref)"},"with-cloud":{type:"boolean",description:"with --from: its machines and databases too (the new environment's GitHub link builds the machines)"}},scope:"environment",async run(e){let t=await M_(e),n=e.args[0],s=e.flags.from;if(e.flags["with-cloud"]===!0&&s===void 0)throw m("--with-cloud goes with --from <env>");let a=s===void 0?[]:(await De(e.studio)).filter(d=>d.project_id===t.project_id),r=s===void 0?void 0:a.find(d=>d.slug===s||d.ref===s);if(s!==void 0&&!r)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(s)}`,{hint:`its environments: ${a.map(d=>d.slug).join(", ")}`});let i=await e.studio.post(`/projects/${t.project_id}/environments`,{name:e.flags.name??n,slug:n}),o=()=>{e.out.line(`Made ${K(t.project_name)} \u203A ${i.environment.slug} (ref ${i.environment.ref}).`),i.keys&&(e.out.line(`publishable key: ${i.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${i.keys.secret.key}`))};if(!r){e.out.result(i,o);return}e.out.json||o();let l=$e({...r,ref:i.environment.ref,slug:i.environment.slug,name:i.environment.name});return e.out.json||e.out.line(),lo(e,e.flags["with-cloud"]===!0?[...as,...ir]:as,$e(r),l,{made:i})}},Nu={name:"envs:delete",summary:"delete an environment of a project, with its data",usage:"<slug>",args:{min:1,max:1},scope:"environment",async run(e){let n=(await qt(e)).find(r=>r.slug===e.args[0]||r.ref===e.args[0]);if(!n)throw new y("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=$e(n);if(e.out.note(`${N(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new y("confirmation_needed","deleting an environment is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the environment's slug to delete it: ")).trim()!==n.slug)throw new y("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/environments/${n.ref}`);let a=await e.state.targetFor(e.io.cwd);a?.target.env_ref===n.ref&&await e.state.clearTarget(a.dir),e.out.result({deleted:n.ref},()=>e.out.line(`Deleted ${N(s)}.`))}},qu={name:"use",summary:"choose the environment this directory acts on (kept on this computer)",usage:"[<project>[/<env>] | <org>/<project>/<env>] [--clear]",details:["Commands run in this directory, or a directory under it, then act on that environment unless -P/-E say otherwise.","Nothing is written into the project: two checkouts of it can each act on their own environment.","With no argument and a person at the terminal, it lists your environments to pick from."],args:{min:0,max:1},flags:{clear:{type:"boolean",description:"forget the choice made for this directory"}},scope:"account",async run(e){if(e.flags.clear===!0){let n=await e.state.clearTarget(e.io.cwd);e.out.result({cleared:n},()=>e.out.line(n?"This directory acts on no environment now.":"No environment was chosen for this directory."));return}let t;if(e.args.length===0)t=await f(e);else{let n=await De(e.studio);t=$e(x_(n,e.args[0]))}await e.state.setTarget(e.io.cwd,t),e.out.result({target:t,dir:e.io.cwd},()=>e.out.line(`This directory acts on ${N(t)}.`))}},Ku={name:"status",summary:"the environment this directory acts on: who you are there, its machines and databases",scope:"environment",async run(e){let t=await f(e),n=await e.studio.get(`/environments/${t.env_ref}`),s=d=>ye(e.studio,t.env_ref,"GET",d).then(p=>p.body,p=>p instanceof j?null:Promise.reject(p)),[a,r]=await Promise.all([s("/machines?limit=100"),s("/databases?limit=100")]),i=a?.machines??[],o=r?.databases??[],l=await e.state.targetFor(e.io.cwd);e.out.result({target:t,role:n.role,address:`https://${t.env_ref}.${e.cloud.domain}`,machines:i,databases:o,chosen_for:l?.dir??null},()=>{e.out.line(N(t)),e.out.line(` ref ${t.env_ref} \xB7 your role ${n.role} \xB7 https://${t.env_ref}.${e.cloud.domain}`),l&&l.target.env_ref===t.env_ref&&e.out.line(` chosen for ${l.dir}`),e.out.line(` machines: ${i.length===0?"none":i.map(d=>`${d.name} (${d.size}, ${d.state})`).join(", ")}`),e.out.line(` databases: ${o.length===0?"none":o.map(d=>`${d.name} (${d.size}, ${d.state})`).join(", ")}`)})}},Gu={name:"open",summary:"open the environment in Studio (its overview, or a section: auth, storage, flags, \u2026)",usage:"[section]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0]?`/${e.args[0].replace(/^\/+/,"")}`:"",s=`${e.cloud.studioWeb}/projects/${t.project_id}/environments/${t.env_ref}${n}`,a=e.interactive&&await e.io.openBrowser(s);e.out.result({url:s,opened:a},()=>e.out.line(a?`Opened ${s}`:s))}},Uu={name:"keys:list",summary:"the environment's API keys (never their values)",scope:"environment",async run(e){let t=await f(e),{keys:n}=await e.studio.get(`/environments/${t.env_ref}/keys`);e.out.result({keys:n},()=>e.out.table(n,[{header:"id",value:s=>s.id},{header:"kind",value:s=>s.kind},{header:"label",value:s=>s.label},{header:"ends",value:s=>s.hint?`\u2026${s.hint}`:""},{header:"state",value:s=>s.revoked_at?`revoked ${s.revoked_at}`:s.expires_at?`until ${s.expires_at}`:"active"},{header:"created",value:s=>s.created_at}]))}},zu={name:"keys:create",summary:"make an API key of the environment; its value is shown this once",usage:"--kind publishable|secret [--label <label>]",flags:{kind:{type:"string",value:"kind",description:"publishable (ships in apps) or secret (servers only)"},label:{type:"string",value:"label",description:"what it is for, as Studio lists it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!=="publishable"&&t!=="secret")throw m("--kind is publishable or secret");let n=await f(e);await v(e,n,`make a ${t} key`);let s=await e.studio.post(`/environments/${n.env_ref}/keys`,{kind:t,label:e.flags.label??""});e.out.result(s,()=>{e.out.line(`${s.kind} key ${s.id}${s.kind==="secret"?" (shown this once)":""}:`),e.out.line(s.key)})}},Bu={name:"keys:revoke",summary:"revoke an API key of the environment; what holds it is refused from then on",usage:"<key-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=`/environments/${D("the environment's ref",t.env_ref)}/keys/${D("the key's id",e.args[0])}`;await v(e,t,`revoke the key ${e.args[0]}`),await e.studio.request("DELETE",n),e.out.result({revoked:e.args[0]},()=>e.out.line(`Revoked ${e.args[0]}.`))}};async function M_(e){return(await qt(e))[0]}async function qt(e){if(vn(e.io))return[await Ci(e)];let t=await De(e.studio);if(e.global.project!==void 0)return _n(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new y("no_target","no project is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});let s=t.filter(a=>a.project_id===n.target.project_id);if(s.length===0)throw new y("no_such_project",`the project chosen here (${n.target.project_name}) is not among yours any more`,{hint:"palbase use another"});return s}function x_(e,t){let n=t.split("/");if(n.length>3||n.some(r=>r===""))throw m(`${JSON.stringify(t)} is not project, project/env or org/project/env`);let s=n.length===3?[{project:`${n[0]}/${n[1]}`,env:n[2]}]:n.length===2?[{project:n[0],env:n[1]},{project:t}]:[{project:t}],a;for(let r of s){let i;try{i=_n(e,r.project)}catch(o){a=o;continue}if(r.env!==void 0){let o=i.find(l=>l.slug===r.env||l.ref===r.env);if(o)return o;a=new y("no_such_environment",`${i[0].project_name} has no environment ${JSON.stringify(r.env)}`,{hint:`its environments: ${i.map(l=>l.slug).join(", ")}`});continue}if(i.length===1)return i[0];throw new y("which_environment",`${i[0].project_name} has more than one environment`,{hint:`name one: ${i.map(o=>`${o.project_name}/${o.slug}`).join(", ")}`})}throw a}import{createReadStream as I_,openAsBlob as L_}from"node:fs";import{lstat as D_,readdir as N_,realpath as Fu,stat as q_}from"node:fs/promises";import{createHash as K_}from"node:crypto";import{join as G_,relative as Ju,resolve as U_,sep as Wu}from"node:path";var uo=2e4,z_=25<<20,B_=1<<30,F_=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,J_=6,Xs={previews:{type:"boolean",description:"build each pull request against its branch into a preview of its own, in this environment with its settings \u2014 production's too"},"no-previews":{type:"boolean",description:"preview no pull request from now on"},"preview-forks":{type:"boolean",description:"preview a fork's pull requests without asking (fork protection off): anyone's code runs with this environment's settings and secrets \u2014 a person's to do, signed in with palbase login"},"no-preview-forks":{type:"boolean",description:"a fork's pull request waits for an admin to authorize each push (fork protection, the default): previews:authorize"}};function Qs(e,t){e.flags.previews===!0&&(t.previews=!0),e.flags["no-previews"]===!0&&(t.previews=!1),e.flags["preview-forks"]===!0&&(t.preview_forks=!0),e.flags["no-preview-forks"]===!0&&(t.preview_forks=!1)}function or(e,t){if(e.out.line(`${t.host} ${t.state}${t.redirect_to?` \u2192 redirects to ${t.redirect_to} (${t.redirect_status??308})`:""}`),t.state!=="active"){e.out.line(` Add to ${t.host}'s DNS:`);for(let n of t.records){let s=n.type==="CNAME"&&t.apex?"ALIAS (or ANAME, or a flattened CNAME: the apex takes no CNAME)":n.type;e.out.line(` ${s} ${n.name} ${n.value}`)}for(let n of t.problems)e.out.line(` - ${n}`)}}function co(e,t,n,s){let a={name:`${t}:domains`,summary:`a ${e}'s own domains: whether each answers, and what its DNS still needs`,usage:`[--${e} <name>]`,flags:s,scope:"environment",async run(l){let d=await f(l),{domains:p}=await g(l,d,"GET",`${await n(l,d)}/domains`);l.out.result({domains:p},()=>{p.length===0&&l.out.line(`No domain yet: palbase ${t}:domains:add <host> adds one.`);for(let u of p)or(l,u)})}},r={name:`${t}:domains:add`,summary:`have a ${e} answer at a domain of your own too (www.example.com, or example.com \u2014 with its www)`,usage:`<host> [--${e} <name>] [--primary www|apex | --only] [--redirect-status 301|302|307|308]`,details:["It answers once its DNS points at the platform and says it is this environment's (the TXT record): both are printed.","At an apex (example.com) its www is added too, and one redirects to the other, as Vercel does: the apex to www.example.com unless --primary apex; --only adds the domain alone.","An apex takes an A record at the platform's address, which any DNS host has; a subdomain a CNAME. A domain is one environment's until it is removed there."],args:{min:1,max:1},flags:{...s,primary:{type:"string",value:"www|apex",description:"at an apex: the address visitors end up at, the other redirecting to it (default: www)"},only:{type:"boolean",description:"add the domain alone: no www, no redirect"},"redirect-status":{type:"string",value:"code",description:"the redirect's status: 301, 302, 307 or 308 (default: 308)"}},scope:"environment",async run(l){let d=l.args[0].trim().toLowerCase().replace(/\.$/,""),p=l.flags.primary??"www";if(p!=="www"&&p!=="apex")throw m("--primary is www or apex");if(l.flags.only===!0&&l.flags.primary!==void 0)throw m("--only adds the domain alone: no --primary");let u=Vu(l.flags["redirect-status"]),h=await f(l),b=await n(l,h),w=`www.${d}`,_=l.flags.only!==!0&&!d.startsWith("www.");await v(l,h,_?`add the domain ${d} \u2014 at an apex, with ${w}, ${p==="www"?`${d} redirecting to ${w}`:`${w} redirecting to ${d}`}`:`add the domain ${d}`);let T=await g(l,h,"POST",`${b}/domains`,{host:d});if(!_||!T.apex){l.out.result(T,()=>or(l,T));return}let S=P=>({redirect_to:P,...u!==void 0?{redirect_status:u}:{}}),$;p==="www"?($=await W_(l,h,b,{host:w}),T=await g(l,h,"PATCH",`${b}/domains/${D("the domain",d)}`,S(w))):$=await V_(l,h,b,w,S(d));let R=p==="www"?[$,T]:[T,$];l.out.result({domains:R},()=>R.forEach(P=>or(l,P)))}},i={name:`${t}:domains:redirect`,summary:`send a domain's visitors to another domain of the ${e}'s \u2014 the same path and query there \u2014 or, with --none, have it answer itself`,usage:`<host> (--to <host> [--status 301|302|307|308] | --none) [--${e} <name>]`,details:["It holds at once. A domain redirects to one that answers itself, and one others redirect to answers itself."],args:{min:1,max:1},flags:{...s,to:{type:"string",value:"host",description:`another domain of the ${e}'s`},status:{type:"string",value:"code",description:"301, 302, 307 or 308 (default: 308)"},none:{type:"boolean",description:"redirect nowhere: the domain answers itself"}},scope:"environment",async run(l){let d=l.args[0].trim().toLowerCase().replace(/\.$/,""),p=l.flags.to?.trim().toLowerCase().replace(/\.$/,"");if(p===void 0==(l.flags.none!==!0))throw m("name where it redirects (--to <host>), or --none");let u=Vu(l.flags.status);if(l.flags.none===!0&&u!==void 0)throw m("--status goes with --to");let h=await f(l),b=await n(l,h);await v(l,h,p?`redirect ${d} to ${p}`:`have ${d} answer itself`);let w=p?{redirect_to:p,...u!==void 0?{redirect_status:u}:{}}:{redirect_to:null},_=await g(l,h,"PATCH",`${b}/domains/${D("the domain",d)}`,w);l.out.result(_,()=>or(l,_))}},o={name:`${t}:domains:remove`,summary:`stop a ${e} answering at a domain: at once, and another environment may add it`,usage:`<host> [--${e} <name>]`,args:{min:1,max:1},flags:s,scope:"environment",async run(l){let d=await f(l),p=await n(l,d);await v(l,d,`remove the domain ${l.args[0]}`),await g(l,d,"DELETE",`${p}/domains/${D("the domain",l.args[0].toLowerCase())}`),l.out.result({removed:l.args[0]},()=>l.out.line(`${l.args[0]} answers no more.`))}};return[a,r,i,o]}function Vu(e){if(e===void 0)return;let t=Number(e);if(![301,302,307,308].includes(t))throw m("a redirect's status is 301, 302, 307 or 308");return t}async function W_(e,t,n,s){try{return await g(e,t,"POST",`${n}/domains`,s)}catch(a){if(!(a instanceof j&&a.code==="domain_exists"))throw a;let{domains:r}=await g(e,t,"GET",`${n}/domains`),i=r.find(o=>o.host===s.host);if(!i)throw a;return i}}async function V_(e,t,n,s,a){try{return await g(e,t,"POST",`${n}/domains`,{host:s,...a})}catch(r){if(!(r instanceof j&&r.code==="domain_exists"))throw r;return await g(e,t,"PATCH",`${n}/domains/${D("the domain",s)}`,a)}}function go(e,t,n,s){return{name:`${t}:previews:authorize`,summary:`build a fork's pull request's preview of a ${e}: its code runs with this environment's settings and secrets`,usage:`<pull request> [--${e} <name>]`,details:["A pull request from a fork waits for an admin of the environment (fork protection): look at what it changes first. The head shown is the one built; a push since asks again.","A person authorizes it, signed in with palbase login: an environment's token (PALBASE_TOKEN) cannot."],args:{min:1,max:1},flags:s,scope:"environment",async run(a){let r=Number(a.args[0].replace(/^#/,""));if(!Number.isInteger(r)||r<=0)throw m("the pull request is its number: 12, or #12");let i=await f(a),o=await n(a,i),{previews:l}=await g(a,i,"GET",`${o}/previews`),d=l.find(u=>u.pull_request===r&&u.state!=="closed");if(!d)throw new y("preview_not_found",`no open preview of pull request #${r}`,{hint:`palbase ${t}:previews lists them`});if(d.state!=="authorization_required")throw new y("preview_not_waiting",`#${r}'s preview waits for no authorization: it is ${d.state}`);await v(a,i,`build pull request #${r} at ${d.commit.slice(0,7)}, from a fork`,`Pull request #${r} is from a fork: its code at ${d.commit.slice(0,7)} runs with this environment's settings and secrets`);let p=await g(a,i,"POST",`${o}/previews/${r}/authorize`,{commit:d.commit});a.out.result(p,()=>a.out.line(`#${r} authorized at ${d.commit.slice(0,7)}: its preview is being built (palbase ${t}:previews).`))}}}function ho(e,t,n){t.length===0&&e.out.line(n);for(let s of t){let a=s.state==="authorization_required"?"waits for authorization (previews:authorize)":s.state==="idle"?"idle: no push for 3 days (previews:open opens it again)":s.state==="refused"?`refused: ${s.reason??""}`:s.state;e.out.line(`#${s.pull_request} ${a} ${s.commit.slice(0,7)}${s.fork?" fork":""} ${s.url}${s.machine?` (${s.machine})`:""}`)}}function Ee(e){if(!F_.test(e))throw m(`${JSON.stringify(e)} is not a site's name: lowercase letters, digits and dashes, starting with a letter`);return D("the site",e)}var Yu={repo:{type:"string",value:"owner/name",description:"build it from this GitHub repository on every push (default with --branch: this checkout's origin)"},branch:{type:"string",value:"branch",description:"the branch whose pushes are built"},path:{type:"string",value:"dir",description:"the site's directory in the repository (default: this directory's place in it)"},"install-command":{type:"string",value:"command",description:"what installs its packages (default: from its lockfile \u2014 pnpm, yarn, npm, bun)"},"build-command":{type:"string",value:"command",description:"what builds it (default: its package.json's build script)"},output:{type:"string",value:"dir",description:"the folder the build writes (default: what its builder writes \u2014 dist, build, out, \u2026)"},"build-env":{type:"string",multiple:!0,value:"K=V",description:"a setting the build runs with (repeat it): what its pages are sent (VITE_*), never a secret"}};async function Xu(e,t){let n={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||t){let r=await Ks(e.io,e.io.cwd),i=e.flags.repo??r?.repository,o=e.flags.branch??r?.branch;if(!i)throw m("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!o)throw m("which branch? none is checked out here","pass --branch <branch>");n.source={repository:i,branch:o,path:e.flags.path??(e.flags.repo===void 0?r?.path??"":"")}}let s={};e.flags["install-command"]!==void 0&&(s.install_command=e.flags["install-command"]),e.flags["build-command"]!==void 0&&(s.command=e.flags["build-command"]),e.flags.output!==void 0&&(s.output_dir=e.flags.output);let a=e.flags["build-env"];if(a){let r={};for(let i of a){let o=i.indexOf("=");if(o<=0)throw m(`a setting is K=V, not ${JSON.stringify(i)}`);r[i.slice(0,o)]=i.slice(o+1)}s.env=r}return Object.keys(s).length>0&&(n.build=s),n}function Zs(e){return e<1024?`${e} B`:e<1<<20?`${(e/1024).toFixed(1)} KiB`:`${(e/(1<<20)).toFixed(1)} MiB`}async function H_(e){let t=await Fu(e),n=[],s=async r=>{let i=await N_(r,{withFileTypes:!0});for(let o of i){if(o.name==="node_modules"||o.name.startsWith(".")&&!(o.name===".well-known"&&r===t))continue;let l=G_(r,o.name),d=await D_(l),p=l;if(d.isSymbolicLink()&&(p=await Fu(l),p!==t&&!p.startsWith(t+Wu)))throw new y("unsafe_path",`${Ju(t,l)} links outside ${e}`);let u=d.isSymbolicLink()?await q_(p):d;if(u.isDirectory()){await s(l);continue}if(!u.isFile())continue;let h="/"+Ju(t,l).split(Wu).join("/");if(/[\u0000-\u001f\u007f?#\\]/.test(h))throw new y("invalid_path",`${h} has a character a site's path cannot carry (?, #, \\ or a control character): rename it`);if(h.length>1024)throw new y("invalid_path",`${h.slice(0,80)}\u2026 is longer than a site's path may be (1024 bytes)`);if(u.size>z_)throw new y("file_too_large",`${h} is ${Zs(u.size)}: a site's file is 25 MiB at most (keep large media in storage)`);if(n.push({path:h,file:p,size:u.size,digest:""}),n.length>uo)throw new y("too_many_files",`${e} holds more than ${uo} files: a site holds ${uo} at most`)}};await s(t),n.sort((r,i)=>r.path<i.path?-1:r.path>i.path?1:0);let a=n.reduce((r,i)=>r+i.size,0);if(a>B_)throw new y("release_too_large",`${e} holds ${Zs(a)}: a release is 1 GiB at most`);for(let r of n)r.digest=await Y_(r.file);return n}function Y_(e){return new Promise((t,n)=>{let s=K_("sha256");I_(e).on("data",a=>s.update(a)).on("error",n).on("end",()=>t(s.digest("hex")))})}function X_(e){let t=e.filter(n=>/\.html?$/i.test(n.path));return t.length===1&&t[0].path==="/index.html"}async function Z_(e,t,n){try{return await g(e,t,"GET",`/sites/${Ee(n)}`)}catch(s){if(s instanceof j&&s.code==="site_not_found")return null;throw s}}async function Hu(e,t,n,s,a,r){let i=0,o=0,l=async()=>{for(;o<a.length;){let d=a[o++],p=r.get(d);if(!p)throw new y("internal",`the release asked for ${d}, which no file here is`);let u=await L_(p.file);await g(e,t,"PUT",`/sites/${Ee(n)}/releases/${D("the release",s)}/files/${d}`,new wn(u,"application/octet-stream")),i+=p.size}};return await Promise.all(Array.from({length:Math.min(J_,a.length)},l)),i}var Q_={name:"hosting:deploy",summary:"deploy a folder as a static site: only the files that changed are sent, and the site serves them at once",usage:"<folder> [--site <name>] [--spa]",details:["The folder is what your build wrote (dist, build, out, public): every file in it is served, at its path from the folder's root.","The site is opened on its first deploy; --spa then answers a path no file answers with /index.html (a single-page app).","A folder of one page, index.html, is taken for a single-page app when the site is opened."],args:{min:1,max:1},flags:{site:{type:"string",value:"name",description:"the site (default: web, which answers at the environment's ref)"},spa:{type:"boolean",description:"when the site is opened: answer every path no file answers with /index.html"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web";Ee(n);let s=U_(e.io.cwd,e.args[0]),a=await H_(s).catch(w=>{throw w instanceof y?w:new y("no_folder",`${e.args[0]}: ${w instanceof Error?w.message:String(w)}`)});if(a.length===0)throw new y("empty_folder",`${e.args[0]} holds no file to serve`);a.some(w=>w.path==="/index.html")||e.out.note(` ${e.args[0]} has no index.html: the site's root answers 404.`);let r=a.reduce((w,_)=>w+_.size,0);await v(e,t,`deploy ${a.length} files (${Zs(r)}) to the site ${n}`);let i=await Z_(e,t,n);if(!i){let w=e.flags.spa===!0||X_(a);i=await g(e,t,"POST","/sites",{name:n,spa:w}),e.out.note(` opened the site ${n}${w?" (a single-page app: every route is its index.html)":""}`)}let o={},l=new Map;for(let w of a)o[w.path]=w.digest,l.set(w.digest,w);let d=await g(e,t,"POST",`/sites/${Ee(n)}/releases`,{files:o}),p=d.missing??[];e.out.note(` release #${d.number}: ${a.length} files, ${p.length} to send${p.length<l.size?` (${l.size-p.length} kept from before)`:""}`);let u=await Hu(e,t,n,d.id,p,l),h;for(let w=0;w<3&&!h;w++)try{h=await g(e,t,"POST",`/sites/${Ee(n)}/releases/${D("the release",d.id)}/finish`)}catch(_){let T=_ instanceof j&&_.code==="files_missing"?_.body?.missing??[]:[];if(T.length===0)throw _;u+=await Hu(e,t,n,d.id,T,l)}if(!h)throw new y("release_unfinished",`the release #${d.number} could not be finished`,{hint:"deploy again"});i=await g(e,t,"GET",`/sites/${Ee(n)}`);let b={site:i.name,url:i.url,urls:i.urls,release:h,sent_bytes:u};e.out.result(b,()=>{e.out.line(`Deployed release #${h.number} of ${i.name} (${a.length} files, ${Zs(u)} sent).`);for(let w of i.urls)e.out.line(` ${w}`)})}},e0={name:"hosting:sites:list",summary:"the environment's static sites, where each answers and the release it serves",scope:"environment",async run(e){let t=await f(e),{sites:n}=await g(e,t,"GET","/sites");e.out.result({sites:n},()=>{n.length===0&&e.out.line("No site yet: palbase hosting:deploy <folder> opens one.");for(let s of n){let a=s.taken_down?`taken down: ${s.taken_down.reason}`:s.release?`release #${s.release.number}`:"nothing deployed";e.out.line(`${s.name} ${s.url} ${a}${s.spa?" (single-page app)":""}`)}})}},t0={name:"hosting:sites:create",summary:"open a static site \u2014 built from GitHub on every push with --repo/--branch, or deployed from a folder (hosting:deploy opens one too)",usage:"[name] [--spa] [--repo <owner/name> --branch <branch> --path <dir>] [--build-command \u2026] [--output <dir>] [--build-env K=V]\u2026 [--previews [--preview-forks]]",details:["With --branch (and --repo, or in a checkout of a GitHub repository), its branch's head is built at once and on every push: the folder its build writes is served.","With --previews, each pull request against the branch is built too and answers at <name>-pr<n>-<ref> until it closes; the pull request is told where.","The environment needs its GitHub link first: palbase github:connect."],args:{min:0,max:1},flags:{spa:{type:"boolean",description:"a single-page app: every path no file answers is its /index.html"},...Yu,previews:Xs.previews,"preview-forks":Xs["preview-forks"]},scope:"environment",async run(e){let t=await f(e),n=e.args[0]??"web";Ee(n);let{source:s,build:a}=await Xu(e,!1);await v(e,t,`open the site ${n}${s?`, built from ${s.repository}@${s.branch}`:""}`);let r={};Qs(e,r);let i=await g(e,t,"POST","/sites",{name:n,spa:e.flags.spa===!0,...s?{source:s}:{},...a?{build:a}:{},...r});e.out.result(i,()=>e.out.line(i.source?`Opened ${i.name}: ${i.url} \u2014 its first build is on its way (palbase hosting:builds --site ${i.name}).`:`Opened ${i.name}: ${i.url} (nothing deployed yet: palbase hosting:deploy <folder> --site ${i.name}).`))}},n0={name:"hosting:sites:update",summary:"change a site: whether it is a single-page app, where it is built from, what its build runs",usage:"<name> [--spa | --no-spa] [--repo \u2026 --branch \u2026 --path \u2026] [--no-source] [--build-command \u2026] [--output \u2026] [--build-env K=V]\u2026 [--previews | --no-previews] [--preview-forks | --no-preview-forks]",args:{min:1,max:1},flags:{spa:{type:"boolean",description:"a single-page app: every path no file answers is its /index.html"},"no-spa":{type:"boolean",description:"not a single-page app: a path no file answers is 404"},...Yu,"no-source":{type:"boolean",description:"build it from no repository any more: deploy folders alone"},...Xs},scope:"environment",async run(e){let t=await f(e),n=e.args[0],s={};e.flags.spa===!0&&(s.spa=!0),e.flags["no-spa"]===!0&&(s.spa=!1);let{source:a,build:r}=await Xu(e,!1);if(a&&(s.source=a),e.flags["no-source"]===!0&&(s.source={repository:""}),r&&(s.build=r),Qs(e,s),Object.keys(s).length===0)throw m("nothing to change: give a flag","palbase help hosting:sites:update");await v(e,t,`change the site ${n}`);let i=await g(e,t,"PATCH",`/sites/${Ee(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: ${i.url}${i.spa?" (single-page app)":""}${i.source?` \u2014 built from ${i.source.repository}@${i.source.branch}`:""}`))}},s0={name:"hosting:sites:delete",summary:"delete a site: its addresses serve nothing from now on",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0];await v(e,t,`delete the site ${n} \u2014 its addresses serve nothing from now on`),await g(e,t,"DELETE",`/sites/${Ee(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted ${n}.`))}},a0={name:"hosting:releases",summary:"a site's releases, newest first: the ten newest are kept to return to",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",{releases:s}=await g(e,t,"GET",`/sites/${Ee(n)}/releases`);e.out.result({releases:s},()=>{for(let a of s)e.out.line(`#${a.number} ${a.id} ${a.state}${a.serving?" serving":""} ${a.files} files ${Zs(a.bytes)} ${a.created_at}${a.commit?` ${a.commit.slice(0,7)}`:""}`)})}},r0={name:"hosting:rollback",summary:"serve an earlier release of a site again, at once (the one before the serving one, or a named one)",usage:"[--site <name>] [--to <release id | #number>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"},to:{type:"string",value:"release",description:"the release: its id, or its number (#3)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",s=e.flags.to;if(s!==void 0&&/^#?\d+$/.test(s)){let r=Number(s.replace("#","")),{releases:i}=await g(e,t,"GET",`/sites/${Ee(n)}/releases`),o=i.find(l=>l.number===r);if(!o)throw new y("release_not_found",`${n} keeps no release #${r}`,{hint:`palbase hosting:releases --site ${n}`});s=o.id}await v(e,t,`serve ${s?`the release ${e.flags.to}`:"the release before the serving one"} of ${n}`);let a=await g(e,t,"POST",`/sites/${Ee(n)}/rollback`,s?{release:s}:{});e.out.result(a,()=>e.out.line(`${a.name} serves release #${a.release?.number}: ${a.url}`))}},i0={name:"hosting:builds",summary:"a site's newest builds (a site built from GitHub), or one with its log",usage:"[build] [--site <name>] [--wait]",args:{min:0,max:1},flags:{site:{type:"string",value:"name",description:"the site (default: web)"},wait:{type:"boolean",description:"with a build: follow it until it ends"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",s=e.args[0];if(s===void 0){let{builds:r}=await g(e,t,"GET",`/sites/${Ee(n)}/builds`);e.out.result({builds:r},()=>{r.length===0&&e.out.line(`${n} has no build: it is built from GitHub with hosting:sites:create --repo \u2026 --branch \u2026.`);for(let i of r)e.out.line(`${i.id} ${i.commit.slice(0,7)} ${i.state}${i.reason?`: ${i.reason}`:""} ${i.message}`)});return}let a=await g(e,t,"GET",`/sites/${Ee(n)}/builds/${D("the build",s)}`);for(let r=0;e.flags.wait===!0&&(a.state==="queued"||a.state==="building")&&r<900;r++)await e.io.sleep(2e3),a=await g(e,t,"GET",`/sites/${Ee(n)}/builds/${D("the build",s)}`);if(e.out.result(a,()=>{e.out.line(`${a.id} ${a.commit.slice(0,7)} ${a.state}${a.reason?`: ${a.reason}`:""}`),a.log&&e.out.line(a.log)}),a.state==="failed")return 1}},o0={name:"hosting:build",summary:"build a site's branch head now and serve it, as a push does",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web";await v(e,t,`build ${n} from the head of its branch`);let s=await g(e,t,"POST",`/sites/${Ee(n)}/builds`);e.out.result(s,()=>e.out.line(`${s.name}: a build of ${s.source?.branch??"its branch"}'s head is on its way (palbase hosting:builds --site ${s.name}).`))}},l0={name:"hosting:previews",summary:"a site's pull request previews, newest first: where each answers, and whether it is built",usage:"[--site <name>]",flags:{site:{type:"string",value:"name",description:"the site (default: web)"}},scope:"environment",async run(e){let t=await f(e),n=e.flags.site??"web",{previews:s}=await g(e,t,"GET",`/sites/${Ee(n)}/previews`);e.out.result({previews:s},()=>ho(e,s,`${n} has no preview: a site built from GitHub previews its pull requests with hosting:sites:update ${n} --previews.`))}},p0=co("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),d0=go("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),Zu=[Q_,e0,t0,n0,s0,a0,r0,i0,o0,l0,d0,...p0];function ec(e){return`its last push (${A(e.commit.slice(0,7))}) did not roll out \u2014 ${A(ea(e.reason))}`}function u0(e){let t=`${e.running_version} serving`;if(e.version!==e.running_version){let s={waiting:"waiting to roll out",not_up:"not up",given_up:"given up on",migration:"held by its database migration",stopped:"not up (the machine is stopped)",failed:"not up (the machine cannot run)"}[e.state_reason_kind??""]??(e.state_reason?"not up":"rolling out");t+=`, ${e.version} ${s}`}return`${t}${e.build?` (build ${e.build.id}, ${e.build.commit.slice(0,7)})`:""}`}function ea(e){return e.split(/\r?\n/).map(t=>t.trim()).filter(t=>t!=="").join(" \xB7 ")}function c0(e){let t=e.openapi_path?`, from ${e.openapi_path}`:"";switch(e.contract_state){case"refused":return`refused: ${(e.contract_error??"its app wrote none").split(/\r?\n/).filter(s=>s.trim()!=="").map(A).join(`
32
32
  ${" ".repeat(15)}`)}${t}`;case"none":return`none: its app serves none${t}`}return`${e.contract_digest?`sha256:${e.contract_digest.slice(0,12)}\u2026`:"none read"}${t}`}var g0=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/;async function B(e,t,n,s,a,r){return(await ye(e.studio,t.env_ref,n,s,a,r)).body}function ne(e){if(!g0.test(e))throw m(`${JSON.stringify(e)} is not a name: lowercase letters, digits and dashes, starting with a letter`);return encodeURIComponent(e)}async function ce(e,t,n){if(n!==void 0)return n;let{machines:s}=await B(e,t,"GET","/machines?limit=100");if(s.length===1)return s[0].name;let a=await Ks(e.io,e.io.cwd),r=a?.repository?s.filter(i=>i.source.repository.toLowerCase()===a.repository?.toLowerCase()&&(i.source.path??"")===a.path):[];if(r.length===1)return r[0].name;throw s.length===0?new y("no_machine","the environment has no machine yet",{hint:"make one with palbase machines:create"}):m("which machine? name it",`the environment's machines: ${s.map(i=>i.name).join(", ")}`)}var tc={name:"machines:sizes",summary:"the machine sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await f(e),{sizes:n}=await B(e,t,"GET","/machines/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"sleeps",value:s=>s.sleeps?"when idle":"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},nc={name:"databases:sizes",summary:"the database sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await f(e),{sizes:n}=await B(e,t,"GET","/databases/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"storage",value:s=>`${(s.storage_bytes/1e9).toFixed(0)} GB`},{header:"sleeps",value:s=>s.sleeps?`when idle${s.awake_hours_per_month?`, ${s.awake_hours_per_month} h awake a month`:""}`:"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},sc={name:"machines:list",summary:"the environment's machines: size, state, the version serving, address",scope:"environment",async run(e){let t=await f(e),{machines:n}=await B(e,t,"GET","/machines?limit=100");e.out.result({machines:n},()=>{n.length===0&&e.out.line("No machine yet: palbase machines:create makes one from the code in this directory."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"size",value:s=>`${s.size}${s.replicas>1?` \xD7${s.replicas}`:""}`},{header:"state",value:s=>s.state},{header:"version",value:s=>s.running_version===s.version?`${s.version}`:`${s.running_version} \u2192 ${s.version}`},{header:"source",value:s=>`${s.source.repository}@${s.source.branch}${s.source.path?`/${s.source.path}`:""}`},{header:"url",value:s=>s.url}])})}},ac={name:"machines:get",summary:"a machine in full: its source, size, environment, database and the build serving",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),s=await B(e,t,"GET",`/machines/${ne(n)}`);e.out.result(s,()=>{e.out.line(`${s.name} \u2014 ${s.state}${s.state_reason?` (${s.state_reason})`:""}`),e.out.line(` url ${s.url}`),e.out.line(` source ${s.source.repository} @ ${s.source.branch}${s.source.path?`, ${s.source.path}`:""}${s.source.context?` (context ${s.source.context})`:""}`),e.out.line(` size ${s.size} \xD7 ${s.replicas}, port ${s.port}${s.health_path?`, health ${s.health_path}`:""}`),e.out.line(` version ${u0(s)}`),s.failed_build&&e.out.line(` last push ${A(`${s.failed_build.commit.slice(0,7)} ${s.failed_build.state}: ${ea(s.failed_build.reason)}`)} (palbase machines:builds:log ${A(s.failed_build.id)} --machine ${s.name})`),e.out.line(` contract ${c0(s)}`),e.out.line(` database ${s.database?.name?`${s.database.name}${s.database.migrations?", its migrations run before each release":""}`:"none"}`),e.out.line(` secrets ${s.secrets?`the ${s.secrets} secrets`:"none"}`),e.out.line(` tests ${s.test_command?`${s.test_command}${s.test_users?.length?`, as ${s.test_users.join(", ")}`:""} \u2014 run on each build before it rolls out`:"none"}`),e.out.line(` env ${Object.keys(s.env).length===0?"none":Object.keys(s.env).sort().join(", ")}`)})}},rc={repo:{type:"string",value:"owner/name",description:"the GitHub repository (default: this checkout's origin)"},branch:{type:"string",value:"branch",description:"the branch whose pushes are built and rolled out (default: the one checked out)"},path:{type:"string",value:"dir",description:"the directory built, from the repository's root (default: this directory's place in it)"},context:{type:"string",value:"dir",description:'what the build is given above --path (a monorepo: ".")'},size:{type:"string",value:"size",description:"its size (palbase machines:sizes lists them)"},replicas:{type:"string",value:"n",description:"how many copies run, each on a node of its own"},port:{type:"string",value:"port",description:"the port it listens on (it also finds it in PORT)"},"always-on":{type:"boolean",description:"keep it awake: never put to sleep when idle (an environment on Pay as you go pays its every hour; on Free a shared machine sleeps when idle)"},"no-always-on":{type:"boolean",description:"let it sleep when idle again"},...Xs,health:{type:"string",value:"path",description:"where it answers 2xx while well; a release that does not is not rolled out"},"openapi-path":{type:"string",value:"path",description:`where its app serves its OpenAPI contract, when it is not a Palbase backend (apps:sync makes its client from it); "" for a Palbase backend's own`},db:{type:"string",value:"database",description:'a database of the environment it is given (PALBASE_DB_URL); "" for none'},migrations:{type:"boolean",description:"run the database's migrations before each release (a Palbase backend's)"},secrets:{type:"string",value:"production|development",description:'the secrets it is started with, by name; "" for none'},var:{type:"string",multiple:!0,value:"K=V",description:"a setting it is started with (repeat it); secrets go in secrets:set"},"test-command":{type:"string",value:"command",description:'what each new build runs before it takes traffic, in its image, against the new version itself once its migration has run (bun test, npm test); a failure fails the build and the machine runs what it ran; "" for none'},"test-users":{type:"string",value:"names",description:'the test users each test run makes for its tests (api.signInAs), comma-separated; "" for none'}};function ic(e,t){e.flags["test-command"]!==void 0&&(t.test_command=e.flags["test-command"]),e.flags["test-users"]!==void 0&&(t.test_users=Fi(e.flags["test-users"]))}function oc(e){if(!e)return;let t={};for(let n of e){let s=n.indexOf("=");if(s<=0)throw m(`a setting is K=V, not ${JSON.stringify(n)}`);t[n.slice(0,s)]=n.slice(s+1)}return t}function kn(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw m(`--${t} is a whole number`);return n}var lc={name:"machines:create",summary:"make a machine from the code in this directory, built on every push to its branch",usage:"[name] [--size <size>] [--db <database> --migrations] [--repo --branch --path --context] \u2026",details:["Run in a checkout, it reads the repository from origin, the branch from HEAD and the directory from where you are; flags say otherwise.","The first build starts at once; --wait follows it until the machine serves, and prints its address.","The environment needs its GitHub link first: palbase github:connect."],args:{min:0,max:1},flags:{...rc,wait:{type:"boolean",description:"follow the first build until the machine serves"}},scope:"environment",async run(e){let t=await f(e),n=await Ks(e.io,e.io.cwd),s=e.flags.repo??n?.repository,a=e.flags.branch??n?.branch;if(!s)throw m("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!a)throw m("which branch? none is checked out here","pass --branch <branch>");let r=e.flags.size;if(r===void 0){let{sizes:h}=await B(e,t,"GET","/machines/sizes");if(r=h.find(b=>b.available)?.name,!r)throw new y("no_size_open","no machine size is open to this environment",{hint:"palbase machines:sizes says why"})}let i=e.flags.path??n?.path??"",o={...e.args[0]?{name:e.args[0]}:{},source:{repository:s,branch:a,...i!==""?{path:i}:{},...e.flags.context?{context:e.flags.context}:{}},size:r},l=kn(e.flags.replicas,"replicas"),d=kn(e.flags.port,"port");l!==void 0&&(o.replicas=l),d!==void 0&&(o.port=d),e.flags["always-on"]===!0&&(o.always_on=!0),e.flags["no-always-on"]===!0&&(o.always_on=!1),Qs(e,o),e.flags.health!==void 0&&(o.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(o.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(o.secrets=e.flags.secrets);let p=oc(e.flags.var);if(p&&(o.env=p),e.flags.db!==void 0)o.database={name:e.flags.db,migrations:e.flags.migrations===!0};else if(e.flags.migrations===!0)throw m("--migrations goes with --db <database>");ic(e,o),await v(e,t,`make a machine from ${s}@${a}${i?`/${i}`:""} (${r})`);let u=await B(e,t,"POST","/machines",o);e.flags.wait===!0&&(u=await m0(e,t,u.name)),e.out.result(u,()=>e.out.line(`Made the machine ${u.name} (${u.size}) \u2014 ${u.state}; it answers at ${u.url}`))}},pc={name:"machines:update",summary:"change a machine: the change rolls out as a new version beside the one serving",usage:"[name] [--size \u2026] [--replicas \u2026] [--db \u2026] [--repo --branch --path --context] \u2026",args:{min:0,max:1},flags:rc,scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),s={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||e.flags.path!==void 0||e.flags.context!==void 0){let o=await B(e,t,"GET",`/machines/${ne(n)}`);s.source={repository:e.flags.repo??o.source.repository,branch:e.flags.branch??o.source.branch,path:e.flags.path??o.source.path??"",context:e.flags.context??o.source.context??""}}e.flags.size!==void 0&&(s.size=e.flags.size);let a=kn(e.flags.replicas,"replicas"),r=kn(e.flags.port,"port");if(a!==void 0&&(s.replicas=a),r!==void 0&&(s.port=r),e.flags["always-on"]===!0&&(s.always_on=!0),e.flags["no-always-on"]===!0&&(s.always_on=!1),Qs(e,s),e.flags.health!==void 0&&(s.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(s.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(s.secrets=e.flags.secrets),e.flags.var!==void 0)throw m("machines:update leaves the settings as they are: machines:env:set and machines:env:unset change them");if(e.flags.db!==void 0&&(s.database={name:e.flags.db,migrations:e.flags.migrations===!0}),ic(e,s),Object.keys(s).length===0)throw m("nothing to change: give a flag","palbase help machines:update");await v(e,t,`change the machine ${n}: ${Object.keys(s).join(", ")}`);let i=await B(e,t,"PATCH",`/machines/${ne(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: version ${i.version} rolls out beside ${i.running_version}.`))}},dc={name:"machines:env:list",summary:"what a machine is started with, and where each comes from (the platform, the machine, its secrets)",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),s=await B(e,t,"GET",`/machines/${ne(n)}`),a=s.secrets?await B(e,t,"GET",`/secrets/${D("the machine's secret set",s.secrets)}`).then(o=>(o.items??o.secrets??[]).map(l=>l.name),()=>[]):[],r=["PORT","PALBASE_URL","PALBASE_PROJECT_ID","PALBASE_REPLICAS","PALBASE_PLATFORM_SECRET","PALBASE_SECRET_KEY","PALBASE_WEBHOOK_SECRET",...s.database?.name?["PALBASE_DB_URL","PALBASE_DB_CONNECTIONS"]:[]],i=r.map(o=>({name:o,from:"platform",note:a.includes(o)||o in s.env?`in place of the ${o} set by hand`:""}));for(let o of Object.keys(s.env).sort())r.includes(o)||i.push({name:o,from:"machine",note:a.includes(o)?`shadows the secret ${o}`:""});for(let o of a.sort())r.includes(o)||o in s.env||i.push({name:o,from:`secrets (${s.secrets})`,note:""});e.out.result({machine:s.name,env:i},()=>e.out.table(i,[{header:"name",value:o=>o.name},{header:"from",value:o=>o.from},{header:"",value:o=>o.note}]))}};async function uc(e,t){let n=await f(e),s=await ce(e,n,e.flags.machine);for(let a=1;;a++){let r=await ye(e.studio,n.env_ref,"GET",`/machines/${ne(s)}`),i={...r.body.env},o=t(i);if(a===1){if(r.body.secrets){let l=await B(e,n,"GET",`/secrets/${D("the machine's secret set",r.body.secrets)}`).catch(()=>({items:[]}));for(let d of Object.keys(i))(l.items??[]).some(p=>p.name===d)&&e.out.warn(`${d} in the machine's env shadows the secret of that name`)}await v(e,n,`${o.join(", ")} on the machine ${s}`)}try{let l=await B(e,n,"PATCH",`/machines/${ne(s)}`,{env:i},r.etag?{"if-match":r.etag}:void 0);e.out.result({machine:l.name,env:Object.keys(l.env).sort()},()=>e.out.line(`${l.name}: version ${l.version} rolls out with it.`));return}catch(l){if(!(l instanceof j&&l.status===412)||a===3)throw l;e.out.note(`${s} changed meanwhile: making the change again on what it is now\u2026`)}}}var cc={name:"machines:env:set",summary:"set settings a machine is started with (not secrets: secrets:set keeps those)",usage:"K=V\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=oc(e.args);await uc(e,n=>(Object.assign(n,t),Object.keys(t).map(s=>`set ${s}`)))}},gc={name:"machines:env:unset",summary:"take settings away from what a machine is started with",usage:"K\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){await uc(e,t=>{for(let n of e.args){if(!(n in t))throw new y("no_such_setting",`the machine is not started with ${n}`);delete t[n]}return e.args.map(n=>`unset ${n}`)})}},hc={name:"machines:builds",summary:"a machine's builds, newest first",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),{builds:s}=await B(e,t,"GET",`/machines/${ne(n)}/builds?limit=20`);e.out.result({builds:s},()=>e.out.table(s,[{header:"build",value:a=>a.id},{header:"commit",value:a=>a.commit.slice(0,7)},{header:"state",value:a=>`${a.state}${a.running?" (serving)":""}${a.reason?` \u2014 ${ea(a.reason)}`:""}`},{header:"message",value:a=>a.message.split(`
33
33
  `)[0].slice(0,60)},{header:"created",value:a=>a.created_at}]))}},mc={name:"machines:previews",summary:"a machine's pull request previews, newest first: each a machine of its own while its pull request is open",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),{previews:s}=await B(e,t,"GET",`/machines/${ne(n)}/previews`);e.out.result({previews:s},()=>ho(e,s,`${n} has no preview: machines:update ${n} --previews previews its pull requests (in a preview environment).`))}},fc=co("machine","machines",async(e,t)=>`/machines/${ne(await ce(e,t,e.flags.machine))}`,{machine:{type:"string",value:"name",description:"the machine (default: the environment's only one)"}}),yc=go("machine","machines",async(e,t)=>`/machines/${ne(await ce(e,t,e.flags.machine))}`,{machine:{type:"string",value:"name",description:"the machine (default: the environment's only one)"}}),bc={name:"machines:previews:open",summary:"open a machine's pull request preview again \u2014 one closed after 3 days with no push, or refused \u2014 at the pull request's head",usage:"<pull request> [--machine <name>]",details:["Its machine is made again and built at the pull request's head; a machine that runs its database's migrations has its preview given a new, empty database of its own, migrated by the preview's release.","A preview that is open goes on as it is, and its 3 days start again. A fork's, under fork protection, opens at a head an admin authorized (previews:authorize)."],args:{min:1,max:1},flags:{machine:{type:"string",value:"name",description:"the machine (default: the environment's only one)"}},scope:"environment",async run(e){let t=Number(e.args[0].replace(/^#/,""));if(!Number.isInteger(t)||t<=0)throw m("the pull request is its number: 12, or #12");let n=await f(e),s=await ce(e,n,e.flags.machine),a=await B(e,n,"POST",`/machines/${ne(s)}/previews/${t}/open`);e.out.result(a,()=>e.out.line(`#${t}: its preview is being built at ${a.commit.slice(0,7)} (palbase machines:previews ${s}).`))}},wc={name:"machines:builds:log",summary:"a build's log (the newest build's when none is named)",usage:"[build] [--machine <name>]",args:{min:0,max:1},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.flags.machine),s=e.args[0];if(!s){let{builds:i}=await B(e,t,"GET",`/machines/${ne(n)}/builds?limit=1`);if(i.length===0)throw new y("no_build",`${n} has no build yet`);s=i[0].id}let a=await B(e,t,"GET",`/machines/${ne(n)}/builds/${D("the build's id",s)}/log`),r=typeof a=="string"?a:JSON.stringify(a);e.out.result({build:s,log:r},()=>{for(let i of r.split(`
34
34
  `))e.out.line(i)})}},vc={name:"machines:logs",summary:"a machine's log: the last hour, or since a time; -f follows what it writes next",usage:"[name] [-f] [--since <time>] [--lines <n>] [--search <text>]",details:["Besides the machine's own lines: its migration runs' (migration), and what a build printed while its tests ran against it (test) \u2014 the error it answered a test with, and that request's id: palbase machines:logs <name> --search <request_id>."],args:{min:0,max:1},flags:{follow:{type:"boolean",short:"f",description:"keep printing what the machine writes"},since:{type:"string",value:"time",description:"from when: an RFC 3339 time, or 10m, 2h, 1d ago"},lines:{type:"string",value:"n",description:"how many lines at most (default 200)"},search:{type:"string",value:"text",description:"only the lines that hold this text"}},scope:"environment",async run(e){let t=await f(e),n=await ce(e,t,e.args[0]),s=kn(e.flags.lines,"lines")??200,a=e.flags.since?f0(e.flags.since,e.io.now()):void 0,r=e.flags.search,i=new Set,o=async d=>{let p=new URLSearchParams({lines:String(s),order:d,...a?{since:a}:{},...r?{q:r}:{}}),u=await B(e,t,"GET",`/machines/${ne(n)}/logs?${p}`),h=u!==null&&typeof u=="object"?[u]:String(u??"").split(`