palbase 0.35.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/ai/index.js +2 -2
- package/dist/backend/engine.js +2 -2
- package/dist/backend/env.js +1 -1
- package/dist/backend/index.js +3 -3
- package/dist/backend/stack.js +1 -1
- package/dist/backend/test.js +2 -2
- package/dist/chunks/{S4BXGBZ5.js → 4XFRN45H.js} +2 -2
- package/dist/chunks/{KBY7IBXO.js → 5A5YCRPV.js} +2 -2
- package/dist/chunks/{33B3S5RY.js → 5XN7I6JA.js} +1 -1
- package/dist/chunks/{C2AFHALD.js → H4U6H3OQ.js} +1 -1
- package/dist/chunks/{BGFOKJRG.js → HBXDBDED.js} +2 -2
- package/dist/chunks/{ACXA3P2N.js → ICTHOHDI.js} +2 -2
- package/dist/chunks/ID4L4YNL.js +2 -0
- package/dist/chunks/{FRUUIOFR.js → IYSOKCF4.js} +2 -2
- package/dist/chunks/{OXEDY6JB.js → KAUIR66Z.js} +2 -2
- package/dist/chunks/{NOTDQXFX.js → KN2GS5WM.js} +2 -2
- package/dist/chunks/{PGH2237Q.js → LFNWB4YQ.js} +1 -1
- package/dist/chunks/{CFJZ3DPG.js → LZM7DZDZ.js} +2 -2
- package/dist/chunks/{5AY6UR4J.js → M7SGK2KE.js} +2 -2
- package/dist/chunks/{TGIVD25Y.js → PORYVJPX.js} +2 -2
- package/dist/chunks/{AAS47Y5B.js → QAH6YJUC.js} +1 -1
- package/dist/chunks/{LO57KBUW.js → QMNURUZN.js} +2 -2
- package/dist/chunks/{2OXYYE3V.js → QY5HQFM5.js} +2 -2
- package/dist/chunks/{ASBRNYVS.js → RR6G2GXT.js} +1 -1
- package/dist/chunks/{ZBKJUKQA.js → S76G5MS2.js} +1 -1
- package/dist/chunks/{CODVONHV.js → VAC32QIB.js} +2 -2
- package/dist/chunks/XNR3LADA.js +22 -0
- package/dist/chunks/{HMWR5O3Y.js → YULBKQL2.js} +2 -2
- package/dist/chunks/{FKGX3BOZ.js → ZBANMLOU.js} +1 -1
- package/dist/cli.js +4 -4
- package/dist/cloud/index.js +2 -2
- package/dist/refuse/ai.js +1 -1
- package/dist/refuse/backend.js +1 -1
- package/dist/refuse/cloud.js +1 -1
- package/dist/refuse/server.js +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/testing.js +2 -2
- package/dist/web/index.js +2 -2
- package/dist/web/message.js +2 -2
- package/dist/web/palbe.js +2 -2
- package/dist/web/paywall.js +2 -2
- package/dist/web/push-sw.js +1 -1
- package/dist/web/ssr.js +2 -2
- package/dist/web/vite.js +2 -2
- package/package.json +2 -6
- package/types/ai/src/ai.d.ts +2 -2
- package/types/web/src/calls.d.ts +142 -9
- package/types/web/src/index.d.ts +1 -1
- package/dist/chunks/2YZ33WMV.js +0 -22
- package/dist/chunks/6L2MMJA6.js +0 -2
- package/dist/web/calls-ui.js +0 -46
- package/types/web/src/calls-ui/index.d.ts +0 -71
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! palbase 0.
|
|
2
|
+
/*! palbase 0.36.1 — Palbase SDK License (see LICENSE) */
|
|
3
3
|
var qb=Object.defineProperty;var We=(e,t)=>{for(var n in t)qb(e,n,{get:t[n],enumerable:!0})};import{parseArgs as kw}from"node:util";var Kb=Symbol.for("palbase.data"),Gb=globalThis,tp=Gb[Kb]??=new WeakSet;function Ub(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||tp.has(n)||Ub(n))){tp.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var zb="X-PoW-Challenge-ID",Bb="X-PoW-Nonce";function np(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 Fb(e){return 8*2**e}var Wn;async function Jb(){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 Wb(e){let t=0;for(let n of e){if(n===0){t+=8;continue}return t+Math.clz32(n)-24}return t}var Vb=4096,Hb=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));async function sp(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 Jb(),s=Fb(e.difficulty),a=Date.now()+(t.timeLimitMs??12e4);for(let r=0;r<s;r++){if(r%Vb===0&&r>0){if(await Hb(),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(Wb(o)>=e.difficulty)return{[zb]:e.id,[Bb]: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 Yb=new Set(["apikey","authorization"]),Xb=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=ew(t,r),l=new Headers;for(let[b,w]of Object.entries(r.headers??{}))w!=null&&!Yb.has(b.toLowerCase())&&l.set(b,String(w));for(let[b,w]of Object.entries(i))l.set(b,w);await tw(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=nw(r.body.kind,r.body.value),Vn(d.body)||(d.duplex="half"));let p;try{p=r.onUploadProgress&&e.xmlHttpRequest?await Zb(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 aw(r.op,p);let g;switch(u){case"json":try{g=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":g=p;break;default:await p.body?.cancel(),g=void 0}return{status:p.status,data:g,headers:p.headers}};return{async send(r){try{return await a(r,{})}catch(i){let o=s&&i instanceof j&&i.status===403?np(i.body):null,l=r.body?.value;if(!o||r.body?.kind==="raw"&&!Vn(l))throw i;let d=i,p;try{p=await sp(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 Zb(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,g)=>l.setRequestHeader(g,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:Qb(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 Qb(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 ew(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+sw(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 tw(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 nw(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 sw(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 aw(e,t){let n=await rw(t,Xb).catch(()=>""),s;try{s=n===""?void 0:JSON.parse(n)}catch{s=n}let a=iw(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 rw(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 iw(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 qb=Object.defineProperty;var We=(e,t)=>{for(var n in t)qb(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>=ww||!bw.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:vw(r);if(s.ok)return i;let o=i??{};throw s.status===401?o.error==="token_not_for_machines"?wp(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 mp(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}${yw}${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 wp(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 vw(e){try{return JSON.parse(e)}catch{return e}}var vp={version:"0.
|
|
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 wp(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 vw(e){try{return JSON.parse(e)}catch{return e}}var vp={version:"0.36.1"};var Na=vp.version;async function Pp(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}=Sw(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${Na}
|
|
13
13
|
`),0):(t.stdout($p(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 _p(S,n);return t.stdout($?Sp($):$p(n)),0}let l=s.get(i);if(!l)throw _p(i,n);let d=[...e.slice(0,o),...e.slice(o+1)];if(d.includes("--help")||d.includes("-h"))return t.stdout(Sp(l)),0;let{flags:p,positionals:u}=$w(l,d),g=l.args??{min:0,max:0};if(u.length<g.min)throw m(`${l.name} needs ${g.min===1?"an argument":`${g.min} arguments`}`,$i(l));if(u.length>g.max)throw m(`${l.name} takes ${g.max===0?"no argument":`at most ${g.max}`}: ${JSON.stringify(u.slice(g.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=pp(t.env),_=new xa(cp(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 Pw(i,t,a,r)}}function Sw(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}=kw({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 Pw(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 qb=Object.defineProperty;var We=(e,t)=>{for(var n in t)qb(e,n,{get:t[n],enum
|
|
|
28
28
|
/${r}
|
|
29
29
|
`),"added"}var ed={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 qv=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/,td=20;async function Kv(e,t,n,s){if(n.length===0)return{};let a=n.map(i=>({email:`${i.toLowerCase()}.${s}@test.invalid`,password:nd(18).toString("base64url")})),r=await h(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 Gv(e,t,n){let s=[];for(let a of Object.values(n))try{await h(e,t,"DELETE",`/admin/test-users/${k(a.id)}`)}catch(r){r.status!==404&&s.push(a.id)}return s}async function Uv(e,t,n){if(e.out.note(`${t}'s test command, as the platform holds it: ${zv(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 zv(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(!qv.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>td)throw m(`--users names ${t.length}; a run makes ${td} at most`);return t}var sd={SIGHUP:1,SIGINT:2,SIGTERM:15};function Bv(){let e,t=Object.keys(sd).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 ad={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 h(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 Uv(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=nd(4).toString("hex"),u=t.length>0?Bv():void 0,g=0;try{let w=await Kv(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`);g=_.code}}finally{if(t.length>0){let _=await Gv(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?g:128+sd[b]}};var rd={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))}},id={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 h(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"],Fv={google:"web_client_id",apple:"services_id",microsoft:"application_id",github:"oauth_client_id"},Jv=new Set(["organizations","consumers","common"]),od={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([h(e,t,"GET","/admin/providers"),h(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}]))}},ld={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:{...Us,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 Wv(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 Hv(e,n,t)}};async function Wv(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 h(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 h(e,t,"PUT","/admin/providers/phone/settings",i):r,d=s===void 0?void 0:await h(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 Vv(e){if(Jv.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 Hv(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 g=["client","client-id","return-uri","application","variant","directory","credential"].filter(T=>s[T]!==void 0);if(s["clients-file"]!==void 0){if(g.length>0)throw m(`--clients-file and --${g[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(g.length>0||u!==void 0){let T=s.client??"web",S=[...o.browser_clients],$=S.findIndex(P=>P.key===T),R=u!==void 0&&g.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=Fv[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?Vv(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 h(e,t,"PATCH",`/admin/social-auth/providers/${k(n)}`,l,Re(i)):b=await h(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 pd={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 h(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}.`))}},dd={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 h(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 h(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.`))}},ud={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 h(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}])})}},cd={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 h(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 h(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":""}.`))}},hd={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 h(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)"}},md={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 h(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"}])})}},fd={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 h(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}`)})}},yd={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,...Us},scope:"environment",async run(e){let t=await f(e),{hooks:n}=await h(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(Yv(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 h(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 Yv(e){return{event:e.event,url:e.url,failure_mode:e.failure_mode,timeout_ms:e.timeout_ms,enabled:e.enabled}}var bd={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 h(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]}.`))}},wd={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 h(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(", ")}])})}},vd={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 h(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}`)})}},_d={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 h(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 h(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 Xv(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 kd={name:"auth",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([h(e,t,"GET","/admin/auth-settings"),h(e,t,"GET","/admin/providers"),h(e,t,"GET","/admin/social-auth"),h(e,t,"GET","/admin/roles"),h(e,t,"GET","/admin/hooks"),h(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}`]=Xv(p);return l},async copy(e,t,n,s){await Zv(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 h(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 h(e,t,"PUT","/admin/providers/phone/settings",r.from)});await Qv(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 h(e,t,"DELETE",`/admin/roles/${k(r.name)}`)}):await V(s,`set the role ${r.name}`,async()=>{await h(e,t,"PUT",`/admin/roles/${k(r.name)}`,r.from)});await e_(e,t,pe(n,"hooks."),s),await t_(e,t,pe(n,"oauth_clients."),s)}};async function Zv(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 h(e,t,"PUT","/admin/auth-settings",Object.fromEntries(a.map(i=>[i.name,i.from])),Re(r))})}async function Qv(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 e_(e,t,n,s){if(n.length===0)return;let{hooks:a}=await h(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 h(e,t,"DELETE",`/admin/hooks/${k(d.id)}`)});else if(d)await V(s,`change the hook ${o} \u2192 ${l}`,async()=>{await h(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 h(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 t_(e,t,n,s){if(n.length===0)return;let{clients:a}=await h(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 h(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 h(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 h(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 Sd=["bool","number","string","object"];async function sn(e,t){return(await h(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 $d(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 n_(e){return typeof e=="boolean"?"bool":typeof e=="number"?"number":typeof e=="string"?"string":"object"}function Pd(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 s_(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:Pd(e.slice(n+2).trim(),t)}}var Ad={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([h(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 h(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??""}])})}},Rd={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&&!Sd.includes(a))throw m(`--type is one of ${Sd.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 h(e,t,"GET","/flags/templates/active")).definitions[n]):i=(await h(e,t,"GET","/flags/system")).find(u=>u.key===n);let l=a??i?.value_type??n_(St(s)),d=e.flags["clear-conditions"]===!0?[]:e.flags.condition!==void 0?e.flags.condition.map(u=>s_(u,l)):i?.value_type===l?i.conditions??[]:[],p=$d({value_type:l,value:Pd(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 h(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.`))}}},Td={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 h(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 h(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 a_(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 h(e,t,"POST","/flags/templates/publish",s===""?{}:{note:s},{...Re(n),...C()})}var Ed={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||a_(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}.`))}},Cd={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 h(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.`))}},jd={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 h(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}]))}},Od={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}.`))}},Md={name:"flags",async read(e,t){let n=await h(e,t,"GET","/flags/system"),s={};for(let a of n)s[`flags.${a.key}`]=$d(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 h(e,t,"DELETE",`/flags/system/${k(i.name)}`)}):await V(s,`set the flag ${i.name}`,async()=>{await h(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 xd=["email","sms","verify","whatsapp","push"],Vi=["email","sms","whatsapp"],Id={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 h(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"}])})}},Ld={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"},...Us},scope:"environment",async run(e){let[t,n]=e.args;if(!xd.includes(t))throw m(`the channels: ${xd.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 h(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.`))}},Dd={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 h(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 Xs(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/${Xs(e)}/${k(t)}/${k(n)}`}function r_(e,t){if(e==="sms"){let s=i_(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 i_(e){try{return JSON.parse(e)}catch{return e}}var Nd={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:[Xs(e)],s=await Promise.all(n.map(r=>h(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})`}]))}},qd={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 h(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)}})}},Kd={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=Xs(e),s=Xi(e,e.args[0]),a=r_(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 h(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}"`:""}.`))}},Gd={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 ${Xs(e)} template ${e.args[0]} (${e.flags.locale})`),await h(e,t,"DELETE",n,void 0,C()),e.out.result({deleted:{channel:Xs(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 h(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${k(s)}`:""}`);n.push(...a.topics),s=a.next_cursor}while(s);return n}var Ud={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"}])})}},zd={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 h(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":""}.`))}},Bd={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 h(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]}.`))}},Fd={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))}},Jd={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 h(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])}`)})}},o_=new Set(["sms_countries","sms_blocked_countries"]);function l_(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Wd={name:"notify",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([h(e,t,"GET","/notify/settings"),h(e,t,"GET","/notify/providers"),h(e,t,"GET","/notify/templates/email"),h(e,t,"GET","/notify/templates/sms"),h(e,t,"GET","/notify/templates/whatsapp"),Hi(e,t)]),l={};for(let[u,g]of Object.entries(l_(n)))l[`settings.${u}`]=g;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=>h(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(g=>g.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]=o_.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 h(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 h(e,t,"DELETE",l,void 0,C())}):await V(s,`override the template ${o.name}`,async()=>{await h(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 h(e,t,"DELETE",l,void 0,C())})}else o.change==="only_in_from"?await V(s,`make the topic ${o.name}`,async()=>{await h(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 h(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 p_(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 Vd={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 h(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=>p_(s)}])})}},Hd={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 h(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 h(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(", ")}.`))}},Yd={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 h(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])})`:""}`)})}},Xd={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 h(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 h(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")}`)})}},Zd={name:"realtime",async read(e,t){let[n,s]=await Promise.all([h(e,t,"GET","/realtime/channels"),h(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 h(e,t,"PUT","/realtime/channels",{channels:a.from??[]})});let r=pe(n,"limits.");if(r.length>0){let i=Zi(await h(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 h(e,t,"PUT","/realtime/limits",i)})}}},Qd={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 h(e,t,"GET","/analytics/settings");e.out.result(n,()=>Yn(e,n))}},eu={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 h(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 h(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])}`)})}},tu={name:"analytics",async read(e,t){let n=await h(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 h(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 h(e,t,"PUT","/analytics/settings",r)})}};function d_(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 nu={name:"machines",async read(e,t){let{machines:n}=await h(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.filter(s=>!s.preview_of).map(s=>[`machines.${s.name}`,d_(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`,()=>h(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)`,()=>h(e,t,"PATCH",`/machines/${k(a.name)}`,o))}}};function u_(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function c_(e,t,n,s){let a=await h(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 su={name:"databases",async read(e,t){let{databases:n}=await h(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.filter(s=>s.size!=="preview").map(s=>[`databases.${s.name}`,u_(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)`,()=>c_(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}`,()=>h(e,t,"PATCH",`/databases/${k(a.name)}`,o))}}};var tt="/messaging/settings",h_=/^[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?au(t):"none: anyone may reach anyone"}}function au(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 g_(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 m_(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 f_={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 h(e,t,"GET",tt);e.out.result(n,()=>g_(e,n))}},y_={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=m_(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 h(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 h(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])}`)})}},ru={"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 iu(e){let t=e.flags["secret-to"];if(t!==void 0&&!h_.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 ou(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 h(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 b_={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 h(e,t,"GET",tt);e.out.result({hook:n},()=>e.out.line(n?au(n):"No hook: anyone may reach anyone. palbase messaging:hook:set --url <url> sets one."))}},w_={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)"},...ru},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=iu(e),r=await f(e),i=await h(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 g=await h(e,r,"PUT",tt,{hook:l});await ou(e,r,g,a,o?"Changed the hook":"Set the hook")}},v_={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 h(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 h(e,t,"PUT",tt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may reach anyone."))}},__={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:ru,scope:"environment",async run(e){let t=iu(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 h(e,n,"POST",`${tt}/hook-secret`);await ou(e,n,s,t,"Made the signing secret anew for the hook")}},lu={name:"messaging",async read(e,t){let n=await h(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 h(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 h(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 h(e,t,"PUT",tt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},pu=[f_,y_,b_,w_,v_,__];var nt="/calls/settings",k_=/^[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?du(t):"none: anyone may call anyone (blocks aside)"}}function du(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 S_(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 h(e,t,"GET",nt);e.out.result(n,()=>S_(e,n))}},P_={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 h(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 h(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])}`)})}},uu={"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 cu(e){let t=e.flags["secret-to"];if(t!==void 0&&!k_.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 hu(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 h(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 A_={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 h(e,t,"GET",nt);e.out.result({hook:n},()=>e.out.line(n?du(n):"No hook: anyone may call anyone (blocks aside). palbase calls:hook:set --url <url> sets one."))}},R_={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)"},...uu},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=cu(e),r=await f(e),i=await h(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 g=await h(e,r,"PUT",nt,{hook:l});await hu(e,r,g,a,o?"Changed the hook":"Set the hook")}},T_={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 h(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 h(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)."))}},E_={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:uu,scope:"environment",async run(e){let t=cu(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 h(e,n,"POST",`${nt}/hook-secret`);await hu(e,n,s,t,"Made the signing secret anew for the hook")}},gu={name:"calls",async read(e,t){let n=await h(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 h(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 h(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 h(e,t,"PUT",nt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},mu=[$_,P_,A_,R_,T_,E_];var C_=/^[a-z][a-z0-9-]{1,62}$/;function ss(e){if(!C_.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 j_(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 O_(e,t,n){try{return await h(e,t,"GET",ss(n))}catch(s){if(s instanceof j&&s.status===404)return;throw s}}var fu={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 h(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(", ")}])})}},yu={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 h(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}`:""}`)})}},bu={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 O_(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=j_(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 h(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(", ")}.`))}},wu={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 h(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},vu={name:"storage",async read(e,t){let{buckets:n}=await h(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 h(e,t,"DELETE",ss(a.name))}):await V(s,`declare the bucket ${a.name}`,async()=>{await h(e,t,"PUT",ss(a.name),a.from)})}};var as=[kd,vu,Md,Wd,Zd,tu,lu,gu],ir=[su,nu],_u={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 ku(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 Su(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 M_(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,M_(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 x_={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:_u,scope:"environment",async run(e){let{from:t,to:n}=await ku(e),s=await ro(e,Su(e),t,n);return e.out.result(io(s),()=>oo(e,s)),s.count===0?0:1}},I_={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:_u,scope:"environment",async run(e){let{from:t,to:n}=await ku(e);return lo(e,Su(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 g=r.modules[u.name];if(g.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,g,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 $u=[rd,id,od,ld,dd,pd,ud,cd,hd,md,fd,yd,bd,wd,vd,_d,fu,yu,bu,wu,Ad,Rd,Td,Ed,jd,Od,Cd,Id,Ld,Dd,Nd,qd,Kd,Gd,Ud,zd,Bd,Fd,Jd,Vd,Hd,Yd,Xd,Qd,eu,...pu,...mu,x_,I_];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 Pu={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}])})}},Au={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}).`))}},Ru={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}]))})}},Tu={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}.`))}},Eu={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.`))}},Cu={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}])})}},ju={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)}])})}},Ou={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}`)})}},Mu={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)})`)}})}},xu={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}])})}},Iu={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.`)})}},Lu={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)}.`))}},Du={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}]))}},Nu={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 L_(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})}},qu={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)}.`))}},Ku={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(D_(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)}.`))}},Gu={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(", ")}`)})}},Uu={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))}},zu={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}]))}},Bu={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)})}},Fu={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 L_(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 D_(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 N_,openAsBlob as q_}from"node:fs";import{lstat as K_,readdir as G_,realpath as Ju,stat as U_}from"node:fs/promises";import{createHash as z_}from"node:crypto";import{join as B_,relative as Wu,resolve as F_,sep as Vu}from"node:path";var uo=2e4,J_=25<<20,W_=1<<30,V_=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,H_=6,Zs={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 ea(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 h(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=Hu(l.flags["redirect-status"]),g=await f(l),b=await n(l,g),w=`www.${d}`,_=l.flags.only!==!0&&!d.startsWith("www.");await v(l,g,_?`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 h(l,g,"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 Y_(l,g,b,{host:w}),T=await h(l,g,"PATCH",`${b}/domains/${D("the domain",d)}`,S(w))):$=await X_(l,g,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=Hu(l.flags.status);if(l.flags.none===!0&&u!==void 0)throw m("--status goes with --to");let g=await f(l),b=await n(l,g);await v(l,g,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 h(l,g,"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 h(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 Hu(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 Y_(e,t,n,s){try{return await h(e,t,"POST",`${n}/domains`,s)}catch(a){if(!(a instanceof j&&a.code==="domain_exists"))throw a;let{domains:r}=await h(e,t,"GET",`${n}/domains`),i=r.find(o=>o.host===s.host);if(!i)throw a;return i}}async function X_(e,t,n,s,a){try{return await h(e,t,"POST",`${n}/domains`,{host:s,...a})}catch(r){if(!(r instanceof j&&r.code==="domain_exists"))throw r;return await h(e,t,"PATCH",`${n}/domains/${D("the domain",s)}`,a)}}function ho(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 h(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 h(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 go(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(!V_.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 Xu={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 Zu(e,t){let n={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||t){let r=await Gs(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 Qs(e){return e<1024?`${e} B`:e<1<<20?`${(e/1024).toFixed(1)} KiB`:`${(e/(1<<20)).toFixed(1)} MiB`}async function Z_(e){let t=await Ju(e),n=[],s=async r=>{let i=await G_(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=B_(r,o.name),d=await K_(l),p=l;if(d.isSymbolicLink()&&(p=await Ju(l),p!==t&&!p.startsWith(t+Vu)))throw new y("unsafe_path",`${Wu(t,l)} links outside ${e}`);let u=d.isSymbolicLink()?await U_(p):d;if(u.isDirectory()){await s(l);continue}if(!u.isFile())continue;let g="/"+Wu(t,l).split(Vu).join("/");if(/[\u0000-\u001f\u007f?#\\]/.test(g))throw new y("invalid_path",`${g} has a character a site's path cannot carry (?, #, \\ or a control character): rename it`);if(g.length>1024)throw new y("invalid_path",`${g.slice(0,80)}\u2026 is longer than a site's path may be (1024 bytes)`);if(u.size>J_)throw new y("file_too_large",`${g} is ${Qs(u.size)}: a site's file is 25 MiB at most (keep large media in storage)`);if(n.push({path:g,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>W_)throw new y("release_too_large",`${e} holds ${Qs(a)}: a release is 1 GiB at most`);for(let r of n)r.digest=await Q_(r.file);return n}function Q_(e){return new Promise((t,n)=>{let s=z_("sha256");N_(e).on("data",a=>s.update(a)).on("error",n).on("end",()=>t(s.digest("hex")))})}function e0(e){let t=e.filter(n=>/\.html?$/i.test(n.path));return t.length===1&&t[0].path==="/index.html"}async function t0(e,t,n){try{return await h(e,t,"GET",`/sites/${Ee(n)}`)}catch(s){if(s instanceof j&&s.code==="site_not_found")return null;throw s}}async function Yu(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 q_(p.file);await h(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(H_,a.length)},l)),i}var n0={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=F_(e.io.cwd,e.args[0]),a=await Z_(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 (${Qs(r)}) to the site ${n}`);let i=await t0(e,t,n);if(!i){let w=e.flags.spa===!0||e0(a);i=await h(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 h(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 Yu(e,t,n,d.id,p,l),g;for(let w=0;w<3&&!g;w++)try{g=await h(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 Yu(e,t,n,d.id,T,l)}if(!g)throw new y("release_unfinished",`the release #${d.number} could not be finished`,{hint:"deploy again"});i=await h(e,t,"GET",`/sites/${Ee(n)}`);let b={site:i.name,url:i.url,urls:i.urls,release:g,sent_bytes:u};e.out.result(b,()=>{e.out.line(`Deployed release #${g.number} of ${i.name} (${a.length} files, ${Qs(u)} sent).`);for(let w of i.urls)e.out.line(` ${w}`)})}},s0={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 h(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)":""}`)}})}},a0={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"},...Xu,previews:Zs.previews,"preview-forks":Zs["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 Zu(e,!1);await v(e,t,`open the site ${n}${s?`, built from ${s.repository}@${s.branch}`:""}`);let r={};ea(e,r);let i=await h(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}).`))}},r0={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"},...Xu,"no-source":{type:"boolean",description:"build it from no repository any more: deploy folders alone"},...Zs},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 Zu(e,!1);if(a&&(s.source=a),e.flags["no-source"]===!0&&(s.source={repository:""}),r&&(s.build=r),ea(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 h(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}`:""}`))}},i0={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 h(e,t,"DELETE",`/sites/${Ee(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted ${n}.`))}},o0={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 h(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 ${Qs(a.bytes)} ${a.created_at}${a.commit?` ${a.commit.slice(0,7)}`:""}`)})}},l0={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 h(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 h(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}`))}},p0={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 h(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 h(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 h(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}},d0={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 h(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}).`))}},u0={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 h(e,t,"GET",`/sites/${Ee(n)}/previews`);e.out.result({previews:s},()=>go(e,s,`${n} has no preview: a site built from GitHub previews its pull requests with hosting:sites:update ${n} --previews.`))}},c0=co("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),h0=ho("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),Qu=[n0,s0,a0,r0,i0,o0,l0,p0,d0,u0,h0,...c0];function tc(e){return`its last push (${A(e.commit.slice(0,7))}) did not roll out \u2014 ${A(ta(e.reason))}`}function g0(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 ta(e){return e.split(/\r?\n/).map(t=>t.trim()).filter(t=>t!=="").join(" \xB7 ")}function m0(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)}})}},Kd={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=Xs(e),s=Xi(e,e.args[0]),a=r_(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 h(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}"`:""}.`))}},Gd={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 ${Xs(e)} template ${e.args[0]} (${e.flags.locale})`),await h(e,t,"DELETE",n,void 0,C()),e.out.result({deleted:{channel:Xs(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 h(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${k(s)}`:""}`);n.push(...a.topics),s=a.next_cursor}while(s);return n}var Ud={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"}])})}},zd={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 h(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":""}.`))}},Bd={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 h(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]}.`))}},Fd={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))}},Jd={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 h(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])}`)})}},o_=new Set(["sms_countries","sms_blocked_countries"]);function l_(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Wd={name:"notify",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([h(e,t,"GET","/notify/settings"),h(e,t,"GET","/notify/providers"),h(e,t,"GET","/notify/templates/email"),h(e,t,"GET","/notify/templates/sms"),h(e,t,"GET","/notify/templates/whatsapp"),Hi(e,t)]),l={};for(let[u,g]of Object.entries(l_(n)))l[`settings.${u}`]=g;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=>h(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(g=>g.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]=o_.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 h(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 h(e,t,"DELETE",l,void 0,C())}):await V(s,`override the template ${o.name}`,async()=>{await h(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 h(e,t,"DELETE",l,void 0,C())})}else o.change==="only_in_from"?await V(s,`make the topic ${o.name}`,async()=>{await h(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 h(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 p_(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 Vd={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 h(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=>p_(s)}])})}},Hd={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 h(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 h(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(", ")}.`))}},Yd={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 h(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])})`:""}`)})}},Xd={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 h(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 h(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")}`)})}},Zd={name:"realtime",async read(e,t){let[n,s]=await Promise.all([h(e,t,"GET","/realtime/channels"),h(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 h(e,t,"PUT","/realtime/channels",{channels:a.from??[]})});let r=pe(n,"limits.");if(r.length>0){let i=Zi(await h(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 h(e,t,"PUT","/realtime/limits",i)})}}},Qd={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 h(e,t,"GET","/analytics/settings");e.out.result(n,()=>Yn(e,n))}},eu={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 h(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 h(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])}`)})}},tu={name:"analytics",async read(e,t){let n=await h(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 h(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 h(e,t,"PUT","/analytics/settings",r)})}};function d_(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 nu={name:"machines",async read(e,t){let{machines:n}=await h(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.filter(s=>!s.preview_of).map(s=>[`machines.${s.name}`,d_(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`,()=>h(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)`,()=>h(e,t,"PATCH",`/machines/${k(a.name)}`,o))}}};function u_(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function c_(e,t,n,s){let a=await h(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 su={name:"databases",async read(e,t){let{databases:n}=await h(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.filter(s=>s.size!=="preview").map(s=>[`databases.${s.name}`,u_(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)`,()=>c_(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}`,()=>h(e,t,"PATCH",`/databases/${k(a.name)}`,o))}}};var tt="/messaging/settings",h_=/^[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?au(t):"none: anyone may reach anyone"}}function au(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 g_(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 m_(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 f_={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 h(e,t,"GET",tt);e.out.result(n,()=>g_(e,n))}},y_={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=m_(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 h(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 h(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])}`)})}},ru={"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 iu(e){let t=e.flags["secret-to"];if(t!==void 0&&!h_.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 ou(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 h(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 b_={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 h(e,t,"GET",tt);e.out.result({hook:n},()=>e.out.line(n?au(n):"No hook: anyone may reach anyone. palbase messaging:hook:set --url <url> sets one."))}},w_={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)"},...ru},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=iu(e),r=await f(e),i=await h(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 g=await h(e,r,"PUT",tt,{hook:l});await ou(e,r,g,a,o?"Changed the hook":"Set the hook")}},v_={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 h(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 h(e,t,"PUT",tt,{hook:null});e.out.result({settings:s,changed:["hook"]},()=>e.out.line("Removed the hook: anyone may reach anyone."))}},__={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:ru,scope:"environment",async run(e){let t=iu(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 h(e,n,"POST",`${tt}/hook-secret`);await ou(e,n,s,t,"Made the signing secret anew for the hook")}},lu={name:"messaging",async read(e,t){let n=await h(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 h(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 h(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 h(e,t,"PUT",tt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},pu=[f_,y_,b_,w_,v_,__];var nt="/calls/settings",k_=/^[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?du(t):"none: anyone may call anyone (blocks aside)"}}function du(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"})${e.places?"; asked of places too":""}`}function S_(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 h(e,t,"GET",nt);e.out.result(n,()=>S_(e,n))}},P_={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 h(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 h(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])}`)})}},uu={"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 cu(e){let t=e.flags["secret-to"];if(t!==void 0&&!k_.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 hu(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 h(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 A_={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 h(e,t,"GET",nt);e.out.result({hook:n},()=>e.out.line(n?du(n):"No hook: anyone may call anyone (blocks aside). palbase calls:hook:set --url <url> sets one."))}},R_={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] [--places on|off] [--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.',`With --places on, the hook is asked of places too ({"action": "place", "place", "from_user_id", "to_user_ids": [that person]}): it says who may see and join the meeting held at a place of the app (a channel). Off (a new hook), places are not asked of it, and a place's meeting is seen only by the people asked into it.`,"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)"},places:{type:"string",value:"on|off",description:"the hook answers places too: who may see and join a channel's meeting (a new hook: off)"},...uu},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");let a=e.flags.places;if(a!==void 0&&a!=="on"&&a!=="off")throw m("--places is on or off");let r=a===void 0?void 0:a==="on";if(t===void 0&&n===void 0&&s===void 0&&r===void 0)throw m("nothing to change","pass --url, --timeout-ms, --on-error or --places");let i=cu(e),o=await f(e),l=await h(e,o,"GET",nt),d=l.hook;if(!d&&t===void 0)throw m("the environment has no hook yet: --url names where it is asked");if(d&&i!==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 p={url:t??d.url},u=n??d?.timeout_ms;u!==void 0&&(p.timeout_ms=u);let g=s??d?.on_error;g!==void 0&&(p.on_error=g);let b=r??d?.places;if(b!==void 0&&(p.places=b),d&&Oe(d,{...d,...p}))return ze(e,"the hook",l);let w=d?["url","timeout_ms","on_error","places"].filter(T=>(p[T]??!1)!==(d[T]??!1)):[];await v(e,o,d?`change the calls hook: ${w.join(", ")}`:`set the calls hook ${p.url}${i?`, its secret kept in ${i.name} (${i.set})`:""}`);let _=await h(e,o,"PUT",nt,{hook:p});await hu(e,o,_,i,d?"Changed the hook":"Set the hook")}},T_={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 h(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 h(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)."))}},E_={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:uu,scope:"environment",async run(e){let t=cu(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 h(e,n,"POST",`${nt}/hook-secret`);await hu(e,n,s,t,"Made the signing secret anew for the hook")}},gu={name:"calls",async read(e,t){let n=await h(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 h(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 h(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 h(e,t,"PUT",nt,{hook:i})).hook_secret}),o&&s.shown(`the hook ${i.url} signs with`,o)}},mu=[$_,P_,A_,R_,T_,E_];var C_=/^[a-z][a-z0-9-]{1,62}$/;function ss(e){if(!C_.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 j_(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 O_(e,t,n){try{return await h(e,t,"GET",ss(n))}catch(s){if(s instanceof j&&s.status===404)return;throw s}}var fu={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 h(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(", ")}])})}},yu={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 h(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}`:""}`)})}},bu={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 O_(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=j_(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 h(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(", ")}.`))}},wu={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 h(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},vu={name:"storage",async read(e,t){let{buckets:n}=await h(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 h(e,t,"DELETE",ss(a.name))}):await V(s,`declare the bucket ${a.name}`,async()=>{await h(e,t,"PUT",ss(a.name),a.from)})}};var as=[kd,vu,Md,Wd,Zd,tu,lu,gu],ir=[su,nu],_u={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 ku(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 Su(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 M_(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,M_(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 x_={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:_u,scope:"environment",async run(e){let{from:t,to:n}=await ku(e),s=await ro(e,Su(e),t,n);return e.out.result(io(s),()=>oo(e,s)),s.count===0?0:1}},I_={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:_u,scope:"environment",async run(e){let{from:t,to:n}=await ku(e);return lo(e,Su(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 g=r.modules[u.name];if(g.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,g,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 $u=[rd,id,od,ld,dd,pd,ud,cd,hd,md,fd,yd,bd,wd,vd,_d,fu,yu,bu,wu,Ad,Rd,Td,Ed,jd,Od,Cd,Id,Ld,Dd,Nd,qd,Kd,Gd,Ud,zd,Bd,Fd,Jd,Vd,Hd,Yd,Xd,Qd,eu,...pu,...mu,x_,I_];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 Pu={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}])})}},Au={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}).`))}},Ru={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}]))})}},Tu={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}.`))}},Eu={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.`))}},Cu={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}])})}},ju={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)}])})}},Ou={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}`)})}},Mu={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)})`)}})}},xu={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}])})}},Iu={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.`)})}},Lu={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)}.`))}},Du={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}]))}},Nu={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 L_(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})}},qu={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)}.`))}},Ku={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(D_(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)}.`))}},Gu={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(", ")}`)})}},Uu={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))}},zu={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}]))}},Bu={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)})}},Fu={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 L_(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 D_(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 N_,openAsBlob as q_}from"node:fs";import{lstat as K_,readdir as G_,realpath as Ju,stat as U_}from"node:fs/promises";import{createHash as z_}from"node:crypto";import{join as B_,relative as Wu,resolve as F_,sep as Vu}from"node:path";var uo=2e4,J_=25<<20,W_=1<<30,V_=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,H_=6,Zs={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 ea(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 h(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=Hu(l.flags["redirect-status"]),g=await f(l),b=await n(l,g),w=`www.${d}`,_=l.flags.only!==!0&&!d.startsWith("www.");await v(l,g,_?`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 h(l,g,"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 Y_(l,g,b,{host:w}),T=await h(l,g,"PATCH",`${b}/domains/${D("the domain",d)}`,S(w))):$=await X_(l,g,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=Hu(l.flags.status);if(l.flags.none===!0&&u!==void 0)throw m("--status goes with --to");let g=await f(l),b=await n(l,g);await v(l,g,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 h(l,g,"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 h(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 Hu(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 Y_(e,t,n,s){try{return await h(e,t,"POST",`${n}/domains`,s)}catch(a){if(!(a instanceof j&&a.code==="domain_exists"))throw a;let{domains:r}=await h(e,t,"GET",`${n}/domains`),i=r.find(o=>o.host===s.host);if(!i)throw a;return i}}async function X_(e,t,n,s,a){try{return await h(e,t,"POST",`${n}/domains`,{host:s,...a})}catch(r){if(!(r instanceof j&&r.code==="domain_exists"))throw r;return await h(e,t,"PATCH",`${n}/domains/${D("the domain",s)}`,a)}}function ho(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 h(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 h(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 go(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(!V_.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 Xu={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 Zu(e,t){let n={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||t){let r=await Gs(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 Qs(e){return e<1024?`${e} B`:e<1<<20?`${(e/1024).toFixed(1)} KiB`:`${(e/(1<<20)).toFixed(1)} MiB`}async function Z_(e){let t=await Ju(e),n=[],s=async r=>{let i=await G_(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=B_(r,o.name),d=await K_(l),p=l;if(d.isSymbolicLink()&&(p=await Ju(l),p!==t&&!p.startsWith(t+Vu)))throw new y("unsafe_path",`${Wu(t,l)} links outside ${e}`);let u=d.isSymbolicLink()?await U_(p):d;if(u.isDirectory()){await s(l);continue}if(!u.isFile())continue;let g="/"+Wu(t,l).split(Vu).join("/");if(/[\u0000-\u001f\u007f?#\\]/.test(g))throw new y("invalid_path",`${g} has a character a site's path cannot carry (?, #, \\ or a control character): rename it`);if(g.length>1024)throw new y("invalid_path",`${g.slice(0,80)}\u2026 is longer than a site's path may be (1024 bytes)`);if(u.size>J_)throw new y("file_too_large",`${g} is ${Qs(u.size)}: a site's file is 25 MiB at most (keep large media in storage)`);if(n.push({path:g,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>W_)throw new y("release_too_large",`${e} holds ${Qs(a)}: a release is 1 GiB at most`);for(let r of n)r.digest=await Q_(r.file);return n}function Q_(e){return new Promise((t,n)=>{let s=z_("sha256");N_(e).on("data",a=>s.update(a)).on("error",n).on("end",()=>t(s.digest("hex")))})}function e0(e){let t=e.filter(n=>/\.html?$/i.test(n.path));return t.length===1&&t[0].path==="/index.html"}async function t0(e,t,n){try{return await h(e,t,"GET",`/sites/${Ee(n)}`)}catch(s){if(s instanceof j&&s.code==="site_not_found")return null;throw s}}async function Yu(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 q_(p.file);await h(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(H_,a.length)},l)),i}var n0={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=F_(e.io.cwd,e.args[0]),a=await Z_(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 (${Qs(r)}) to the site ${n}`);let i=await t0(e,t,n);if(!i){let w=e.flags.spa===!0||e0(a);i=await h(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 h(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 Yu(e,t,n,d.id,p,l),g;for(let w=0;w<3&&!g;w++)try{g=await h(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 Yu(e,t,n,d.id,T,l)}if(!g)throw new y("release_unfinished",`the release #${d.number} could not be finished`,{hint:"deploy again"});i=await h(e,t,"GET",`/sites/${Ee(n)}`);let b={site:i.name,url:i.url,urls:i.urls,release:g,sent_bytes:u};e.out.result(b,()=>{e.out.line(`Deployed release #${g.number} of ${i.name} (${a.length} files, ${Qs(u)} sent).`);for(let w of i.urls)e.out.line(` ${w}`)})}},s0={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 h(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)":""}`)}})}},a0={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"},...Xu,previews:Zs.previews,"preview-forks":Zs["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 Zu(e,!1);await v(e,t,`open the site ${n}${s?`, built from ${s.repository}@${s.branch}`:""}`);let r={};ea(e,r);let i=await h(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}).`))}},r0={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"},...Xu,"no-source":{type:"boolean",description:"build it from no repository any more: deploy folders alone"},...Zs},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 Zu(e,!1);if(a&&(s.source=a),e.flags["no-source"]===!0&&(s.source={repository:""}),r&&(s.build=r),ea(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 h(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}`:""}`))}},i0={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 h(e,t,"DELETE",`/sites/${Ee(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted ${n}.`))}},o0={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 h(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 ${Qs(a.bytes)} ${a.created_at}${a.commit?` ${a.commit.slice(0,7)}`:""}`)})}},l0={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 h(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 h(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}`))}},p0={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 h(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 h(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 h(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}},d0={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 h(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}).`))}},u0={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 h(e,t,"GET",`/sites/${Ee(n)}/previews`);e.out.result({previews:s},()=>go(e,s,`${n} has no preview: a site built from GitHub previews its pull requests with hosting:sites:update ${n} --previews.`))}},c0=co("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),h0=ho("site","hosting",async e=>`/sites/${Ee(e.flags.site??"web")}`,{site:{type:"string",value:"name",description:"the site (default: web)"}}),Qu=[n0,s0,a0,r0,i0,o0,l0,p0,d0,u0,h0,...c0];function tc(e){return`its last push (${A(e.commit.slice(0,7))}) did not roll out \u2014 ${A(ta(e.reason))}`}function g0(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 ta(e){return e.split(/\r?\n/).map(t=>t.trim()).filter(t=>t!=="").join(" \xB7 ")}function m0(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 f0=/^[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(!f0.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 Gs(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 nc={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??""}`}]))}},sc={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??""}`}]))}},ac={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}])})}},rc={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 ${g0(s)}`),s.failed_build&&e.out.line(` last push ${A(`${s.failed_build.commit.slice(0,7)} ${s.failed_build.state}: ${ta(s.failed_build.reason)}`)} (palbase machines:builds:log ${A(s.failed_build.id)} --machine ${s.name})`),e.out.line(` contract ${m0(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(", ")}`)})}},ic={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"},...Zs,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 oc(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 lc(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 pc={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:{...ic,wait:{type:"boolean",description:"follow the first build until the machine serves"}},scope:"environment",async run(e){let t=await f(e),n=await Gs(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:g}=await B(e,t,"GET","/machines/sizes");if(r=g.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),ea(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=lc(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>");oc(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 b0(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}`))}},dc={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:ic,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),ea(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}),oc(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}.`))}},uc={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 cc(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 hc={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=lc(e.args);await cc(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 cc(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}`)})}},mc={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 ${ta(a.reason)}`:""}`},{header:"message",value:a=>a.message.split(`
|
|
33
33
|
`)[0].slice(0,60)},{header:"created",value:a=>a.created_at}]))}},fc={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},()=>go(e,s,`${n} has no preview: machines:update ${n} --previews previews its pull requests (in a preview environment).`))}},yc=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)"}}),bc=ho("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)"}}),wc={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}).`))}},vc={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)})}},_c={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?w0(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}`),g=u!==null&&typeof u=="object"?[u]:String(u??"").split(`
|
|
@@ -638,7 +638,7 @@ ${ee}`;for(let[G,ee]of w){ee.has(fe)?i.set(`${z}/${G}.ts`,M(`export * from './en
|
|
|
638
638
|
`))}for(let{machine:G}of F.get(fe)??[])i.set(`${z}/palbe/${G}.ts`,M(`export * from '../environments/${fe}/palbe/${G}';
|
|
639
639
|
`));for(let G of Qe)for(let{machine:ee}of F.get(G)??[])i.set(`${z}/palbe/${ee}.${G}.ts`,Q(G,`export * from '../environments/${G}/palbe/${ee}';
|
|
640
640
|
`))}let Fn={schema:1,project:{id:n.id,name:n.name},apps:r.map(M=>({id:M.id,slug:M.slug,name:M.name,platform:M.platform})),...fe!==void 0&&Qe.length>0?{types_environment:fe}:{},...Object.keys(L).length>0?{clients:Object.fromEntries(Object.entries(L).sort(([M],[Q])=>M.localeCompare(Q)))}:{}};return i.set(`${z}/project.json`,us(Fn)),{files:i,notes:o,project:Fn}}async function g$(e,t,n){let s=`${z}/environments/${t}`,a;try{a=await pa(await he(e,`${s}/contracts/${n}.openapi.json`),"utf8")}catch(o){if(o.code==="ENOENT")return;throw o}let r=`contracts/${n}.openapi.json, sha256 `,i=vg("sha256").update(a).digest("hex");for(let o of[`${s}/palbe/${n}.ts`,`${s}/palbe/${n}.kt`]){let l=await pa(rt(e,o),"utf8").catch(()=>""),d=l.indexOf(r);if(d>=0){i=l.slice(d+r.length,l.indexOf(")",d));break}}return{doc:JSON.parse(a),digest:i}}function m$(e){return[...e.matchAll(/^public (?:nonisolated )?(?:struct|enum|typealias|final class|class|protocol) ([A-Za-z_][A-Za-z0-9_]*)/gm)].map(t=>t[1])}function f$(e){return[...e.matchAll(/^(?:(?:data|sealed|enum|value|internal|private|public) )*(?:class|object|interface|typealias) ([A-Za-z_][A-Za-z0-9_]*)|^val ([A-Za-z_][A-Za-z0-9_.]*\.[A-Za-z_][A-Za-z0-9_]*):/gm)].map(t=>t[1]??t[2])}function wg(e,t,n,s,a){for(let r of t){let i=e.get(r);if(i!==void 0&&i!==n)throw new y("backends_collide",`${i} and ${n} both declare ${r} in ${s}'s ${a} client, which one target cannot hold`,{hint:"rename the controller or route in one of them (it names the type), or reach them from different apps"});e.set(r,n)}}async function y$(e){let t=new Map,n=async s=>{let a;try{a=await No(rt(e,s),{withFileTypes:!0})}catch(r){if(r.code==="ENOENT")return;throw r}for(let r of a){let i=`${s}/${r.name}`;if(r.isDirectory()){(s!==z||r.name==="environments"||r.name==="palbe")&&await n(i);continue}if(!r.isFile()||r.name.endsWith(".storekit"))continue;let o=await pa(rt(e,i),"utf8");(s!==z||r.name==="project.json"||r.name.endsWith(".palbase")||o.startsWith(la))&&t.set(i,o)}};return await n(z),t}async function jr(e){try{return JSON.parse(await pa(rt(e,z,"project.json"),"utf8"))}catch(t){if(t.code==="ENOENT")return null;throw new y("unreadable_project_file",`${z}/project.json is not what palbase apps:sync writes: ${t.message}`)}}async function kg(e){let t=e.io.cwd,n=e.flags.check===!0,s=await jr(t),a=await h$(e,s),r=await y$(t),i=[],o=[],l=[];for(let[u,g]of a.files){let b=r.get(u);b===void 0?i.push(u):b!==g&&o.push(u)}for(let u of r.keys())a.files.has(u)||l.push(u);let d=i.length+o.length+l.length>0;if(!n){for(let u of[...i,...o]){let g=await he(t,u);await t$(i$(g),{recursive:!0}),await he(t,u),await a$(g,a.files.get(u))}for(let u of l)await _g(await he(t,u));await Sg(t,rt(t,z))}let p={project:a.project.project.name,apps:a.project.apps.map(u=>u.slug),added:i,changed:o,removed:l,unchanged:a.files.size-i.length-o.length,check:n,differs:d};for(let u of a.notes)e.out.note(u);return e.out.result(p,()=>{let u=(g,b)=>b.forEach(w=>e.out.line(` ${g} ${w}`));if(!d){e.out.line(`${z}/ is what ${a.project.project.name} gives now (${a.files.size} files)`);return}e.out.line(n?`${z}/ differs from what ${a.project.project.name} gives now:`:`${z}/ written for ${a.project.apps.map(g=>g.slug).join(", ")}:`),u(n?"missing":"added",i),u(n?"differs":"changed",o),u(n?"not given":"removed",l),n&&e.out.note("palbase apps:sync brings it up to date")}),n&&d?1:void 0}async function he(e,t){let n=rt(e,z),s=rt(e,...t.split("/")),a=Cr(n,s);if(a===""||a.startsWith(`..${Er}`)||a===".."||a.split(Er).some(i=>i===".."||i===""))throw new y("outside_palbase",`${t} is not under ${z}/`);let r=e;for(let i of[z,...a.split(Er)]){r=rt(r,i);try{if((await e$(r)).isSymbolicLink())throw new y("linked_path",`${Cr(e,r)} is a link: palbase writes only into ${z}/ itself`,{hint:"remove the link, and run it again"})}catch(o){if(o.code==="ENOENT")break;throw o}}return s}async function Sg(e,t){let n;try{n=await No(t,{withFileTypes:!0})}catch{return}for(let s of n)s.isDirectory()&&await Sg(e,rt(t,s.name));Cr(rt(e,z),t)===""||Cr(rt(e,z),t).startsWith(`..${Er}`)||(await No(t)).length===0&&await s$(t)}async function ln(e){let t=await qt(e);return{id:t[0].project_id,name:t[0].project_name,org:t[0].org_name}}async function Xe(e,t){return(await e.studio.get(`/projects/${t}/apps`)).apps}function lt(e,t){let n=e.find(s=>s.slug===t||s.id===t);if(!n)throw new y("no_such_app",`the project has no app ${t}`,{hint:`its apps: ${e.map(s=>s.slug).join(", ")||"none \u2014 palbase apps:create"}`});return n}function Or(e){let t=e.ios?[e.ios.bundle,e.ios.app_clip&&`App Clip ${e.ios.app_clip}`,e.ios.watch&&`watch ${e.ios.watch}`].filter(Boolean).join(", "):e.android?`${e.android.package} (${e.android.fingerprints.map(n=>n.label).join(", ")||"no certificate"})`:e.web?.origins.join(", ")??"";return e.suspended?`${t} \u2014 suspended`:t}var $g={name:"apps:list",summary:"the project's apps, and what each is in every environment it is set in",scope:"environment",async run(e){let t=await ln(e),n=await Xe(e,t.id);e.out.result({apps:n},()=>{if(n.length===0)return e.out.note(`${t.name} has no app yet: palbase apps:create ios|android|web <name>`);e.out.table(n,[{header:"slug",value:s=>s.slug},{header:"platform",value:s=>s.platform},{header:"name",value:s=>s.deleted_at?`${s.name} (deleted; restorable until ${s.purge_after?.slice(0,10)})`:s.name},{header:"environments",value:s=>s.environments.map(a=>`${a.slug}: ${Or(a)}`).join("; ")||"\u2014"}])})}},Pg={name:"apps:create",summary:"register an app of the project (an iOS app names its Apple team); then set it in each environment (apps:env:set)",usage:"ios|android|web <name> [--slug <slug>] [--team-id <id>] [--app-id-prefix <prefix>]",details:["An app is one product on one platform: its widgets, notification service and other extensions are not apps of their own \u2014 Apple gives them bundles under the app's, and they share its sign-in.","The slug is the app's file in a checkout: palbase/environments/<env>/<slug>.json."],args:{min:2,max:2},flags:{slug:{type:"string",value:"slug",description:"its name in a checkout (default: from the name)"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team: 10 characters, Apple Developer \u203A Membership"},"app-id-prefix":{type:"string",value:"prefix",description:"an iOS app's App ID prefix when it is not the team's (an app made before 2011, or moved from another team)"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="ios"&&t!=="android"&&t!=="web")throw m("the platform is ios, android or web");let s=await ln(e),a=await e.studio.post(`/projects/${s.id}/apps`,{platform:t,name:n,...e.flags.slug?{slug:e.flags.slug}:{},...e.flags["team-id"]?{team_id:e.flags["team-id"]}:{},...e.flags["app-id-prefix"]?{app_id_prefix:e.flags["app-id-prefix"]}:{}});e.out.result(a,()=>{e.out.line(`${s.name} \u203A ${a.name} (${a.platform}, ${a.slug})`),e.out.note(`set it in each environment: palbase apps:env:set ${a.slug} -E <env> ${t==="ios"?"--bundle <bundle id>":t==="android"?"--package <application id> --sha256 <label>=<fingerprint>":"--origin <https://\u2026>"}`)})}},Ag={name:"apps:get",summary:"an app of the project, with what it is and its key in every environment it is set in",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await ln(e),n=lt(await Xe(e,t.id),e.args[0]);e.out.result(n,()=>{if(e.out.line(`${n.name} \u2014 ${n.platform}, ${n.slug}${n.team_id?`, team ${n.team_id}${n.app_id_prefix?` (App ID prefix ${n.app_id_prefix})`:""}`:""}`),n.deleted_at&&e.out.note(`deleted; restorable until ${n.purge_after} (palbase apps:restore ${n.slug})`),n.environments.length===0)return e.out.note(`in no environment yet: palbase apps:env:set ${n.slug} -E <env> \u2026`);e.out.table(n.environments,[{header:"environment",value:s=>s.slug},{header:"is",value:s=>Or(s)},{header:"publishable key",value:s=>s.key?.key??"\u2014"}])})}},Rg={name:"apps:update",summary:"rename an app, or change an iOS app's Apple team: every environment it is in is told",usage:'<app> [--name <name>] [--team-id <id>] [--app-id-prefix <prefix>|""]',args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team"},"app-id-prefix":{type:"string",value:"prefix",description:`its App ID prefix; "" for the team's`}},scope:"environment",async run(e){let t=await ln(e),n=lt(await Xe(e,t.id),e.args[0]),s={};if(e.flags.name!==void 0&&(s.name=e.flags.name),e.flags["team-id"]!==void 0&&(s.team_id=e.flags["team-id"]),e.flags["app-id-prefix"]!==void 0&&(s.app_id_prefix=e.flags["app-id-prefix"]===""?null:e.flags["app-id-prefix"]),Object.keys(s).length===0)throw m("nothing to change: --name, --team-id or --app-id-prefix");let a=await e.studio.request("PATCH",`/projects/${t.id}/apps/${n.id}`,s);e.out.result(a,()=>e.out.line(`${a.name} (${a.slug}) \u2014 told to ${a.environments.length} environment(s)`))}},Tg={name:"apps:delete",summary:"delete an app: suspended in every environment for 30 days \u2014 its builds stop reaching the project \u2014 then removed for good",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await ln(e),n=lt(await Xe(e,t.id),e.args[0]);if(!e.global.yes){if(!e.interactive)throw new y("confirmation_needed",`deleting ${n.name} stops every build of it reaching the project`,{hint:"confirm with -y"});let a=(await e.io.prompt(`Every build of ${n.name} stops reaching ${t.name} at once (restorable for 30 days). Delete it? [y/N] `)).trim().toLowerCase();if(a!=="y"&&a!=="yes")throw new y("cancelled","nothing was changed")}let s=await e.studio.request("DELETE",`/projects/${t.id}/apps/${n.id}`);e.out.result(s,()=>e.out.line(`${s.name} is deleted; palbase apps:restore ${s.slug} brings it back as it was until ${s.purge_after}`))}},Eg={name:"apps:restore",summary:"bring a deleted app back as it was, with its keys, in every environment",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await ln(e),n=lt(await Xe(e,t.id),e.args[0]),s=await e.studio.post(`/projects/${t.id}/apps/${n.id}/restore`);e.out.result(s,()=>e.out.line(`${s.name} is back in ${s.environments.length} environment(s)`))}},b$=/^([a-z][a-z0-9_-]{0,31})=(.+)$/,Cg={name:"apps:env:set",summary:"what an app is in the environment: its bundle (iOS), package and signing certificates (Android) or origins (web)",usage:"<app> [--bundle <id> [--app-clip <id>] [--watch <id>]] [--package <id> --sha256 <label>=<fingerprint>\u2026] [--origin <url>\u2026 --redirect <url>\u2026]",details:["iOS: the app's bundle in this environment; its App Clip and watch app are the bundle followed by one more part. Its extensions are not set: they share the app's.","Android: its application ID and the SHA-256 of each certificate it is signed with \u2014 Play Console \u203A App signing (Google's app signing key, not the upload key), ./gradlew signingReport for a debug key. Label each: debug, play_signing, upload\u2026","Web: the origins its pages are served from (https; http only on this computer) and the addresses a sign-in may return to.","The platform publishes it to Apple and Google for the environment: a device sees a change in a day or so (Apple caches its file; Android 15 re-checks within a week)."],args:{min:1,max:1},flags:{bundle:{type:"string",value:"id",description:"an iOS app's bundle identifier here"},"app-clip":{type:"string",value:"id",description:"its App Clip's bundle (the bundle followed by one more part)"},watch:{type:"string",value:"id",description:"its watch app's bundle (the bundle followed by one more part)"},package:{type:"string",value:"id",description:"an Android app's application ID here"},sha256:{type:"string",multiple:!0,value:"label=fingerprint",description:"a signing certificate's SHA-256, labelled (play_signing=AB:CD:\u2026); every one the app is signed with"},origin:{type:"string",multiple:!0,value:"url",description:"a web app's origin"},redirect:{type:"string",multiple:!0,value:"url",description:"an address a sign-in may return to"}},scope:"environment",async run(e){let t=await f(e),n=lt(await Xe(e,t.project_id),e.args[0]),s=i=>(e.flags[i]??[]).flatMap(o=>o.split(",")).map(o=>o.trim()).filter(Boolean),a;if(n.platform==="ios"){if(!e.flags.bundle)throw m(`${n.name} is an iOS app: give its bundle here (--bundle)`);a={ios:{bundle:e.flags.bundle,...e.flags["app-clip"]?{app_clip:e.flags["app-clip"]}:{},...e.flags.watch?{watch:e.flags.watch}:{}}}}else if(n.platform==="android"){if(!e.flags.package)throw m(`${n.name} is an Android app: give its package here (--package)`);let i=(e.flags.sha256??[]).map(o=>{let l=b$.exec(o.trim());if(!l)throw m(`--sha256 ${JSON.stringify(o)}: label=fingerprint, like play_signing=AB:CD:\u2026`);return{label:l[1],sha256:l[2]}});a={android:{package:e.flags.package,fingerprints:i}}}else{let i=s("origin");if(i.length===0)throw m(`${n.name} is a web app: give its origins here (--origin)`);let o=s("redirect");a={web:{origins:i,...o.length?{redirects:o}:{}}}}await v(e,t,`set ${n.name} (${n.slug}) here: ${Or(a)}`);let r=await e.studio.request("PUT",`/environments/${t.env_ref}/apps/${n.id}`,a);for(let i of r.warnings)e.out.warn(i);e.out.result(r,()=>{e.out.line(`${n.name} in ${t.env_name}: ${Or(r)}`),r.key&&e.out.line(`its publishable key here: ${r.key.key}`),e.out.note("palbase apps:sync writes its settings into this checkout")})}},jg={name:"apps:env:unset",summary:"take an app out of the environment: its key there revoked, Apple's and Google's files no longer naming it",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=lt(await Xe(e,t.project_id),e.args[0]);await v(e,t,`take ${n.name} out of here: builds of it stop reaching ${t.env_name}`),await e.studio.request("DELETE",`/environments/${t.env_ref}/apps/${n.id}`),e.out.result({app:n.slug,environment:t.env_slug,removed:!0},()=>e.out.line(`${n.name} is out of ${t.env_name}`))}},Og={name:"apps:sdkconfig",summary:"an app's settings in the environment, as palbase apps:sync writes them: its address, its publishable key, what its modules tell it",usage:"<app> [--format json|env]",details:["The publishable key is public: it ships in the app. A secret key never does."],args:{min:1,max:1},flags:{format:{type:"string",value:"format",description:"json (default) or env"}},scope:"environment",async run(e){let t=await f(e),n=lt(await Xe(e,t.project_id),e.args[0]),s=e.flags.format??"json";if(s!=="json"&&s!=="env")throw m("--format is json or env");let a=await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`);e.out.result(a,()=>{s==="env"?(e.out.line(`PALBASE_URL=${a.url}`),e.out.line(`PALBASE_PUBLISHABLE_KEY=${a.publishable_key}`)):e.out.line(JSON.stringify(a,null,2))})}},Mg={name:"apps:sync",summary:"write each environment's settings and backend clients (Palbe) of this checkout's apps under palbase/ \u2014 nothing else is touched",usage:"[<app>\u2026] [--check] [--types <env>] [--backend [<machine>=]<dir>] [--as <machine>=<name>]\u2026",args:{min:0,max:20},details:["The apps named join this checkout's (palbase/project.json); with none named, its apps are synced.","palbase/environments/<env>/: each app's settings (<app>.json), each backend's contract (contracts/<machine>.openapi.json) and Palbe client made from it \u2014 TypeScript for a web app, Swift for an iOS app (in palbase/<app>.palbase), Kotlin for an Android app. A web app imports pb from palbase/<app>.ts: one environment's for the editor (--types), the build's mode's for the build (palbase() in vite.config), which type-checks the app against that environment.","--check writes nothing: it exits 1 naming what differs from the project now \u2014 for CI.","--backend makes a backend's Palbe from its checkout on this computer (its palbase/backend app writes its contract and exits; nothing is connected to), to try an endpoint before any machine serves it.","A backend is pb.<name> in the apps \u2014 pb.shopApi.rooms.create() for the machine shop-api \u2014 unless --as names it (--as shop-api=shop): the name is the checkout's, the same in every environment, and none of the client's own (auth, flags, storage, \u2026)."],flags:{check:{type:"boolean",description:"write nothing; exit 1 when the checkout is not what the project gives now"},types:{type:"string",value:"env",description:"the environment the editor's types are of (remembered in palbase/project.json)"},backend:{type:"string",value:"[machine=]dir",description:"a backend's checkout here: its Palbe made from the contract it writes, not from its machine's"},as:{type:"string",multiple:!0,value:"machine=name",description:"a backend's name in the apps (pb.<name>): its machine's camelCased unless named; the same in every environment (remembered in palbase/project.json)"}},scope:"environment",async run(e){return kg(e)}};function Mr(e){let t=new URLSearchParams;for(let[s,a]of Object.entries(e))a!==void 0&&t.set(s,String(a));let n=t.toString();return n===""?"":`?${n}`}function xr(e,t){let n=te(e.flags.limit,"limit");if(n!==void 0&&(n<1||n>t))throw m(`--limit is a whole number from 1 to ${t}`);return n}function Ir(e,t){return{limit:{type:"string",value:"n",description:`at most this many (1\u2013${e}, default ${t})`},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}}}var Ut=e=>e?e.slice(0,19).replace("T"," "):"",On=(e,t)=>e||t;function Lr(e,t,n){t&&e.out.line(`More: palbase ${n} --cursor ${t}`)}function Ig(e){e.out.note("This read is in the project's access log (palbase messaging:access-log).")}function Lg(e){return e.map(t=>`${t.name?A(t.name):"a file"} (${A(t.content_type)}, ${t.size} bytes)`).join(", ")}var xg="A report is deleted, with what it carries, 190 days after it is resolved; one never resolved, 2 years after it was made.",w$={name:"messaging:reports:list",summary:"the reports people made, newest first: why, whom, which messages (what they carry: messaging:reports:get)",usage:"[--status open|resolved] [--limit <n>] [--cursor <cursor>]",flags:{status:{type:"string",value:"status",description:"only the open ones, or only the resolved ones"},...Ir(100,30)},scope:"environment",async run(e){let t=e.flags.status;if(t!==void 0&&t!=="open"&&t!=="resolved")throw m("--status is open or resolved");let n=xr(e,100),s=await f(e),a=await h(e,s,"GET",`/messaging/admin/reports${Mr({status:t,limit:n,cursor:e.flags.cursor})}`);e.out.result(a,()=>{a.data.length===0&&e.out.line(t===void 0?"No report yet.":`No ${t} report.`),e.out.table(a.data,[{header:"report",value:r=>r.id},{header:"made",value:r=>Ut(r.created_at)},{header:"status",value:r=>r.status==="resolved"?`resolved: ${r.resolution??""}`:"open"},{header:"reason",value:r=>r.reason},{header:"reported",value:r=>On(r.reported_user_id,"")},{header:"by",value:r=>On(r.reporter_id,"(deleted)")},{header:"messages",value:r=>String(r.message_ids.length)}]),Lr(e,a.next_cursor,`messaging:reports:list${t?` --status ${t}`:""}`)})}},v$={name:"messaging:reports:get",summary:"one report with what it carries: the reporter's note, what the messages it names said when reported and before they were edited",usage:"<report id>",details:["An owner's or an admin's (a member lists the reports, without what they carry); the read is in the project's access log. --json carries the messages' files with the keys that open them.","A report of a group a person deleted says who deleted it and when; naming no message, it carries the last 30 messages the reporter saw in it.",xg],args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET",`/messaging/admin/reports/${D("the report's id",e.args[0])}`);Ig(e),e.out.note(xg),e.out.result(n,()=>{e.out.line(`report ${n.id} (${n.status==="resolved"?`resolved ${Ut(n.resolved_at)}: ${A(n.resolution??"")}`:"open"})`),e.out.line(`made ${Ut(n.created_at)} by ${On(n.reporter_id,"a user since deleted")}`),e.out.line(`reported ${On(n.reported_user_id,"no one in particular")}`),e.out.line(`reason ${A(n.reason)}`),e.out.line(`conversation ${n.conversation_id}${n.conversation_deleted_at?` (group deleted by ${On(n.conversation_deleted_by,"a user since deleted")} at ${Ut(n.conversation_deleted_at)})`:""}`),n.note&&e.out.line(`note ${A(n.note)}`);let s=n.messages??[];if(s.length!==0){e.out.line(),e.out.line("What the messages said when reported:");for(let a of s){e.out.line(` #${a.seq} ${Ut(a.created_at)} ${On(a.sender_id,"the server")} ${a.kind==="text"||a.text?A(a.text??""):`(${a.kind})`}`),a.attachments.length>0&&e.out.line(` files: ${Lg(a.attachments)}`);for(let r of a.earlier_versions)e.out.line(` before it was edited: ${A(r.text)}, until ${Ut(r.replaced_at)}`)}}})}},_$={name:"messaging:reports:resolve",summary:"resolve a report, saying what came of it in the project's own words",usage:"<report id> --resolution <text>",args:{min:1,max:1},flags:{resolution:{type:"string",value:"text",description:"what came of it: 1 to 64 characters (warned, removed, nothing found\u2026)"}},scope:"environment",async run(e){let t=e.flags.resolution?.trim();if(!t||[...t].length>64)throw m("--resolution says what came of it: 1 to 64 characters");let n=await f(e),s=D("the report's id",e.args[0]);await v(e,n,`resolve the report ${e.args[0]}: ${t}`);let a=await h(e,n,"POST",`/messaging/admin/reports/${s}/resolve`,{resolution:t});e.out.result(a,()=>e.out.line(`Resolved the report ${a.id}: ${A(a.resolution??t)}.`))}},k$={name:"messaging:access-log",summary:"each time people's messages were read or removed \u2014 by the project's server, or an owner or admin from Studio or the CLI \u2014 newest first: what, which conversation, whose, by whom (kept 400 days)",usage:"[--limit <n>] [--cursor <cursor>]",flags:Ir(200,50),scope:"environment",async run(e){let t=xr(e,200),n=await f(e),s=await h(e,n,"GET",`/messaging/admin/access-log${Mr({limit:t,cursor:e.flags.cursor})}`);e.out.result(s,()=>{s.data.length===0&&e.out.line("No read or removal of people's messages yet."),e.out.table(s.data,[{header:"when",value:a=>Ut(a.at)},{header:"what",value:a=>a.action},{header:"messages",value:a=>String(a.count)},{header:"conversation",value:a=>a.conversation_id??""},{header:"person",value:a=>a.user_id??""},{header:"by",value:a=>a.actor},{header:"request",value:a=>a.request_id??""}]),Lr(e,s.next_cursor,"messaging:access-log")})}},S$={name:"messaging:conversations:list",summary:"the project's conversations, or a person's with --user, or a space's with --space, the latest first: whose, how many people, when it last moved",usage:"[--user <user id>] [--space <space>] [--limit <n>] [--cursor <cursor>]",flags:{user:{type:"string",value:"user id",description:"only this person's conversations"},space:{type:"string",value:"space",description:"only the conversations of this space (as org:<id>)"},...Ir(100,30)},scope:"environment",async run(e){let t=e.flags.user;if(t!==void 0&&t.trim()==="")throw m("--user is a person's id");let n=e.flags.space;if(n!==void 0&&!/^[A-Za-z0-9:_./@-]{1,128}$/.test(n))throw m("--space is 1 to 128 letters, digits and : _ . / @ - (as org:<id>)");let s=xr(e,100),a=await f(e),r=await h(e,a,"GET",`/messaging/admin/conversations${Mr({user_id:t,space:n,limit:s,cursor:e.flags.cursor})}`);e.out.result(r,()=>{r.data.length===0&&e.out.line(t===void 0?"No conversation yet.":`${t} is in no conversation.`),e.out.table(r.data,[{header:"conversation",value:i=>i.id},{header:"kind",value:i=>i.kind==="group"&&i.visibility==="open"?"group (open)":i.kind},{header:"space",value:i=>i.space??""},{header:"title",value:i=>i.kind==="group"?i.title??"":(i.members??[]).map(o=>o.user_id).join(" \xB7 ")},{header:"people",value:i=>String(i.member_count)},{header:"messages",value:i=>String(i.last_seq)},{header:"last moved",value:i=>Ut(i.last_message_at??i.created_at)}]),Lr(e,r.next_cursor,`messaging:conversations:list${t!==void 0?` --user ${t}`:""}${n!==void 0?` --space ${n}`:""}`)})}},$$={name:"messaging:messages:list",summary:"what a conversation said, newest first \u2014 each read in the project's access log",usage:"<conversation id> [--limit <n>] [--cursor <cursor>]",details:["An owner's or an admin's: a member sees the conversations and the reports, not what was said.","--json carries the messages whole, their files with the keys that open them."],args:{min:1,max:1},flags:Ir(100,50),scope:"environment",async run(e){let t=xr(e,100),n=await f(e),s=D("the conversation's id",e.args[0]),a=await h(e,n,"GET",`/messaging/admin/conversations/${s}/messages${Mr({limit:t,cursor:e.flags.cursor})}`);Ig(e),e.out.result(a,()=>{a.data.length===0&&e.out.line("The conversation has said nothing yet.");for(let r of a.data){let i=r.deleted?`(removed${r.removed_reason?`: ${r.removed_reason}`:""})`:r.kind==="system"?`(${r.system?.type??"system"})`:r.text!==null?A(r.text):`(${r.kind})`;e.out.line(`#${r.seq} ${Ut(r.created_at)} ${On(r.sender_id,"the server")} ${r.id} ${i}`),!r.deleted&&r.attachments.length>0&&e.out.line(` files: ${Lg(r.attachments)}`)}Lr(e,a.next_cursor,`messaging:messages:list ${e.args[0]}`)})}},P$={name:"messaging:messages:delete",summary:"remove a message for everyone in its conversation: what it said and its files go (in the access log)",usage:"<conversation id> <message id>",details:["It is not undone: asked before it is done, whatever the environment (-y confirms)."],args:{min:2,max:2},scope:"environment",async run(e){let[t,n]=e.args,s=D("the conversation's id",t),a=D("the message's id",n),r=await f(e);await v(e,r,`remove the message ${n} of the conversation ${t} for everyone`,"It removes what the message said for everyone in the conversation, and it is not undone"),await h(e,r,"DELETE",`/messaging/admin/conversations/${s}/messages/${a}`),e.out.result({removed:{conversation_id:t,message_id:n}},()=>e.out.line(`Removed the message ${n} for everyone in ${N(r)}; the removal is in the access log.`))}},Dg=[w$,v$,_$,k$,S$,$$,P$];function A$(e){let t=new URLSearchParams;for(let[s,a]of Object.entries(e))a!==void 0&&t.set(s,String(a));let n=t.toString();return n===""?"":`?${n}`}function R$(e,t){let n=te(e.flags.limit,"limit");if(n!==void 0&&(n<1||n>t))throw m(`--limit is a whole number from 1 to ${t}`);return n}function T$(e,t){return{limit:{type:"string",value:"n",description:`at most this many (1\u2013${e}, default ${t})`},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}}}var Dr=e=>e?e.slice(0,19).replace("T"," "):"";function E$(e,t,n){t&&e.out.line(`More: palbase ${n} --cursor ${t}`)}function qo(e){return!e.answered_at||!e.ended_at?"":String(Math.max(0,Math.round((Date.parse(e.ended_at)-Date.parse(e.answered_at))/1e3)))}var Ng=e=>e.video?"video":"voice";function C$(e){if(e<1e3)return`${e} B`;let t=["kB","MB","GB","TB"],n=e/1e3,s=0;for(;n>=1e3&&s<t.length-1;)n/=1e3,s+=1;return`${n>=100?Math.round(n):Math.round(n*10)/10} ${t[s]}`}var j$=["ringing","active","ended"],O$={name:"calls:list",summary:"the project's calls, or a person's with --user, newest first: kind, voice or video, state, why it ended, who called, how many were in it, how long",usage:"[--user <user id> [--missed]] [--state ringing|active|ended] [--limit <n>] [--cursor <cursor>]",details:["A call's length is from its answer to its end, in seconds: a group call, which no one answers, has its people's seconds in calls:get."],flags:{user:{type:"string",value:"user id",description:"only this person's calls"},missed:{type:"boolean",description:"with --user: only the calls they missed (or were busy for)"},state:{type:"string",value:"state",description:"only the calls ringing, going on (active), or over (ended)"},...T$(100,20)},scope:"environment",async run(e){let t=e.flags.user;if(t!==void 0&&t.trim()==="")throw m("--user is a person's id");let n=e.flags.missed===!0;if(n&&t===void 0)throw m("--missed says whose: give --user with it");let s=e.flags.state;if(s!==void 0&&!j$.includes(s))throw m("--state is ringing, active or ended");let a=R$(e,100),r=await f(e),i=await h(e,r,"GET",`/calls/admin/calls${A$({user_id:t,missed:n?"true":void 0,state:s,limit:a,cursor:e.flags.cursor})}`);e.out.result(i,()=>{i.calls.length===0&&e.out.line(`No ${n?"missed ":""}${s?`${s} `:""}call${t!==void 0?` of ${t}`:n||s?"":" yet"}.`),e.out.table(i.calls,[{header:"call",value:l=>l.id},{header:"started",value:l=>Dr(l.created_at)},{header:"kind",value:l=>l.kind},{header:"media",value:Ng},{header:"state",value:l=>l.state},{header:"end reason",value:l=>l.end_reason??""},{header:"caller",value:l=>l.caller_id},{header:"people",value:l=>String(l.participants.length)},{header:"seconds",value:qo}]);let o=[t!==void 0?` --user ${t}`:"",n?" --missed":"",s?` --state ${s}`:""].join("");E$(e,i.next_cursor,`calls:list${o}`)})}},M$={name:"calls:get",summary:"one call with its people and bots: how each was in it (rung, joined, left, declined, missed, busy), for how many seconds, and what the media server sent them",usage:"<call id>",details:["Every call's media goes through the platform's media server, and a call is billed by the data it sent the call's people: each one's, in bytes, is as its meter last told it (up to a minute behind).","--json carries the call whole: its people's names and pictures, the caller's app's words about it (metadata), each one's bytes."],args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET",`/calls/admin/calls/${D("the call's id",e.args[0])}`);e.out.result(n,()=>{e.out.line(`call ${n.id}`),e.out.line(`kind ${n.kind}, ${Ng(n)}${n.ring?"":", rings no one"}`),e.out.line(`state ${n.state}${n.end_reason?`: ${n.end_reason}${n.ended_by?` by ${n.ended_by}`:""}`:""}`),e.out.line(`caller ${n.caller_id}`),e.out.line(`started ${Dr(n.created_at)}`),n.answered_at&&e.out.line(`answered ${Dr(n.answered_at)}`),n.ended_at&&e.out.line(`ended ${Dr(n.ended_at)}${qo(n)?`, after ${qo(n)} s`:""}`),n.conversation_id&&e.out.line(`conversation ${n.conversation_id}`),e.out.line(`holds at most ${n.max_participants}`),e.out.line(),e.out.table(n.participants,[{header:"user",value:s=>s.user_id},{header:"kind",value:s=>s.kind},{header:"role",value:s=>s.role},{header:"state",value:s=>s.state},{header:"seconds",value:s=>String(s.seconds)},{header:"data",value:s=>C$(s.bytes)}])})}},x$={name:"calls:end",summary:"end a call for everyone in it (asked before it is done, whatever the environment)",usage:"<call id>",details:["It is not undone: asked before it is done, whatever the environment (-y confirms). A call already over is left as it is."],args:{min:1,max:1},scope:"environment",async run(e){let t=D("the call's id",e.args[0]),n=await f(e);await v(e,n,`end the call ${e.args[0]} for everyone in it`,"It ends the call for everyone in it, and it is not undone");let s=await h(e,n,"POST",`/calls/admin/calls/${t}/end`);e.out.result(s,()=>e.out.line(s.end_reason&&s.end_reason!=="ended"?`The call ${s.id} was already over: ${s.end_reason}.`:`Ended the call ${s.id} for everyone in it in ${N(n)}.`))}},qg=[O$,M$,x$];import{writeFile as I$}from"node:fs/promises";var L$=/^[A-Z][A-Z0-9_]{0,127}$/,hs={dev:{type:"boolean",description:"the development set (what a backend on a developer's computer reads), not production"}};function gs(e){return e.flags.dev===!0?"development":"production"}function da(e){let t=e.args[0];if(!L$.test(t))throw m(`${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);return t}async function Ko(e,t,n){let s=[],a;do{let r=await h(e,t,"GET",`/secrets/${n}?limit=1000${a?`&cursor=${encodeURIComponent(a)}`:""}`);s.push(...r.secrets),a=r.next_cursor}while(a);return s}var D$={name:"secrets:list",summary:"the environment's secrets by name, with their version \u2014 never their values",usage:"[--dev]",flags:hs,scope:"environment",async run(e){let t=await f(e),n=gs(e),s=await Ko(e,t,n);e.out.result({set:n,secrets:s},()=>{s.length===0&&e.out.line(`No ${n} secrets.`);for(let a of s)e.out.line(`${a.name} v${a.version} ${a.updated_at}`)})}},N$={name:"secrets:set",summary:"set a secret: its value is read from stdin, never from the command line",usage:"<NAME> [--dev] < value",details:["The environment's machines read production at their next start; a backend run with palbase run or env:pull reads development."],args:{min:1,max:1},flags:hs,scope:"environment",async run(e){let t=await f(e),n=da(e),s=gs(e),a=await Me(e,`the value of ${n}`);await v(e,t,`set the ${s} secret ${n}`);let r=await h(e,t,"PUT",`/secrets/${s}/${n}`,{value:a});e.out.result({set:s,name:n,version:r.version},()=>e.out.line(`Set ${n} (${s}, v${r.version}) in ${N(t)}.`))}},q$={name:"secrets:unset",summary:"take a secret away",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:hs,scope:"environment",async run(e){let t=await f(e),n=da(e),s=gs(e);await v(e,t,`take away the ${s} secret ${n}`),await h(e,t,"DELETE",`/secrets/${s}/${n}`),e.out.result({set:s,name:n,deleted:!0},()=>e.out.line(`Took ${n} (${s}) away from ${N(t)}.`))}},K$={name:"secrets:get",summary:"print one secret's value (an admin's)",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:hs,scope:"environment",async run(e){let t=await f(e),n=da(e),s=await h(e,t,"GET",`/secrets/${gs(e)}/${n}`);e.out.result({name:n,version:s.version,value:s.value},()=>e.out.line(s.value))}},G$={name:"secrets:versions",summary:"a secret's versions, newest first \u2014 never their values",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:hs,scope:"environment",async run(e){let t=await f(e),n=da(e),s=await h(e,t,"GET",`/secrets/${gs(e)}/${n}/versions`);e.out.result(s,()=>{for(let a of s.versions)e.out.line(`v${a.version} ${a.created_at}${a.current?" (current)":""}`)})}},U$={name:"secrets:rollback",summary:"put a secret back to one of its versions (a new version with that value)",usage:"<NAME> --version <n> [--dev]",args:{min:1,max:1},flags:{...hs,version:{type:"string",value:"n",description:"the version to go back to"}},scope:"environment",async run(e){let t=await f(e),n=da(e),s=Number(e.flags.version);if(!Number.isInteger(s)||s<1)throw m("--version is a version number (secrets:versions lists them)");let a=gs(e);await v(e,t,`put the ${a} secret ${n} back to v${s}`);let r=await h(e,t,"POST",`/secrets/${a}/${n}/rollback`,{version:s});e.out.result({set:a,name:n,version:r.version},()=>e.out.line(`${n} (${a}) is v${s}'s value again, as v${r.version}.`))}},z$={name:"secrets:audit",summary:"every read and write of the environment's secrets, newest first: which secret, which key did it, when \u2014 never a value",usage:"[--name <NAME>] [--dev | --prod] [--limit <n>]",details:["A backend that reads the secrets at boot (palbase run, a machine) shows here as a read, with the key it read with: palbase keys:list names the key.","An encrypted column's values opened through Palbase (Studio's table, a secret key) are recorded under the column's address: --name patients.tc lists who opened them, and when."],flags:{name:{type:"string",value:"NAME",description:"only this secret's, or an encrypted field's opens (table.column)"},dev:{type:"boolean",description:"only the development secrets"},prod:{type:"boolean",description:"only the production secrets"},limit:{type:"string",value:"n",description:"how many (default 30)"}},scope:"environment",async run(e){let t=await f(e);if(e.flags.dev===!0&&e.flags.prod===!0)throw m("--dev or --prod, not both");let n=Number(e.flags.limit??30);if(!Number.isInteger(n)||n<1||n>500)throw m("--limit is a whole number from 1 to 500");let s=new URLSearchParams({limit:String(n),...e.flags.name?{name:e.flags.name}:{},...e.flags.dev===!0?{environment:"development"}:e.flags.prod===!0?{environment:"production"}:{}}),a=await h(e,t,"GET",`/secrets/audit?${s}`);e.out.result(a,()=>{a.entries.length===0?e.out.line("No read or write of a secret yet."):e.out.table(a.entries,[{header:"when",value:r=>r.at.slice(0,19).replace("T"," ")},{header:"what",value:r=>r.action},{header:"set",value:r=>r.environment??""},{header:"secret",value:r=>r.name?`${r.name}${r.version?` v${r.version}`:""}`:"every one"},{header:"key",value:r=>r.key_id}])})}};function Kg(e,t){e.out.table(t.keys,[{header:"key",value:n=>`${n.id}${n.id===t.current?" (in force)":""}`},{header:"in force from",value:n=>n.activates_at.slice(0,19).replace("T"," ")},{header:"made by",value:n=>n.created_by}])}var B$={name:"fields:list",summary:"the keys the environment's encrypted database fields are sealed with \u2014 never the keys themselves",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/secrets/fields");e.out.result(n,()=>Kg(e,n))}},F$={name:"fields:rotate",summary:"make a new key for the environment's encrypted fields (an owner's or an admin's)",details:["It comes into force in five minutes: by then every running backend has fetched it. New values are sealed with it from then on; a palbase/backend app seals its older values again with it, in the background.","The older keys go on opening what they sealed."],scope:"environment",async run(e){let t=await f(e);await v(e,t,"rotate the encrypted fields' key");let n=await h(e,t,"POST","/secrets/fields/rotate"),s=n.keys[n.keys.length-1];e.out.result(n,()=>{e.out.line(`Made ${s.id} in ${N(t)}: in force from ${s.activates_at.slice(0,19).replace("T"," ")} UTC.`),Kg(e,n)})}},J$={name:"fields:keys",summary:"download the environment's field keys into a file (its owner's): what opens a dump of its database elsewhere",usage:"--out <file>",details:["The file opens every encrypted field of the environment: keep it as you would the database's password. It is the shape palbase/server's Fields reads: new Fields({ fetch: async () => JSON.parse(readFileSync(file, 'utf8')) }).","The download is in the secrets audit trail (secrets:audit)."],flags:{out:{type:"string",value:"file",description:"where the keys go (made readable by you alone); never the screen"}},scope:"environment",async run(e){let t=e.flags.out;if(!t)throw m("fields:keys writes the keys into a file: --out <file> (never onto the screen, which keeps them)");let n=await f(e),s=await h(e,n,"GET","/secrets/fields/keys");await I$(t,`${JSON.stringify(s,null,2)}
|
|
641
|
-
`,{mode:384,flag:"wx"}),e.out.result({file:t,keys:s.keys.map(a=>a.id)},()=>e.out.line(`Wrote the keys of ${N(n)} (${s.keys.map(a=>a.id).join(", ")}) to ${t}.`))}};function Go(e,t){e.out.table(t.keys,[{header:"key",value:n=>`${n.id}${n.id===t.current?" (in use)":""}`},{header:"made",value:n=>n.created_at.slice(0,19).replace("T"," ")},{header:"made by",value:n=>n.created_by},{header:"retired",value:n=>n.retired_at?`${n.retired_at.slice(0,19).replace("T"," ")} by ${n.retired_by}`:""}])}var W$={name:"transit:list",summary:"the keys the encrypted fields of a request to the environment's backend are sealed to \u2014 their public halves alone",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/secrets/transit");e.out.result(n,()=>Go(e,n))}},V$={name:"transit:rotate",summary:"make a new key for the encrypted fields of requests to the backend (an owner's or an admin's)",usage:"[--retire-previous]",details:["Apps seal to it from their next fetch of the project's keys (within the hour), or at once with --retire-previous: every older key is retired with it \u2014 what to do when one may have been taken \u2014 and an app that seals to a retired one is told so, fetches the keys again and sends its call once more.","No app needs a new build: an app holds the project's signing key, not its keys."],flags:{"retire-previous":{type:"boolean",description:"retire every older key at once"}},scope:"environment",async run(e){let t=await f(e),n=e.flags["retire-previous"]===!0;await v(e,t,n?"rotate the transit keys, retiring every older one":"rotate the transit keys");let s=await h(e,t,"POST","/secrets/transit/rotate",{retire_previous:n});e.out.result(s,()=>{e.out.line(`Made ${s.current} in ${N(t)}: apps seal to it from now${n?"; every older key is retired":""}.`),Go(e,s)})}},H$={name:"transit:retire",summary:"retire a transit key that is not the one in use: no request sealed to it opens from then on",usage:"<key>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0];if(!/^t[1-9][0-9]{0,5}$/.test(n))throw m(`${n} is not a transit key's id`,"palbase transit:list names them (t1, t2, \u2026)");await v(e,t,`retire the transit key ${n}`);let s=await h(e,t,"POST",`/secrets/transit/${n}/retire`);e.out.result(s,()=>{e.out.line(`Retired ${n} in ${N(t)}.`),Go(e,s)})}},Gg=[D$,N$,K$,q$,G$,U$,z$,B$,F$,J$,W$,V$,H$];var Y$=/^[A-Z][A-Z0-9_]{0,127}$/;function pn(e,t){return`/environments/${D("the environment's ref",e.env_ref)}/webhooks${t!==void 0?`/${D("the webhook's id",t)}`:""}`}function Ug(e){return re(e.flags.events)}function Nr(e){let t=e.machine?`, the machine ${A(e.machine)}'s \u2014 kept by the platform to what its app takes`:"",n=e.created_by?`, made by ${A(e.created_by.name)}${e.created_by.id.startsWith("tok_")?" (access token)":e.created_by.id.startsWith("etk_")?" (environment token)":""}`:"";return`${A(e.url)} \u2014 ${e.events.length===0?"every event":A(e.events.join(", "))} (${A(e.id)}${t}${n})`}function X$(e,t){let n=t.find(a=>a.id===e.endpoint_id)?.url??`${e.endpoint_id} (no longer an endpoint)`,s=e.redelivered_at?`; redelivered ${A(e.redelivered_at)}`:e.redeliver_asked_at?"; redelivery asked":e.redelivery_error?`; redelivery failed: ${A(e.redelivery_error)}`:"";return`${A(e.id)} ${A(e.event_type)} ${A(e.event_id)} \u2192 ${A(n)} \u2014 ${A(e.last_error)} (${A(String(e.attempts))} tries, given up ${A(e.failed_at)})${s}`}var Z$={name:"webhooks:list",summary:"the environment's webhook endpoints: where its events go, which (never their secrets)",scope:"environment",async run(e){let t=await f(e),{endpoints:n}=await e.studio.get(pn(t));e.out.result({endpoints:n},()=>{n.length===0&&e.out.line("No webhook endpoint yet: palbase webhooks:create <url> makes one.");for(let s of n)e.out.line(Nr(s))})}},Q$={name:"webhooks:create",summary:"add a webhook endpoint; its signing secret is printed this once, or kept in a secret with --secret-to",usage:"<url> [--events <a,b>] [--secret-to <NAME> [--dev]]",details:["A machine is given its own endpoint and its secret (PALBASE_WEBHOOK_SECRET) by the platform, for the events its app takes: this is for another receiver. --secret-to keeps the secret in the environment's secrets (production; --dev: development) and prints it nowhere."],args:{min:1,max:1},flags:{events:{type:"string",value:"list",description:"the events it is sent, comma-separated (user.deleted, \u2026); none: every event"},"secret-to":{type:"string",value:"NAME",description:"keep its signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}},scope:"environment",async run(e){let t=await f(e),n=e.flags["secret-to"];if(n!==void 0&&!Y$.test(n))throw m(`--secret-to ${JSON.stringify(n)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&n===void 0)throw m("--dev goes with --secret-to <NAME>");let s=e.flags.dev===!0?"development":"production";await v(e,t,`add the webhook endpoint ${e.args[0]}${n?`, its secret kept in ${n} (${s})`:""}`);let a=await e.studio.post(pn(t),{url:e.args[0],events:Ug(e)??[]}),{secret:r,...i}=a;if(n!==void 0){let o=await h(e,t,"PUT",`/secrets/${s}/${n}`,{value:r});e.out.result({endpoint:i,secret_to:{set:s,name:n,version:o.version}},()=>e.out.line(`Added ${Nr(i)}; its secret is ${n} (${s}, v${o.version}) in ${N(t)}.`));return}e.out.result(a,()=>{e.out.line(`Added ${Nr(i)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}},eP={name:"webhooks:update",summary:"change a webhook endpoint's address or events (its secret stays)",usage:'<id> [--url <url>] [--events <a,b>|""]',args:{min:1,max:1},flags:{url:{type:"string",value:"url",description:"where it is delivered now"},events:{type:"string",value:"list",description:'the events it is sent now, comma-separated; "" for every event'}},scope:"environment",async run(e){let t=await f(e);if(e.flags.url===void 0&&e.flags.events===void 0)throw m("nothing to change: --url or --events");let{endpoints:n}=await e.studio.get(pn(t)),s=n.find(r=>r.id===e.args[0]);if(!s)throw m(`the environment has no webhook endpoint ${JSON.stringify(e.args[0])}`,"palbase webhooks:list shows them");await v(e,t,`change the webhook endpoint ${s.url}`);let a=await e.studio.request("PUT",pn(t,s.id),{url:e.flags.url??s.url,events:Ug(e)??s.events});e.out.result(a,()=>e.out.line(`Changed: ${Nr(a)}.`))}},tP={name:"webhooks:delete",summary:"remove a webhook endpoint: its events are no longer delivered there",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`remove the webhook endpoint ${e.args[0]}`),await e.studio.request("DELETE",pn(t,e.args[0])),e.out.result({deleted:e.args[0]},()=>e.out.line(`Removed the webhook endpoint ${e.args[0]}.`))}},nP={name:"webhooks:deliveries:list",summary:"the deliveries the platform gave up on after its tries, still owed (--failed), or every one kept 30 days (--all)",usage:"[--failed] [--all]",flags:{failed:{type:"boolean",description:"those it gave up on and still owes (the default)"},all:{type:"boolean",description:"every one kept, those delivered again among them"}},scope:"environment",async run(e){let t=await f(e),[{deliveries:n},{endpoints:s}]=await Promise.all([e.studio.get(`${pn(t)}/deliveries${e.flags.all===!0?"?all=true":"?failed=true"}`),e.studio.get(pn(t))]);e.out.result({deliveries:n},()=>{n.length===0&&e.out.line("No failed delivery: every event reached its endpoints.");for(let a of n)e.out.line(X$(a,s))})}},sP={name:"webhooks:deliveries:redeliver",summary:"send a failed delivery again: the same event, its id and body, to the endpoint it failed at, within seconds",usage:"<id>",details:["As Stripe's Resend: the receiver takes an event more than once by its Palbase-Event-Id. An admin's; it is written to the organization's audit log."],args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`send the failed delivery ${e.args[0]} again`);let n=await e.studio.post(`${pn(t)}/deliveries/${D("the delivery's id",e.args[0])}/redeliver`,{});e.out.result(n,()=>e.out.line(`Asked: ${A(n.event_type)} ${A(n.event_id)} goes to its endpoint again within seconds (palbase webhooks:deliveries:list --all shows when it went).`))}},zg=[Z$,Q$,eP,tP,nP,sP];import{constants as qr,createReadStream as aP}from"node:fs";import{mkdir as rP,open as iP,readdir as oP,realpath as Bg,stat as Vg}from"node:fs/promises";import{dirname as Fg,extname as lP,join as Hg,relative as pP,resolve as Jg,sep as Kr}from"node:path";import{Readable as Yg}from"node:stream";import{pipeline as dP}from"node:stream/promises";var Xg=600;function ua(e){let t=/^([a-z0-9][a-z0-9_-]{0,62}):(.*)$/.exec(e);return!t||e.startsWith("/")||e.startsWith(".")?null:{bucket:t[1],path:t[2].replace(/^\/+/,"")}}function Gr(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new y("unsafe_path",`the object ${JSON.stringify(e)} has a name no request can carry`);return encodeURIComponent(e)}async function Uo(e,t,n,s){let a=[],r;do{let i=new URLSearchParams({limit:"1000",...s?{prefix:s}:{},...r?{cursor:r}:{}}),o=await h(e,t,"GET",`/storage/buckets/${D("the bucket",n)}/objects?${i}`);a.push(...o.objects),r=o.next_cursor}while(r);return a}async function Zg(e,t,n,s){let a;try{a=await e.io.fetch(t,{...n,redirect:"manual"})}catch(r){throw new y("network_error",`${s}: ${r instanceof Error?r.message:String(r)}`)}if(!a.ok){let r=await a.json().catch(()=>({}));throw new y(r.error??`http_${a.status}`,`${s}: ${r.error_description??r.error??`answered ${a.status}`}`)}return a}var uP={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".heif":"image/heif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".pdf":"application/pdf",".json":"application/json",".txt":"text/plain",".csv":"text/csv",".html":"text/html",".css":"text/css",".js":"text/javascript",".xml":"application/xml",".zip":"application/zip",".mp4":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4"};function Wg(e){return uP[lP(e).toLowerCase()]}async function zo(e,t,n,s,a,r){let i=(await Vg(n)).size,o=await h(e,t,"POST",`/storage/buckets/${D("the bucket",s)}/upload-urls`,{path:a,expires_in:Xg,...r?{owner_id:r}:{}});return await Zg(e,o.url,{method:"PUT",body:Yg.toWeb(aP(n)),headers:{"content-length":String(i),...Wg(n)?{"content-type":Wg(n)}:{}},duplex:"half"},`${n} \u2192 ${s}:${a}`),i}function cP(e,t){let n=t.split("/");if(t===""||n.some(a=>a===""||a==="."||a===".."||a.includes("\\")||a.includes("\0")))throw new y("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=Hg(e,...n);if(!s.startsWith(e+Kr))throw new y("unsafe_path",`${t} would land outside ${e}`);return s}async function hP(e,t,n,s,a,r){let i=await h(e,t,"POST",`/storage/buckets/${D("the bucket",n)}/signed-urls`,{path:s,expires_in:Xg}),o=await Zg(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${a}`);if(await rP(Fg(a),{recursive:!0}),r!==void 0){let[g,b]=await Promise.all([Bg(r),Bg(Fg(a))]);if(b!==g&&!b.startsWith(g+Kr))throw new y("unsafe_path",`${s} would land outside ${r} (through a link)`)}let d=(await iP(a,qr.O_WRONLY|qr.O_CREAT|qr.O_TRUNC|qr.O_NOFOLLOW)).createWriteStream();if(!o.body)return await new Promise((g,b)=>d.on("error",b).end(g)),0;let p=0,u=Yg.fromWeb(o.body).on("data",g=>{p+=g.length});return await dP(u,d),p}async function Qg(e){let t=[];for(let n of await oP(e,{withFileTypes:!0})){let s=Hg(e,n.name);n.isDirectory()?t.push(...await Qg(s)):n.isFile()&&t.push(s)}return t}var gP={name:"storage:ls",summary:"a bucket's objects, or those under a prefix: path, size, type",usage:"<bucket>[:prefix]",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0].includes(":")?e.args[0]:`${e.args[0]}:`);if(!n)throw m(`${e.args[0]} is not a bucket (bucket or bucket:prefix)`);let s=await Uo(e,t,n.bucket,n.path);e.out.result({bucket:n.bucket,objects:s},()=>{s.length===0&&e.out.line(`Nothing in ${n.bucket}:${n.path}`);for(let a of s)e.out.line(`${a.path} ${a.size} ${a.content_type??""}`)})}},mP={name:"storage:cp",summary:"copy files between this computer and a bucket: a file, or a folder whole with -r",usage:"<from> <to> [-r] [--owner <user id>]",details:["bucket:path names an object of the environment; anything else is a path on this computer.","Up: a file to bucket:path (a path ending in / keeps the file name); with -r, a folder to bucket:prefix, its paths kept.","Down: bucket:path to a file; with -r, every object under bucket:prefix into a folder."],args:{min:2,max:2},flags:{recursive:{type:"boolean",short:"r",description:"a folder (up) or a prefix (down), whole"},owner:{type:"string",value:"user id",description:"the user uploaded objects belong to (deleted with them)"}},scope:"environment",async run(e){let t=await f(e),[n,s]=e.args,a=ua(n),r=ua(s),i=e.flags.recursive===!0,o=e.flags.owner;if(a&&r)throw m("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!a&&!r)throw m("one side is bucket:path");let l=0,d=0;if(r){let p=Jg(e.io.cwd,n),u=i?await Qg(p):[p];if(!i&&(await Vg(p)).isDirectory())throw m(`${n} is a folder: copy it with -r`);await v(e,t,`upload ${u.length} file${u.length===1?"":"s"} to ${r.bucket}:${r.path} in ${N(t)}`);for(let g of u){let b=i?pP(p,g).split(Kr).join("/"):g.split(Kr).pop(),w=i||r.path===""||r.path.endsWith("/")?`${r.path.replace(/\/?$/,r.path?"/":"")}${b}`:r.path;d+=await zo(e,t,g,r.bucket,w,o),l++,u.length>1&&e.out.note(`${l}/${u.length} ${w}`)}}else if(a){let p=Jg(e.io.cwd,s),u=i?await Uo(e,t,a.bucket,a.path):[{path:a.path,size:0}];for(let g of u){let b=i?g.path.slice(a.path.length).replace(/^\/+/,""):"",w=i?cP(p,b):p;d+=await hP(e,t,a.bucket,g.path,w,i?p:void 0),l++,u.length>1&&e.out.note(`${l}/${u.length} ${g.path}`)}}e.out.result({copied:l,bytes:d},()=>e.out.line(`Copied ${l} file${l===1?"":"s"} (${d} bytes).`))}},fP={name:"storage:rm",summary:"delete an object, or every object under a prefix with -r",usage:"<bucket:path> [-r]",args:{min:1,max:1},flags:{recursive:{type:"boolean",short:"r",description:"every object under the prefix"}},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0]);if(!n||!n.path&&e.flags.recursive!==!0)throw m(`${e.args[0]} is not bucket:path`);let s=e.flags.recursive===!0?(await Uo(e,t,n.bucket,n.path)).map(a=>a.path):[n.path];await v(e,t,`delete ${s.length} object${s.length===1?"":"s"} from ${n.bucket} in ${N(t)}`);for(let a of s)await h(e,t,"DELETE",`/storage/buckets/${D("the bucket",n.bucket)}/objects/${Gr(a)}`);e.out.result({deleted:s.length},()=>e.out.line(`Deleted ${s.length} object${s.length===1?"":"s"}.`))}},yP={name:"storage:url",summary:"a signed download URL for one object",usage:"<bucket:path> [--expires <seconds>]",args:{min:1,max:1},flags:{expires:{type:"string",value:"seconds",description:"how long it works (default 3600, at most 604800)"}},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0]);if(!n||!n.path)throw m(`${e.args[0]} is not bucket:path`);let s=e.flags.expires===void 0?3600:Number(e.flags.expires);if(!Number.isInteger(s)||s<1||s>604800)throw m("--expires is 1 to 604800 seconds");let a=await h(e,t,"POST",`/storage/buckets/${D("the bucket",n.bucket)}/signed-urls`,{path:n.path,expires_in:s});e.out.result(a,()=>e.out.line(a.url))}},em=[gP,mP,fP,yP];import{readFile as bP}from"node:fs/promises";import{resolve as wP}from"node:path";var Wo=/^[a-z][a-z0-9_-]{0,63}$/,tm=["backend","client","https","mcp","platform"];function dn(e,t,n=0){let s=e.args[n];if(!Wo.test(s))throw m(`${JSON.stringify(s)} is not ${t}'s name: lowercase letters, digits, _ and -, a letter first, at most 64`);return s}function jt(e,t){let n=te(e,t);if(n===0)throw m(`--${t} is a whole number from 1`);return n}function un(e){let t=new URLSearchParams;for(let[s,a]of Object.entries(e))a!==void 0&&t.set(s,String(a));let n=t.toString();return n===""?"":`?${n}`}async function nm(e,t,n){let s;try{s=await bP(wP(e.io.cwd,t),"utf8")}catch(a){throw new y("unreadable_file",`${t} could not be read: ${a instanceof Error?a.message:String(a)}`)}return ue(s,n)}var Ur=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),zr=e=>`$${e<.01&&e>0?e.toPrecision(2):e.toFixed(e<1?4:2)}`,Br=e=>`$${e.toFixed(2)}`,sm="or connect the environment's own key for the model's provider, which its provider bills: palbase ai:providers:set <provider>",Bo=`raise the budget in Studio \u203A AI \u203A Settings or with palbase ai:settings:set --monthly-budget <usd>; ${sm}`,Fo=`move the environment to Pay as you go in Studio \u203A Billing or with palbase billing:env --plan pay_as_you_go (its organization paid for by a payment account with a card, not held); ${sm}`;function am(e){if(!(e instanceof j))return e;switch(e.code){case"ai_budget_reached":return new y(e.code,"the environment's monthly AI budget is spent: Palbase's models serve it again when the month turns (UTC) or the budget is raised",{hint:Bo});case"ai_payment_required":return new y(e.code,"the month's free AI dollar is spent and the environment has nothing to pay for Palbase's models with: it is on Free, or its payment account is held",{hint:Fo});case"provider_key_required":return new y(e.code,"Palbase's models do not serve the environment, and it has no key of its own for the model's provider",{hint:"connect one: palbase ai:providers:set <provider> (palbase ai:spend says why)"});case"billing_unavailable":return new y(e.code,`Palbase's billing did not say whether its models serve the environment${e.retryAfter!==void 0?`: try again in ${e.retryAfter} s`:"; try again shortly"}`);default:return e}}function vP(e){if(e===void 0)return;let t={};for(let n of Array.isArray(e)?e:[e]){let s=String(n),a=s.indexOf("=");if(a<=0)throw m(`--metadata is key=value, not ${JSON.stringify(s)}`);Object.defineProperty(t,s.slice(0,a),{value:s.slice(a+1),enumerable:!0,writable:!0,configurable:!0})}return t}var _P={name:"ai:usage",summary:"the environment's uses of models, newest first: what each was, what it cost, and whose key it went out with",usage:"[--kind text|embedding|image] [--metadata key=value]\u2026 [--limit <n>] [--cursor <cursor>]",flags:{kind:{type:"string",value:"kind",description:"only uses of this kind: text, embedding or image"},metadata:{type:"string",multiple:!0,value:"key=value",description:"only uses carrying this label (repeat for several): a product's spend, a task's"},limit:{type:"string",value:"n",description:"at most this many (1\u2013500, default 50)"},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!==void 0&&!["text","embedding","image"].includes(t))throw m("--kind is text, embedding or image");let n=vP(e.flags.metadata),s=jt(e.flags.limit,"limit"),a=await f(e),r=await h(e,a,"GET",`/ai/usage${un({kind:t,limit:s,cursor:e.flags.cursor,metadata:n&&JSON.stringify(n)})}`);e.out.result(r,()=>{r.usage.length===0&&e.out.line("No use of a model yet."),e.out.table(r.usage,[{header:"when",value:i=>i.created_at},{header:"kind",value:i=>i.kind},{header:"model",value:i=>i.model},{header:"tokens",value:i=>`${i.kind==="image"?`${i.items} picture(s)`:`${i.input_tokens} in / ${i.output_tokens} out`}${i.estimated?" (estimate)":""}`},{header:"cost",value:i=>zr(i.cost_nano_usd/1e9)},{header:"key",value:i=>i.connection_owner==="project"?"project's":"platform's"},{header:"labels",value:i=>Object.entries(i.metadata??{}).map(([o,l])=>`${o}=${l}`).join(" ")}]),r.usage.some(i=>i.estimated)&&e.out.line("(estimate): a reasoning call cut before its answer \u2014 its provider never said what its hidden thinking came to, so it is billed as the time the model thought at its measured speed, rounded down."),r.next_cursor&&e.out.line(`More: palbase ai:usage --cursor ${r.next_cursor}`)})}},kP={name:"ai:models",summary:"the models the environment may name \u2014 every text model with its rungs, window and price, and the embedding and picture models",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/models");e.out.result(n,()=>{e.out.line("Roles (an agent names one to get the model Palbase picks for it now):"),e.out.table(n.text,[{header:"role",value:s=>s.role},{header:"what it is for",value:s=>s.description}]),e.out.line(),e.out.line("Text models (an agent's or an answer's model by its id):"),n.chat.length===0?e.out.line("None listed for the environment just now: name a role, or connect a provider's key (ai:providers:set) to list its models."):e.out.table(n.chat,[{header:"id",value:s=>`${s.id}${s.default?" (default)":""}`},{header:"roles",value:s=>s.roles.join(",")},{header:"efforts",value:s=>s.efforts.join(",")||"-"},{header:"window",value:s=>String(s.context_window)},{header:"$/M in",value:s=>String(s.price.input_usd_per_mtok)},{header:"$/M out",value:s=>String(s.price.output_usd_per_mtok)}]),e.out.line(),e.out.line("Embedding models:"),e.out.table(n.embeddings,[{header:"model",value:s=>s.model},{header:"role",value:s=>s.role||"-"},{header:"dimensions",value:s=>String(s.dimensions)},{header:"$/M in",value:s=>String(s.input_usd_per_mtok)}]),e.out.line(),e.out.line("Picture models:"),e.out.table(n.images,[{header:"model",value:s=>s.model},{header:"role",value:s=>s.role||"-"},{header:"$/M picture out",value:s=>String(s.image_output_usd_per_mtok)}]),n.unavailable.length>0&&e.out.line(`Not listed just now (their models are missing until they answer): ${n.unavailable.join(", ")}`)})}},SP={free:"Free \u2014 its free dollar a month, then its own provider keys",pay_as_you_go:"Pay as you go \u2014 past the free dollar, the account's balance pays: the provider's price and 10%",exempt:"exempt \u2014 measured, not charged"},$P={name:"ai:spend",summary:"what Palbase's models cost the environment this month: the free dollar and what is left of it, the monthly budget, and whether they serve it now \u2014 and if not, why",details:["Palbase's models are the platform's provider keys. A month brings a dollar of them free: a Free environment's own, a paying account's once for all its projects. Past it a Free environment uses its own provider keys (ai:providers:set); a Pay as you go one pays from the account's balance, at the provider's price and 10%.","Its own provider keys always serve it, and are not counted here: their provider bills them."],scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/spend");e.out.result(n,()=>{if(e.out.line(`month ${n.month} (UTC; starts again ${n.resets_on})`),e.out.line(`plan ${SP[n.plan]??n.plan}`),e.out.line(`spent ${zr(n.spent_usd)} on Palbase's models, at what it is billed, before the free dollar`),e.out.line(`free ${Br(n.free_usd)} a month, the environment's own${n.free_left_usd===null?"":` \u2014 ${zr(n.free_left_usd)} left`}`),e.out.line(`monthly budget ${n.monthly_budget_usd===null?"none":Br(n.monthly_budget_usd)}`),n.platform_keys){e.out.line("Palbase's models serve the environment now");return}n.reason==="budget"?(e.out.line(`Palbase's models do not serve the environment: its monthly budget is spent \u2014 until ${n.resets_on}, or until it is raised`),e.out.line(` ${Bo[0].toUpperCase()}${Bo.slice(1)}.`)):n.reason==="payment"?(e.out.line("Palbase's models do not serve the environment: the free dollar is spent and the account has no balance to pay"),e.out.line(` ${Fo[0].toUpperCase()}${Fo.slice(1)}.`)):(e.out.line("Palbase's models do not serve the environment"),e.out.line(" Connect the environment's own key for a provider: palbase ai:providers:set <provider>."))})}},PP={name:"ai:providers:list",summary:"the providers the environment calls with its own key (never the keys): its calls go out with them instead of the platform's",scope:"environment",async run(e){let t=await f(e),{providers:n}=await h(e,t,"GET","/ai/providers");e.out.result({providers:n},()=>{n.length===0&&e.out.line("No key of the environment's own: its calls go out with the platform's."),e.out.table(n,[{header:"provider",value:s=>s.name},{header:"verified",value:s=>s.verified?"yes":"no"},{header:"key",value:()=>"set"}])})}},AP={name:"ai:providers:set",summary:"call a provider with the environment's own key: the key is piped in or typed unseen, never shown or put on the command line",usage:"<provider> [--key-stdin] < key.txt",details:["Without --key-stdin the key is asked for at the terminal, and not echoed as it is typed or pasted."],args:{min:1,max:1},flags:{"key-stdin":{type:"boolean",description:"read the key from stdin (a pipe, a file)"}},scope:"environment",async run(e){let t=e.args[0];if(!/^[a-z][a-z0-9-]{0,31}$/.test(t))throw m(`${JSON.stringify(t)} is not a provider's name (openai, anthropic, google\u2026)`);if(e.flags["key-stdin"]!==!0&&!e.interactive)throw m(`${t}'s key is read from stdin`,`pipe it in: palbase ai:providers:set ${t} --key-stdin < key.txt`);let n=await f(e),s=await Me(e,`${t}'s key`);await v(e,n,`call ${t} with the environment's own key`);let a=await h(e,n,"PUT",`/ai/providers/${k(t)}`,{key:s});e.out.result(a,()=>e.out.line(`${a.name}'s calls go out with the environment's own key from now on${a.verified?"":" (the provider did not confirm it: check the key)"}; it is kept sealed.`))}},RP={name:"ai:providers:remove",summary:"forget the environment's own key for a provider: its calls go out with the platform's again",usage:"<provider>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await f(e);await v(e,n,`forget the environment's own ${t} key`),await h(e,n,"DELETE",`/ai/providers/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`${t}'s calls go out with the platform's key again.`))}};function rm(e,t){e.out.line(`clients ${t.clients?"on: the app's users talk to the agents with their own session":"off: the server starts and writes every chat"}`),e.out.line(`messages per minute ${t.user_messages_per_minute} a user`),e.out.line(`history kept ${t.history_bytes} bytes of ${t.history_limit_bytes===null?"no fixed limit":`${t.history_limit_bytes} at most`}`);let n=t.monthly_budget_usd??null;e.out.line(`monthly budget ${n===null?"none: Palbase's models are not capped":`${Br(n)} a month on Palbase's models`}`)}function TP(e){if(e===void 0)return;let t=String(e);if(t==="none")return null;let n=/^\d{1,7}(\.\d{1,2})?$/.test(t)?Number(t):NaN;if(!(n>=.01&&n<=1e6))throw m(`--monthly-budget is US dollars from 0.01 to 1000000, cents at most, or none \u2014 not ${JSON.stringify(t)}`,"palbase ai:settings:set --monthly-budget 25");return n}var EP={name:"ai:settings:get",summary:"what the environment allows its chats \u2014 whether the app's users talk to the agents, how often, how much history is kept \u2014 and its monthly AI budget",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/settings");e.out.result(n,()=>rm(e,n))}},CP={name:"ai:settings:set",summary:"change what the environment allows its chats, and its monthly AI budget: a setting left out is left as it is",usage:"[--clients on|off] [--messages-per-minute <n>] [--history-limit <bytes>|none] [--monthly-budget <usd>|none]",details:["--monthly-budget is the most the environment spends a month on Palbase's models (the platform's provider keys), at what it is billed: past it a call they would serve is refused (ai_budget_reached) until the month turns (UTC) or the budget is raised, and the organization's owners are emailed once. Its own provider keys are not counted: their provider bills them. palbase ai:spend says what is spent.","Only an owner or an admin of the organization sets it, as themselves: a member's change of it is refused, and so is a key's or a token's \u2014 what spends the money does not raise its own limit."],flags:{clients:{type:"string",value:"on|off",description:"the app's users talk to the agents of their chats with their own session"},"messages-per-minute":{type:"string",value:"n",description:"how many messages one user sends a minute (1\u2013600)"},"history-limit":{type:"string",value:"bytes|none",description:"the chat history kept at most, in bytes; none: the plan's"},"monthly-budget":{type:"string",value:"usd|none",description:"the most spent a month on Palbase's models, in US dollars (0.01\u20131000000, cents at most); none: no budget"}},scope:"environment",async run(e){let t={},n=e.flags.clients;if(n!==void 0){if(!["on","off"].includes(n))throw m("--clients is on or off");t.clients=n==="on"}let s=jt(e.flags["messages-per-minute"],"messages-per-minute");s!==void 0&&(t.user_messages_per_minute=s);let a=e.flags["history-limit"];a!==void 0&&(a==="none"?t.history_limit_bytes=null:t.history_limit_bytes=jt(a,"history-limit"));let r=TP(e.flags["monthly-budget"]);if(r!==void 0&&(t.monthly_budget_usd=r),Object.keys(t).length===0)throw m("nothing to change","pass --clients, --messages-per-minute, --history-limit or --monthly-budget");let i=await f(e),o=Object.keys(t).filter(p=>p!=="monthly_budget_usd"),l=[...o.length>0?[`change what the chats are allowed: ${o.join(", ")}`]:[],...r===void 0?[]:[r===null?"take away the monthly AI budget":`set the monthly AI budget to ${Br(r)}`]];await v(e,i,l.join("; "));let d;try{d=await h(e,i,"PUT","/ai/settings",t)}catch(p){throw r===void 0?p:jP(p)}e.out.result(d,()=>rm(e,d))}};function jP(e){return e instanceof j?e.status===403?new y(e.code,"only an owner or an admin of the organization sets the environment's monthly AI budget, as themselves: a member's change of it is refused, and so is a key's or a token's",{hint:"ask an owner or an admin to set it \u2014 in Studio \u203A AI \u203A Settings, or with palbase ai:settings:set --monthly-budget signed in as themselves (palbase login)"}):e.code==="no_billing"?new y(e.code,"this stack bills no AI, so it keeps no monthly AI budget: nothing was changed"):e.code==="registry_unavailable"?new y(e.code,"Palbase's billing did not answer: nothing was changed",{hint:"run the command again shortly"}):e:e}function Jo(e){return e.source==="code"?`the backend's code (machine ${e.machine??"backend"})`:e.published_version===null||e.published_version===void 0?`a draft (revision ${e.revision??"?"}), not published`:`version ${e.published_version} published${e.unpublished_changes?`, the draft (revision ${e.revision}) changed since`:""}`}var OP={name:"ai:agents:list",summary:"the environment's agents: its backend's (its build's contract names them) and those it keeps, a draft and published versions",scope:"environment",async run(e){let t=await f(e),{agents:n}=await h(e,t,"GET","/ai/agents");e.out.result({agents:n},()=>{n.length===0&&e.out.line("No agent yet: an ai.agent of the backend, or ai:agents:create <name>."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"source",value:s=>s.source},{header:"published",value:s=>s.source==="code"?"-":s.published_version?`v${s.published_version}`:"no"},{header:"draft",value:s=>s.source==="code"?"-":s.unpublished_changes?`changed (r${s.revision})`:`r${s.revision}`},{header:"machine",value:s=>s.machine??"-"},{header:"description",value:s=>s.description??""}])})}};async function MP(e,t,n,s){let a;for(let r=0;r<100;r++){let i=await h(e,t,"GET",`/ai/agents/${k(n)}/versions${un({limit:100,cursor:a})}`),o=i.versions.find(l=>l.version===s);if(o)return o;if(!i.next_cursor||i.versions.every(l=>l.version<s))break;a=i.next_cursor}throw new y("not_found",`${n} has no version ${s}`,{hint:`palbase ai:agents:versions ${n}`})}function ms(e,t,n){if(e.out.line(t),n===void 0)e.out.line(" (none yet)");else for(let s of JSON.stringify(n,null,2).split(`
|
|
641
|
+
`,{mode:384,flag:"wx"}),e.out.result({file:t,keys:s.keys.map(a=>a.id)},()=>e.out.line(`Wrote the keys of ${N(n)} (${s.keys.map(a=>a.id).join(", ")}) to ${t}.`))}};function Go(e,t){e.out.table(t.keys,[{header:"key",value:n=>`${n.id}${n.id===t.current?" (in use)":""}`},{header:"made",value:n=>n.created_at.slice(0,19).replace("T"," ")},{header:"made by",value:n=>n.created_by},{header:"retired",value:n=>n.retired_at?`${n.retired_at.slice(0,19).replace("T"," ")} by ${n.retired_by}`:""}])}var W$={name:"transit:list",summary:"the keys the encrypted fields of a request to the environment's backend are sealed to \u2014 their public halves alone",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/secrets/transit");e.out.result(n,()=>Go(e,n))}},V$={name:"transit:rotate",summary:"make a new key for the encrypted fields of requests to the backend (an owner's or an admin's)",usage:"[--retire-previous]",details:["Apps seal to it from their next fetch of the project's keys (within the hour), or at once with --retire-previous: every older key is retired with it \u2014 what to do when one may have been taken \u2014 and an app that seals to a retired one is told so, fetches the keys again and sends its call once more.","No app needs a new build: an app holds the project's signing key, not its keys."],flags:{"retire-previous":{type:"boolean",description:"retire every older key at once"}},scope:"environment",async run(e){let t=await f(e),n=e.flags["retire-previous"]===!0;await v(e,t,n?"rotate the transit keys, retiring every older one":"rotate the transit keys");let s=await h(e,t,"POST","/secrets/transit/rotate",{retire_previous:n});e.out.result(s,()=>{e.out.line(`Made ${s.current} in ${N(t)}: apps seal to it from now${n?"; every older key is retired":""}.`),Go(e,s)})}},H$={name:"transit:retire",summary:"retire a transit key that is not the one in use: no request sealed to it opens from then on",usage:"<key>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=e.args[0];if(!/^t[1-9][0-9]{0,5}$/.test(n))throw m(`${n} is not a transit key's id`,"palbase transit:list names them (t1, t2, \u2026)");await v(e,t,`retire the transit key ${n}`);let s=await h(e,t,"POST",`/secrets/transit/${n}/retire`);e.out.result(s,()=>{e.out.line(`Retired ${n} in ${N(t)}.`),Go(e,s)})}},Gg=[D$,N$,K$,q$,G$,U$,z$,B$,F$,J$,W$,V$,H$];var Y$=/^[A-Z][A-Z0-9_]{0,127}$/;function pn(e,t){return`/environments/${D("the environment's ref",e.env_ref)}/webhooks${t!==void 0?`/${D("the webhook's id",t)}`:""}`}function Ug(e){return re(e.flags.events)}function Nr(e){let t=e.machine?`, the machine ${A(e.machine)}'s \u2014 kept by the platform to what its app takes`:"",n=e.created_by?`, made by ${A(e.created_by.name)}${e.created_by.id.startsWith("tok_")?" (access token)":e.created_by.id.startsWith("etk_")?" (environment token)":""}`:"";return`${A(e.url)} \u2014 ${e.events.length===0?"every event":A(e.events.join(", "))} (${A(e.id)}${t}${n})`}function X$(e,t){let n=t.find(a=>a.id===e.endpoint_id)?.url??`${e.endpoint_id} (no longer an endpoint)`,s=e.redelivered_at?`; redelivered ${A(e.redelivered_at)}`:e.redeliver_asked_at?"; redelivery asked":e.redelivery_error?`; redelivery failed: ${A(e.redelivery_error)}`:"";return`${A(e.id)} ${A(e.event_type)} ${A(e.event_id)} \u2192 ${A(n)} \u2014 ${A(e.last_error)} (${A(String(e.attempts))} tries, given up ${A(e.failed_at)})${s}`}var Z$={name:"webhooks:list",summary:"the environment's webhook endpoints: where its events go, which (never their secrets)",scope:"environment",async run(e){let t=await f(e),{endpoints:n}=await e.studio.get(pn(t));e.out.result({endpoints:n},()=>{n.length===0&&e.out.line("No webhook endpoint yet: palbase webhooks:create <url> makes one.");for(let s of n)e.out.line(Nr(s))})}},Q$={name:"webhooks:create",summary:"add a webhook endpoint; its signing secret is printed this once, or kept in a secret with --secret-to",usage:"<url> [--events <a,b>] [--secret-to <NAME> [--dev]]",details:["A machine is given its own endpoint and its secret (PALBASE_WEBHOOK_SECRET) by the platform, for the events its app takes: this is for another receiver. --secret-to keeps the secret in the environment's secrets (production; --dev: development) and prints it nowhere."],args:{min:1,max:1},flags:{events:{type:"string",value:"list",description:"the events it is sent, comma-separated (user.deleted, \u2026); none: every event"},"secret-to":{type:"string",value:"NAME",description:"keep its signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}},scope:"environment",async run(e){let t=await f(e),n=e.flags["secret-to"];if(n!==void 0&&!Y$.test(n))throw m(`--secret-to ${JSON.stringify(n)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&n===void 0)throw m("--dev goes with --secret-to <NAME>");let s=e.flags.dev===!0?"development":"production";await v(e,t,`add the webhook endpoint ${e.args[0]}${n?`, its secret kept in ${n} (${s})`:""}`);let a=await e.studio.post(pn(t),{url:e.args[0],events:Ug(e)??[]}),{secret:r,...i}=a;if(n!==void 0){let o=await h(e,t,"PUT",`/secrets/${s}/${n}`,{value:r});e.out.result({endpoint:i,secret_to:{set:s,name:n,version:o.version}},()=>e.out.line(`Added ${Nr(i)}; its secret is ${n} (${s}, v${o.version}) in ${N(t)}.`));return}e.out.result(a,()=>{e.out.line(`Added ${Nr(i)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}},eP={name:"webhooks:update",summary:"change a webhook endpoint's address or events (its secret stays)",usage:'<id> [--url <url>] [--events <a,b>|""]',args:{min:1,max:1},flags:{url:{type:"string",value:"url",description:"where it is delivered now"},events:{type:"string",value:"list",description:'the events it is sent now, comma-separated; "" for every event'}},scope:"environment",async run(e){let t=await f(e);if(e.flags.url===void 0&&e.flags.events===void 0)throw m("nothing to change: --url or --events");let{endpoints:n}=await e.studio.get(pn(t)),s=n.find(r=>r.id===e.args[0]);if(!s)throw m(`the environment has no webhook endpoint ${JSON.stringify(e.args[0])}`,"palbase webhooks:list shows them");await v(e,t,`change the webhook endpoint ${s.url}`);let a=await e.studio.request("PUT",pn(t,s.id),{url:e.flags.url??s.url,events:Ug(e)??s.events});e.out.result(a,()=>e.out.line(`Changed: ${Nr(a)}.`))}},tP={name:"webhooks:delete",summary:"remove a webhook endpoint: its events are no longer delivered there",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`remove the webhook endpoint ${e.args[0]}`),await e.studio.request("DELETE",pn(t,e.args[0])),e.out.result({deleted:e.args[0]},()=>e.out.line(`Removed the webhook endpoint ${e.args[0]}.`))}},nP={name:"webhooks:deliveries:list",summary:"the deliveries the platform gave up on after its tries, still owed (--failed), or every one kept 30 days (--all)",usage:"[--failed] [--all]",flags:{failed:{type:"boolean",description:"those it gave up on and still owes (the default)"},all:{type:"boolean",description:"every one kept, those delivered again among them"}},scope:"environment",async run(e){let t=await f(e),[{deliveries:n},{endpoints:s}]=await Promise.all([e.studio.get(`${pn(t)}/deliveries${e.flags.all===!0?"?all=true":"?failed=true"}`),e.studio.get(pn(t))]);e.out.result({deliveries:n},()=>{n.length===0&&e.out.line("No failed delivery: every event reached its endpoints.");for(let a of n)e.out.line(X$(a,s))})}},sP={name:"webhooks:deliveries:redeliver",summary:"send a failed delivery again: the same event, its id and body, to the endpoint it failed at, within seconds",usage:"<id>",details:["As Stripe's Resend: the receiver takes an event more than once by its Palbase-Event-Id. An admin's; it is written to the organization's audit log."],args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e);await v(e,t,`send the failed delivery ${e.args[0]} again`);let n=await e.studio.post(`${pn(t)}/deliveries/${D("the delivery's id",e.args[0])}/redeliver`,{});e.out.result(n,()=>e.out.line(`Asked: ${A(n.event_type)} ${A(n.event_id)} goes to its endpoint again within seconds (palbase webhooks:deliveries:list --all shows when it went).`))}},zg=[Z$,Q$,eP,tP,nP,sP];import{constants as qr,createReadStream as aP}from"node:fs";import{mkdir as rP,open as iP,readdir as oP,realpath as Bg,stat as Vg}from"node:fs/promises";import{dirname as Fg,extname as lP,join as Hg,relative as pP,resolve as Jg,sep as Kr}from"node:path";import{Readable as Yg}from"node:stream";import{pipeline as dP}from"node:stream/promises";var Xg=600;function ua(e){let t=/^([a-z0-9][a-z0-9_-]{0,62}):(.*)$/.exec(e);return!t||e.startsWith("/")||e.startsWith(".")?null:{bucket:t[1],path:t[2].replace(/^\/+/,"")}}function Gr(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new y("unsafe_path",`the object ${JSON.stringify(e)} has a name no request can carry`);return encodeURIComponent(e)}async function Uo(e,t,n,s){let a=[],r;do{let i=new URLSearchParams({limit:"1000",...s?{prefix:s}:{},...r?{cursor:r}:{}}),o=await h(e,t,"GET",`/storage/buckets/${D("the bucket",n)}/objects?${i}`);a.push(...o.objects),r=o.next_cursor}while(r);return a}async function Zg(e,t,n,s){let a;try{a=await e.io.fetch(t,{...n,redirect:"manual"})}catch(r){throw new y("network_error",`${s}: ${r instanceof Error?r.message:String(r)}`)}if(!a.ok){let r=await a.json().catch(()=>({}));throw new y(r.error??`http_${a.status}`,`${s}: ${r.error_description??r.error??`answered ${a.status}`}`)}return a}var uP={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".heif":"image/heif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".pdf":"application/pdf",".json":"application/json",".txt":"text/plain",".csv":"text/csv",".html":"text/html",".css":"text/css",".js":"text/javascript",".xml":"application/xml",".zip":"application/zip",".mp4":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4"};function Wg(e){return uP[lP(e).toLowerCase()]}async function zo(e,t,n,s,a,r){let i=(await Vg(n)).size,o=await h(e,t,"POST",`/storage/buckets/${D("the bucket",s)}/upload-urls`,{path:a,expires_in:Xg,...r?{owner_id:r}:{}});return await Zg(e,o.url,{method:"PUT",body:Yg.toWeb(aP(n)),headers:{"content-length":String(i),...Wg(n)?{"content-type":Wg(n)}:{}},duplex:"half"},`${n} \u2192 ${s}:${a}`),i}function cP(e,t){let n=t.split("/");if(t===""||n.some(a=>a===""||a==="."||a===".."||a.includes("\\")||a.includes("\0")))throw new y("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=Hg(e,...n);if(!s.startsWith(e+Kr))throw new y("unsafe_path",`${t} would land outside ${e}`);return s}async function hP(e,t,n,s,a,r){let i=await h(e,t,"POST",`/storage/buckets/${D("the bucket",n)}/signed-urls`,{path:s,expires_in:Xg}),o=await Zg(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${a}`);if(await rP(Fg(a),{recursive:!0}),r!==void 0){let[g,b]=await Promise.all([Bg(r),Bg(Fg(a))]);if(b!==g&&!b.startsWith(g+Kr))throw new y("unsafe_path",`${s} would land outside ${r} (through a link)`)}let d=(await iP(a,qr.O_WRONLY|qr.O_CREAT|qr.O_TRUNC|qr.O_NOFOLLOW)).createWriteStream();if(!o.body)return await new Promise((g,b)=>d.on("error",b).end(g)),0;let p=0,u=Yg.fromWeb(o.body).on("data",g=>{p+=g.length});return await dP(u,d),p}async function Qg(e){let t=[];for(let n of await oP(e,{withFileTypes:!0})){let s=Hg(e,n.name);n.isDirectory()?t.push(...await Qg(s)):n.isFile()&&t.push(s)}return t}var gP={name:"storage:ls",summary:"a bucket's objects, or those under a prefix: path, size, type",usage:"<bucket>[:prefix]",args:{min:1,max:1},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0].includes(":")?e.args[0]:`${e.args[0]}:`);if(!n)throw m(`${e.args[0]} is not a bucket (bucket or bucket:prefix)`);let s=await Uo(e,t,n.bucket,n.path);e.out.result({bucket:n.bucket,objects:s},()=>{s.length===0&&e.out.line(`Nothing in ${n.bucket}:${n.path}`);for(let a of s)e.out.line(`${a.path} ${a.size} ${a.content_type??""}`)})}},mP={name:"storage:cp",summary:"copy files between this computer and a bucket: a file, or a folder whole with -r",usage:"<from> <to> [-r] [--owner <user id>]",details:["bucket:path names an object of the environment; anything else is a path on this computer.","Up: a file to bucket:path (a path ending in / keeps the file name); with -r, a folder to bucket:prefix, its paths kept.","Down: bucket:path to a file; with -r, every object under bucket:prefix into a folder."],args:{min:2,max:2},flags:{recursive:{type:"boolean",short:"r",description:"a folder (up) or a prefix (down), whole"},owner:{type:"string",value:"user id",description:"the user uploaded objects belong to (deleted with them)"}},scope:"environment",async run(e){let t=await f(e),[n,s]=e.args,a=ua(n),r=ua(s),i=e.flags.recursive===!0,o=e.flags.owner;if(a&&r)throw m("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!a&&!r)throw m("one side is bucket:path");let l=0,d=0;if(r){let p=Jg(e.io.cwd,n),u=i?await Qg(p):[p];if(!i&&(await Vg(p)).isDirectory())throw m(`${n} is a folder: copy it with -r`);await v(e,t,`upload ${u.length} file${u.length===1?"":"s"} to ${r.bucket}:${r.path} in ${N(t)}`);for(let g of u){let b=i?pP(p,g).split(Kr).join("/"):g.split(Kr).pop(),w=i||r.path===""||r.path.endsWith("/")?`${r.path.replace(/\/?$/,r.path?"/":"")}${b}`:r.path;d+=await zo(e,t,g,r.bucket,w,o),l++,u.length>1&&e.out.note(`${l}/${u.length} ${w}`)}}else if(a){let p=Jg(e.io.cwd,s),u=i?await Uo(e,t,a.bucket,a.path):[{path:a.path,size:0}];for(let g of u){let b=i?g.path.slice(a.path.length).replace(/^\/+/,""):"",w=i?cP(p,b):p;d+=await hP(e,t,a.bucket,g.path,w,i?p:void 0),l++,u.length>1&&e.out.note(`${l}/${u.length} ${g.path}`)}}e.out.result({copied:l,bytes:d},()=>e.out.line(`Copied ${l} file${l===1?"":"s"} (${d} bytes).`))}},fP={name:"storage:rm",summary:"delete an object, or every object under a prefix with -r",usage:"<bucket:path> [-r]",args:{min:1,max:1},flags:{recursive:{type:"boolean",short:"r",description:"every object under the prefix"}},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0]);if(!n||!n.path&&e.flags.recursive!==!0)throw m(`${e.args[0]} is not bucket:path`);let s=e.flags.recursive===!0?(await Uo(e,t,n.bucket,n.path)).map(a=>a.path):[n.path];await v(e,t,`delete ${s.length} object${s.length===1?"":"s"} from ${n.bucket} in ${N(t)}`);for(let a of s)await h(e,t,"DELETE",`/storage/buckets/${D("the bucket",n.bucket)}/objects/${Gr(a)}`);e.out.result({deleted:s.length},()=>e.out.line(`Deleted ${s.length} object${s.length===1?"":"s"}.`))}},yP={name:"storage:url",summary:"a signed download URL for one object",usage:"<bucket:path> [--expires <seconds>]",args:{min:1,max:1},flags:{expires:{type:"string",value:"seconds",description:"how long it works (default 3600, at most 604800)"}},scope:"environment",async run(e){let t=await f(e),n=ua(e.args[0]);if(!n||!n.path)throw m(`${e.args[0]} is not bucket:path`);let s=e.flags.expires===void 0?3600:Number(e.flags.expires);if(!Number.isInteger(s)||s<1||s>604800)throw m("--expires is 1 to 604800 seconds");let a=await h(e,t,"POST",`/storage/buckets/${D("the bucket",n.bucket)}/signed-urls`,{path:n.path,expires_in:s});e.out.result(a,()=>e.out.line(a.url))}},em=[gP,mP,fP,yP];import{readFile as bP}from"node:fs/promises";import{resolve as wP}from"node:path";var Wo=/^[a-z][a-z0-9_-]{0,63}$/,tm=["backend","client","https","mcp","platform"];function dn(e,t,n=0){let s=e.args[n];if(!Wo.test(s))throw m(`${JSON.stringify(s)} is not ${t}'s name: lowercase letters, digits, _ and -, a letter first, at most 64`);return s}function jt(e,t){let n=te(e,t);if(n===0)throw m(`--${t} is a whole number from 1`);return n}function un(e){let t=new URLSearchParams;for(let[s,a]of Object.entries(e))a!==void 0&&t.set(s,String(a));let n=t.toString();return n===""?"":`?${n}`}async function nm(e,t,n){let s;try{s=await bP(wP(e.io.cwd,t),"utf8")}catch(a){throw new y("unreadable_file",`${t} could not be read: ${a instanceof Error?a.message:String(a)}`)}return ue(s,n)}var Ur=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),zr=e=>`$${e<.01&&e>0?e.toPrecision(2):e.toFixed(e<1?4:2)}`,Br=e=>`$${e.toFixed(2)}`,sm="or connect the environment's own key for the model's provider, which its provider bills: palbase ai:providers:set <provider>",Bo=`raise the budget in Studio \u203A AI \u203A Settings or with palbase ai:settings:set --monthly-budget <usd>; ${sm}`,Fo=`move the environment to Pay as you go in Studio \u203A Billing or with palbase billing:env --plan pay_as_you_go (its organization paid for by a payment account with a card, not held); ${sm}`;function am(e){if(!(e instanceof j))return e;switch(e.code){case"ai_budget_reached":return new y(e.code,"the environment's monthly AI budget is spent: Palbase's models serve it again when the month turns (UTC) or the budget is raised",{hint:Bo});case"ai_payment_required":return new y(e.code,"the month's free AI dollar is spent and the environment has nothing to pay for Palbase's models with: it is on Free, or its payment account is held",{hint:Fo});case"provider_key_required":return new y(e.code,"Palbase's models do not serve the environment, and it has no key of its own for the model's provider",{hint:"connect one: palbase ai:providers:set <provider> (palbase ai:spend says why)"});case"billing_unavailable":return new y(e.code,`Palbase's billing did not say whether its models serve the environment${e.retryAfter!==void 0?`: try again in ${e.retryAfter} s`:"; try again shortly"}`);default:return e}}function vP(e){if(e===void 0)return;let t={};for(let n of Array.isArray(e)?e:[e]){let s=String(n),a=s.indexOf("=");if(a<=0)throw m(`--metadata is key=value, not ${JSON.stringify(s)}`);Object.defineProperty(t,s.slice(0,a),{value:s.slice(a+1),enumerable:!0,writable:!0,configurable:!0})}return t}var _P={name:"ai:usage",summary:"the environment's uses of models, newest first: what each was, what it cost, and whose key it went out with",usage:"[--kind text|embedding|image] [--metadata key=value]\u2026 [--limit <n>] [--cursor <cursor>]",flags:{kind:{type:"string",value:"kind",description:"only uses of this kind: text, embedding or image"},metadata:{type:"string",multiple:!0,value:"key=value",description:"only uses carrying this label (repeat for several): a product's spend, a task's"},limit:{type:"string",value:"n",description:"at most this many (1\u2013500, default 50)"},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!==void 0&&!["text","embedding","image"].includes(t))throw m("--kind is text, embedding or image");let n=vP(e.flags.metadata),s=jt(e.flags.limit,"limit"),a=await f(e),r=await h(e,a,"GET",`/ai/usage${un({kind:t,limit:s,cursor:e.flags.cursor,metadata:n&&JSON.stringify(n)})}`);e.out.result(r,()=>{r.usage.length===0&&e.out.line("No use of a model yet."),e.out.table(r.usage,[{header:"when",value:i=>i.created_at},{header:"kind",value:i=>i.kind},{header:"model",value:i=>i.model},{header:"tokens",value:i=>`${i.kind==="image"?`${i.items} picture(s)`:`${i.input_tokens} in / ${i.output_tokens} out`}${i.estimated?" (estimate)":""}`},{header:"cost",value:i=>zr(i.cost_nano_usd/1e9)},{header:"key",value:i=>i.connection_owner==="project"?"project's":"platform's"},{header:"labels",value:i=>Object.entries(i.metadata??{}).map(([o,l])=>`${o}=${l}`).join(" ")}]),r.usage.some(i=>i.estimated)&&e.out.line("(estimate): a reasoning call cut before its answer \u2014 its provider never said what its hidden thinking came to, so it is billed as the time the model thought at its measured speed, rounded down."),r.next_cursor&&e.out.line(`More: palbase ai:usage --cursor ${r.next_cursor}`)})}},kP={name:"ai:models",summary:"the models the environment may name \u2014 every text model with its rungs, window and price, and the embedding and picture models",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/models");e.out.result(n,()=>{e.out.line("Roles (an agent names one to get the model Palbase picks for it now):"),e.out.table(n.text,[{header:"role",value:s=>s.role},{header:"what it is for",value:s=>s.description}]),e.out.line(),e.out.line("Text models (an agent's or an answer's model by its id):"),n.chat.length===0?e.out.line("None listed for the environment just now: name a role, or connect a provider's key (ai:providers:set) to list its models."):e.out.table(n.chat,[{header:"id",value:s=>`${s.id}${s.default?" (default)":""}`},{header:"roles",value:s=>s.roles.join(",")},{header:"efforts",value:s=>s.efforts.join(",")||"-"},{header:"window",value:s=>String(s.context_window)},{header:"$/M in",value:s=>String(s.price.input_usd_per_mtok)},{header:"$/M out",value:s=>String(s.price.output_usd_per_mtok)}]),e.out.line(),e.out.line("Embedding models:"),e.out.table(n.embeddings,[{header:"model",value:s=>s.model},{header:"role",value:s=>s.role||"-"},{header:"dimensions",value:s=>String(s.dimensions)},{header:"$/M in",value:s=>String(s.input_usd_per_mtok)}]),e.out.line(),e.out.line("Picture models:"),e.out.table(n.images,[{header:"model",value:s=>s.model},{header:"role",value:s=>s.role||"-"},{header:"$/M picture out",value:s=>String(s.image_output_usd_per_mtok)}]),n.unavailable.length>0&&e.out.line(`Not listed just now (their models are missing until they answer): ${n.unavailable.join(", ")}`)})}},SP={free:"Free \u2014 its free dollar a month, then its own provider keys",pay_as_you_go:"Pay as you go \u2014 past the free dollar, the account's balance pays: the provider's price and 10%",exempt:"exempt \u2014 measured, not charged"},$P={name:"ai:spend",summary:"what Palbase's models cost the environment this month: the free dollar and what is left of it, the monthly budget, and whether they serve it now \u2014 and if not, why",details:["Palbase's models are the platform's provider keys. A month brings a dollar of them free: a Free environment's own, a paying account's once for all its projects. Past it a Free environment uses its own provider keys (ai:providers:set); a Pay as you go one pays from the account's balance, at the provider's price and 10%.","Its own provider keys always serve it, and are not counted here: their provider bills them."],scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/spend");e.out.result(n,()=>{if(e.out.line(`month ${n.month} (UTC; starts again ${n.resets_on})`),e.out.line(`plan ${SP[n.plan]??n.plan}`),e.out.line(`spent ${zr(n.spent_usd)} on Palbase's models, at what it is billed, before the free dollar`),e.out.line(`free ${Br(n.free_usd)} a month, the environment's own${n.free_left_usd===null?"":` \u2014 ${zr(n.free_left_usd)} left`}`),e.out.line(`monthly budget ${n.monthly_budget_usd===null?"none":Br(n.monthly_budget_usd)}`),n.platform_keys){e.out.line("Palbase's models serve the environment now");return}n.reason==="budget"?(e.out.line(`Palbase's models do not serve the environment: its monthly budget is spent \u2014 until ${n.resets_on}, or until it is raised`),e.out.line(` ${Bo[0].toUpperCase()}${Bo.slice(1)}.`)):n.reason==="payment"?(e.out.line("Palbase's models do not serve the environment: the free dollar is spent and it has no paid rights now (it is on Free, at its month's limit, or its payment account has an unpaid charge)"),e.out.line(` ${Fo[0].toUpperCase()}${Fo.slice(1)}.`)):(e.out.line("Palbase's models do not serve the environment"),e.out.line(" Connect the environment's own key for a provider: palbase ai:providers:set <provider>."))})}},PP={name:"ai:providers:list",summary:"the providers the environment calls with its own key (never the keys): its calls go out with them instead of the platform's",scope:"environment",async run(e){let t=await f(e),{providers:n}=await h(e,t,"GET","/ai/providers");e.out.result({providers:n},()=>{n.length===0&&e.out.line("No key of the environment's own: its calls go out with the platform's."),e.out.table(n,[{header:"provider",value:s=>s.name},{header:"verified",value:s=>s.verified?"yes":"no"},{header:"key",value:()=>"set"}])})}},AP={name:"ai:providers:set",summary:"call a provider with the environment's own key: the key is piped in or typed unseen, never shown or put on the command line",usage:"<provider> [--key-stdin] < key.txt",details:["Without --key-stdin the key is asked for at the terminal, and not echoed as it is typed or pasted."],args:{min:1,max:1},flags:{"key-stdin":{type:"boolean",description:"read the key from stdin (a pipe, a file)"}},scope:"environment",async run(e){let t=e.args[0];if(!/^[a-z][a-z0-9-]{0,31}$/.test(t))throw m(`${JSON.stringify(t)} is not a provider's name (openai, anthropic, google\u2026)`);if(e.flags["key-stdin"]!==!0&&!e.interactive)throw m(`${t}'s key is read from stdin`,`pipe it in: palbase ai:providers:set ${t} --key-stdin < key.txt`);let n=await f(e),s=await Me(e,`${t}'s key`);await v(e,n,`call ${t} with the environment's own key`);let a=await h(e,n,"PUT",`/ai/providers/${k(t)}`,{key:s});e.out.result(a,()=>e.out.line(`${a.name}'s calls go out with the environment's own key from now on${a.verified?"":" (the provider did not confirm it: check the key)"}; it is kept sealed.`))}},RP={name:"ai:providers:remove",summary:"forget the environment's own key for a provider: its calls go out with the platform's again",usage:"<provider>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await f(e);await v(e,n,`forget the environment's own ${t} key`),await h(e,n,"DELETE",`/ai/providers/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`${t}'s calls go out with the platform's key again.`))}};function rm(e,t){e.out.line(`clients ${t.clients?"on: the app's users talk to the agents with their own session":"off: the server starts and writes every chat"}`),e.out.line(`messages per minute ${t.user_messages_per_minute} a user`),e.out.line(`history kept ${t.history_bytes} bytes of ${t.history_limit_bytes===null?"no fixed limit":`${t.history_limit_bytes} at most`}`);let n=t.monthly_budget_usd??null;e.out.line(`monthly budget ${n===null?"none: Palbase's models are not capped":`${Br(n)} a month on Palbase's models`}`)}function TP(e){if(e===void 0)return;let t=String(e);if(t==="none")return null;let n=/^\d{1,7}(\.\d{1,2})?$/.test(t)?Number(t):NaN;if(!(n>=.01&&n<=1e6))throw m(`--monthly-budget is US dollars from 0.01 to 1000000, cents at most, or none \u2014 not ${JSON.stringify(t)}`,"palbase ai:settings:set --monthly-budget 25");return n}var EP={name:"ai:settings:get",summary:"what the environment allows its chats \u2014 whether the app's users talk to the agents, how often, how much history is kept \u2014 and its monthly AI budget",scope:"environment",async run(e){let t=await f(e),n=await h(e,t,"GET","/ai/settings");e.out.result(n,()=>rm(e,n))}},CP={name:"ai:settings:set",summary:"change what the environment allows its chats, and its monthly AI budget: a setting left out is left as it is",usage:"[--clients on|off] [--messages-per-minute <n>] [--history-limit <bytes>|none] [--monthly-budget <usd>|none]",details:["--monthly-budget is the most the environment spends a month on Palbase's models (the platform's provider keys), at what it is billed: past it a call they would serve is refused (ai_budget_reached) until the month turns (UTC) or the budget is raised, and the organization's owners are emailed once. Its own provider keys are not counted: their provider bills them. palbase ai:spend says what is spent.","Only an owner or an admin of the organization sets it, as themselves: a member's change of it is refused, and so is a key's or a token's \u2014 what spends the money does not raise its own limit."],flags:{clients:{type:"string",value:"on|off",description:"the app's users talk to the agents of their chats with their own session"},"messages-per-minute":{type:"string",value:"n",description:"how many messages one user sends a minute (1\u2013600)"},"history-limit":{type:"string",value:"bytes|none",description:"the chat history kept at most, in bytes; none: the plan's"},"monthly-budget":{type:"string",value:"usd|none",description:"the most spent a month on Palbase's models, in US dollars (0.01\u20131000000, cents at most); none: no budget"}},scope:"environment",async run(e){let t={},n=e.flags.clients;if(n!==void 0){if(!["on","off"].includes(n))throw m("--clients is on or off");t.clients=n==="on"}let s=jt(e.flags["messages-per-minute"],"messages-per-minute");s!==void 0&&(t.user_messages_per_minute=s);let a=e.flags["history-limit"];a!==void 0&&(a==="none"?t.history_limit_bytes=null:t.history_limit_bytes=jt(a,"history-limit"));let r=TP(e.flags["monthly-budget"]);if(r!==void 0&&(t.monthly_budget_usd=r),Object.keys(t).length===0)throw m("nothing to change","pass --clients, --messages-per-minute, --history-limit or --monthly-budget");let i=await f(e),o=Object.keys(t).filter(p=>p!=="monthly_budget_usd"),l=[...o.length>0?[`change what the chats are allowed: ${o.join(", ")}`]:[],...r===void 0?[]:[r===null?"take away the monthly AI budget":`set the monthly AI budget to ${Br(r)}`]];await v(e,i,l.join("; "));let d;try{d=await h(e,i,"PUT","/ai/settings",t)}catch(p){throw r===void 0?p:jP(p)}e.out.result(d,()=>rm(e,d))}};function jP(e){return e instanceof j?e.status===403?new y(e.code,"only an owner or an admin of the organization sets the environment's monthly AI budget, as themselves: a member's change of it is refused, and so is a key's or a token's",{hint:"ask an owner or an admin to set it \u2014 in Studio \u203A AI \u203A Settings, or with palbase ai:settings:set --monthly-budget signed in as themselves (palbase login)"}):e.code==="no_billing"?new y(e.code,"this stack bills no AI, so it keeps no monthly AI budget: nothing was changed"):e.code==="registry_unavailable"?new y(e.code,"Palbase's billing did not answer: nothing was changed",{hint:"run the command again shortly"}):e:e}function Jo(e){return e.source==="code"?`the backend's code (machine ${e.machine??"backend"})`:e.published_version===null||e.published_version===void 0?`a draft (revision ${e.revision??"?"}), not published`:`version ${e.published_version} published${e.unpublished_changes?`, the draft (revision ${e.revision}) changed since`:""}`}var OP={name:"ai:agents:list",summary:"the environment's agents: its backend's (its build's contract names them) and those it keeps, a draft and published versions",scope:"environment",async run(e){let t=await f(e),{agents:n}=await h(e,t,"GET","/ai/agents");e.out.result({agents:n},()=>{n.length===0&&e.out.line("No agent yet: an ai.agent of the backend, or ai:agents:create <name>."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"source",value:s=>s.source},{header:"published",value:s=>s.source==="code"?"-":s.published_version?`v${s.published_version}`:"no"},{header:"draft",value:s=>s.source==="code"?"-":s.unpublished_changes?`changed (r${s.revision})`:`r${s.revision}`},{header:"machine",value:s=>s.machine??"-"},{header:"description",value:s=>s.description??""}])})}};async function MP(e,t,n,s){let a;for(let r=0;r<100;r++){let i=await h(e,t,"GET",`/ai/agents/${k(n)}/versions${un({limit:100,cursor:a})}`),o=i.versions.find(l=>l.version===s);if(o)return o;if(!i.next_cursor||i.versions.every(l=>l.version<s))break;a=i.next_cursor}throw new y("not_found",`${n} has no version ${s}`,{hint:`palbase ai:agents:versions ${n}`})}function ms(e,t,n){if(e.out.line(t),n===void 0)e.out.line(" (none yet)");else for(let s of JSON.stringify(n,null,2).split(`
|
|
642
642
|
`))e.out.line(` ${s}`)}var xP={name:"ai:agents:show",summary:"one agent: where it stands and what it is \u2014 a kept one's published version (or its draft, or a version), a backend's last turn",usage:"<name> [--draft | --version <n>]",args:{min:1,max:1},flags:{draft:{type:"boolean",description:"the draft, as the next publish makes a version of it"},version:{type:"string",value:"n",description:"that version, as it was published"}},scope:"environment",async run(e){let t=dn(e,"an agent"),n=jt(e.flags.version,"version");if(n!==void 0&&e.flags.draft===!0)throw m("--draft or --version, not both");let s=await f(e);if(n!==void 0){let r=await MP(e,s,t,n);e.out.result(r,()=>ms(e,`${t} version ${r.version}, published ${r.published_at} by ${r.published_by}:`,r.definition));return}let a=await h(e,s,"GET",`/ai/agents/${k(t)}`);if(e.flags.draft===!0&&a.source==="code")throw new y("agent_defined_in_code",`${t} is the backend's code's: it has no draft \u2014 it changes with the code`);e.out.result(a,()=>{e.out.line(`${a.name}: ${Jo(a)}`),a.description&&e.out.line(a.description),a.updated_at&&e.out.line(`Draft saved ${a.updated_at}${a.updated_by?` by ${a.updated_by}`:""}.`),e.out.line(),a.source==="code"?ms(e,`Its last turn${a.defined_at?` (${a.defined_at})`:""} was defined as:`,a.definition):e.flags.draft===!0||a.published===void 0?ms(e,`The draft (revision ${a.revision}):`,a.draft):ms(e,`Version ${a.published_version}, the one its chats talk to:`,a.published)})}};function IP(e){let t=e.indexOf(":"),n=t>0?e.slice(0,t):"",s=e.slice(t+1);if(!tm.includes(n)||s==="")throw m(`--tool is source:name \u2014 ${tm.join(", ")} \u2014 not ${JSON.stringify(e)}`,"palbase ai:tools lists the tools an agent may be given");let a=n==="backend"?s.indexOf("/"):-1;if(a<0)return{source:n,name:s};let r=s.slice(0,a);if(s=s.slice(a+1),!Wo.test(r)||s==="")throw m(`--tool backend:<machine>/<name> names one of the environment's machines and its tool, not ${JSON.stringify(e)}`);return{source:"backend",name:s,machine:r}}async function LP(e){let t={},n=e.flags.from;if(n!==void 0){let a=await nm(e,n,n),r=Ur(a)&&Ur(a.definition)&&typeof a.name=="string"?a.definition:a;if(!Ur(r))throw m(`${n} is an agent's definition: a JSON object { instructions, model, tools, \u2026 }`);t=r}e.flags.instructions!==void 0&&(t.instructions=e.flags.instructions),e.flags.description!==void 0&&(t.description=e.flags.description),e.flags.model!==void 0&&(t.model=e.flags.model),e.flags.tool!==void 0&&(t.tools=e.flags.tool.map(IP));let s=e.flags["output-schema"];if(s!==void 0){let a=await nm(e,s,s);if(!Ur(a)||a.type!=="object")throw m(`${s} is the JSON Schema of the agent's one-shot answers: an object's ({ "type": "object", "properties": \u2026 })`);t.output=a}if(t.backend!==void 0)throw m("an agent the environment keeps names no backend: a backend's agent is its code's (ai.agent)");return t}async function DP(e,t,n){try{let s=await h(e,t,"GET",`/ai/agents/${k(n)}`);return s.source==="platform"&&s.draft&&s.revision!==void 0?{draft:s.draft,revision:s.revision}:void 0}catch(s){if(s instanceof j&&s.status===404)return;throw s}}async function im(e,t,n,s,a){return h(e,t,"PUT",`/ai/agents/${k(n)}${un({revision:a})}`,s)}async function Vo(e,t,n,s){return h(e,t,"POST",`/ai/agents/${k(n)}/publish`,s!==void 0?{revision:s}:{})}var NP={name:"ai:agents:create",summary:"save the draft of an agent the environment keeps \u2014 a new one's, or a change of one's \u2014 and publish it with --publish",usage:"<name> [--from <file.json>] [--instructions <text>] [--description <text>] [--model <model>] [--tool <source:name>]\u2026 [--output-schema <file.json>] [--revision <n>] [--publish]",details:["Its chats talk to the version published: a draft changes nothing until it is published (ai:agents:publish, or --publish here).","A change of an agent it keeps is made on its draft: each flag given replaces its field, and the rest stays.",'--from is the whole definition as JSON ({ "instructions", "model", "tools": [{ "source", "name" }], "subagents", "output", \u2026 }) \u2014 or what ai:agents:generate --json printed; each flag given replaces its field.',"--tool is source:name: backend:<ai.tool> (backend:<machine>/<ai.tool> for another machine's), client:<ai.clientTool>, https:<endpoint>, mcp:<server>, platform:web.search|web.fetch|workspace.fetch_archive (palbase ai:tools lists them). Given, the tools are those; a backend or client tool goes by its name alone, and the platform takes what it does from the build that declares it.","--revision is the draft's revision as it was read (ai:agents:show): when another was saved since, the save is refused (draft_changed) and nothing is lost."],args:{min:1,max:1},flags:{from:{type:"string",value:"file.json",description:"the definition, as JSON"},instructions:{type:"string",value:"text",description:"what the agent is told to do"},description:{type:"string",value:"text",description:"what it is for, in a sentence or two"},model:{type:"string",value:"model",description:"a role (fast, default, strong) or a model of ai:models"},tool:{type:"string",multiple:!0,value:"source:name",description:"a tool it is given (repeat for several)"},"output-schema":{type:"string",value:"file.json",description:"the JSON Schema of its one-shot answers"},revision:{type:"string",value:"n",description:"the draft's revision the change is made on"},publish:{type:"boolean",description:"publish the draft as the next version once it is saved"}},scope:"environment",async run(e){let t=dn(e,"an agent"),n=jt(e.flags.revision,"revision"),s=await LP(e);if(Object.keys(s).length===0)throw m("the agent is empty","pass --from <file.json>, or --instructions, --model, --tool\u2026");let a=await f(e),r=e.flags.from===void 0?await DP(e,a,t):void 0,i={...r?.draft,...s},o=e.flags.publish===!0;await v(e,a,`save the draft of the agent ${t}${o?" and publish it":""}`);let l=await im(e,a,t,i,n??r?.revision),d=o?await Vo(e,a,t,l.revision):l;e.out.result(d,()=>{e.out.line(`Saved the draft of ${t} (revision ${l.revision}).`),o?e.out.line(`Published ${t} as version ${d.published_version}: its chats talk to it from their next turn.`):e.out.line(`${Jo(d)[0].toUpperCase()}${Jo(d).slice(1)}: palbase ai:agents:publish ${t} makes its chats talk to it.`)})}},qP={name:"ai:agents:publish",summary:"make the draft of an agent the next version, the one its chats talk to from their next turn",usage:"<name> [--revision <n>]",args:{min:1,max:1},flags:{revision:{type:"string",value:"n",description:"the draft as it was read: refused when another was saved since"}},scope:"environment",async run(e){let t=dn(e,"an agent"),n=jt(e.flags.revision,"revision"),s=await f(e);await v(e,s,`publish the draft of the agent ${t}`);let a=await Vo(e,s,t,n);e.out.result(a,()=>e.out.line(`Published ${t} as version ${a.published_version}: its chats talk to it from their next turn.`))}},KP={name:"ai:agents:rollback",summary:"make a version an agent had the one its chats talk to again \u2014 and its draft that version",usage:"<name> <version>",args:{min:2,max:2},scope:"environment",async run(e){let t=dn(e,"an agent"),n=Number(e.args[1]);if(!Number.isSafeInteger(n)||n<1)throw m(`the version is a version the agent had, a whole number from 1 (not ${JSON.stringify(e.args[1])})`,`palbase ai:agents:versions ${t}`);let s=await f(e);await v(e,s,`make version ${n} of the agent ${t} the one its chats talk to (a draft not published is dropped)`);let a=await h(e,s,"POST",`/ai/agents/${k(t)}/rollback`,{version:n});e.out.result(a,()=>e.out.line(`${t}'s chats talk to version ${a.published_version} again; its draft is that version.`))}},GP={name:"ai:agents:versions",summary:"the versions an agent the environment keeps was published as, newest first, and the one its chats talk to",usage:"<name> [--limit <n>] [--cursor <cursor>]",args:{min:1,max:1},flags:{limit:{type:"string",value:"n",description:"at most this many (1\u2013100, default 20)"},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}},scope:"environment",async run(e){let t=dn(e,"an agent"),n=jt(e.flags.limit,"limit"),s=await f(e),a=await h(e,s,"GET",`/ai/agents/${k(t)}/versions${un({limit:n,cursor:e.flags.cursor})}`);e.out.result(a,()=>{a.versions.length===0&&e.out.line(`${t} has no version yet: palbase ai:agents:publish ${t}`),e.out.table(a.versions,[{header:"version",value:r=>`${r.version}${r.version===a.published_version?" (published)":""}`},{header:"published",value:r=>r.published_at},{header:"by",value:r=>r.published_by},{header:"model",value:r=>r.definition.model??"-"},{header:"description",value:r=>r.definition.description??""}]),a.next_cursor&&e.out.line(`More: palbase ai:agents:versions ${t} --cursor ${a.next_cursor}`)})}},UP={name:"ai:agents:generate",summary:"write an agent from a description \u2014 instructions, a model, the environment's tools that fit \u2014 and save it as a draft with --save",usage:'"<description>" [--name <name>] [--save] [--publish]',details:["Nothing is kept unless --save (or --publish) says so: the definition is printed, for ai:agents:create --from.","It is written as you \u2014 your sign-in, or PALBASE_TOKEN \u2014 20 an hour; the platform pays for it."],args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"the name it is to have (left out: one is suggested)"},save:{type:"boolean",description:"save it as the draft of an agent of that name"},publish:{type:"boolean",description:"save it and publish it as the next version"}},scope:"environment",async run(e){let t=e.args[0].trim();if(t===""||t.length>4e3)throw m("the description says the agent in words: 1 to 4000 characters");let n=e.flags.name;if(n!==void 0&&!Wo.test(n))throw m(`${JSON.stringify(n)} is not an agent's name: lowercase letters, digits, _ and -, a letter first, at most 64`);let s=await f(e),a=await h(e,s,"POST","/ai/agent-drafts",{description:t,...n!==void 0?{name:n}:{}}),r=e.flags.publish===!0;if(e.flags.save!==!0&&!r){e.out.result(a,()=>{ms(e,`${a.name}:`,a.definition),e.out.line(),e.out.line(`Nothing was kept. Save it: palbase ai:agents:generate "\u2026" --save, or palbase ai:agents:create ${a.name} --from <the definition, as ai:agents:generate --json prints it>`)});return}await v(e,s,`save the draft of the agent ${a.name}${r?" and publish it":""}`);let i=await im(e,s,a.name,a.definition,void 0),o=r?await Vo(e,s,a.name,i.revision):i;e.out.result({...a,agent:o},()=>{ms(e,`${a.name}:`,a.definition),e.out.line(),e.out.line(r?`Saved and published ${a.name} as version ${o.published_version}.`:`Saved the draft of ${a.name} (revision ${i.revision}): palbase ai:agents:publish ${a.name} makes its chats talk to it.`)})}},zP={name:"ai:agents:remove",summary:"forget an agent the environment keeps, its versions with it: its chats stay, and are refused a message until one of its name is there again",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=dn(e,"an agent"),n=await f(e);await v(e,n,`forget the agent ${t} and its versions`),await h(e,n,"DELETE",`/ai/agents/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`Forgot ${t} and its versions.`))}},BP={name:"ai:tools",summary:"the tools an agent may be given: the backend's (its build's contract), the environment's endpoints, and the platform's",scope:"environment",async run(e){let t=await f(e),{tools:n}=await h(e,t,"GET","/ai/tools");e.out.result({tools:n},()=>{e.out.table(n,[{header:"tool",value:s=>`${s.source}:${s.name}`},{header:"machine",value:s=>s.machine??"-"},{header:"approval",value:s=>s.approval?"yes":"no"},{header:"description",value:s=>s.description}]),e.out.line("An agent is given one as --tool source:name (ai:agents:create).")})}},FP={name:"ai:chats:list",summary:"the environment's chats, newest first \u2014 every one, or a member's",usage:"[--member <user id>] [--limit <n>] [--cursor <cursor>]",flags:{member:{type:"string",value:"user id",description:"only the chats this user is in"},limit:{type:"string",value:"n",description:"at most this many (1\u2013100, default 100)"},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}},scope:"environment",async run(e){let t=jt(e.flags.limit,"limit"),n=await f(e),s=await h(e,n,"GET",`/ai/chats${un({member:e.flags.member,limit:t,cursor:e.flags.cursor})}`);e.out.result(s,()=>{s.chats.length===0&&e.out.line("No chat."),e.out.table(s.chats,[{header:"id",value:a=>a.id},{header:"agent",value:a=>`${a.agent}${a.agent_version?`@${a.agent_version}`:""}`},{header:"title",value:a=>a.title},{header:"members",value:a=>String(a.members.length)},{header:"started",value:a=>a.created_at}]),s.next_cursor&&e.out.line(`More: palbase ai:chats:list --cursor ${s.next_cursor}`)})}},JP=/^chat_[0-9a-f]{32}$/;function om(e){let t=e.args[0];if(!JP.test(t))throw m(`${JSON.stringify(t)} is not a chat's id (chat_ and 32 hex digits)`,"palbase ai:chats:list");return t}var WP={name:"ai:chats:show",summary:"one chat: its agent and members, and what they wrote, newest last \u2014 with what each turn cost (--cost)",usage:"<chat id> [--limit <n>] [--cost]",args:{min:1,max:1},flags:{limit:{type:"string",value:"n",description:"its last this many messages (1\u2013100, default 50)"},cost:{type:"boolean",description:"what each turn a message started cost, its subagents counted"}},scope:"environment",async run(e){let t=om(e),n=jt(e.flags.limit,"limit")??50,s=await f(e),a=await h(e,s,"GET",`/ai/chats/${k(t)}`),r=await h(e,s,"GET",`/ai/chats/${k(t)}/messages${un({limit:n})}`),i=[...r.messages].reverse(),o=await VP(e,s,t,new Set(i.flatMap(p=>p.response_id?[p.response_id]:[]))),l=new Map;i.forEach((p,u)=>p.response_id&&l.set(p.response_id,u));let d=new Map;if(e.flags.cost===!0)for(let p of new Set(i.flatMap(u=>u.response_id?[u.response_id]:[])))d.set(p,await h(e,s,"GET",`/ai/chats/${k(t)}/turns/${k(p)}/cost`));e.out.result({chat:a,messages:i,answers:Object.fromEntries(o),...e.flags.cost===!0?{costs:Object.fromEntries(d)}:{}},()=>{e.out.line(`${a.id} ${a.title||"(untitled)"}`),e.out.line(`agent ${a.agent}${a.agent_version?` (stays on version ${a.agent_version})`:""}; started ${a.created_at}; ${a.history_bytes} bytes of history`),a.members.length>0&&e.out.line(`members: ${a.members.map(p=>`${p.name||p.user_id}${p.owner?" (owner)":""}`).join(", ")}`),e.out.line(),i.length===0&&e.out.line("Nothing written yet."),i.forEach((p,u)=>{if(e.out.line(`${p.created_at} ${p.author===null?"server":p.author_name||p.author}: ${p.text}${p.status==="failed"?` [failed: ${p.error??""}]`:""}`),!p.response_id||l.get(p.response_id)!==u)return;let g=o.get(p.response_id);g&&e.out.line(` ${a.agent}: ${g.replace(/\s*\n\s*/g," ")}`);let b=d.get(p.response_id);b&&e.out.line(` turn ${b.response_id}: ${zr(b.usd)}${b.unpriced?" at least":""}, ${b.input_tokens} in / ${b.output_tokens} out${b.final?"":" (still settling)"}`)}),r.next_cursor&&e.out.line(`(older messages are not shown: --limit ${Math.min(n*2,100)})`)})}};async function VP(e,t,n,s){let a=[],r;for(let o=0;o<20&&s.size>0;o++){let l=await h(e,t,"GET",`/ai/chats/${k(n)}/parts${un({limit:1e3,cursor:r})}`);if(a.unshift(l.parts),!l.next_cursor)break;r=l.next_cursor}let i=new Map;for(let o of a.flat()){let l=o.data.response_id,d=o.data.offset;if(typeof l!="string"||!s.has(l)||typeof d!="number")continue;let p=(i.get(l)??Buffer.alloc(0)).subarray(0,d);i.set(l,o.type==="text"&&typeof o.data.text=="string"?Buffer.concat([p,Buffer.from(o.data.text)]):o.type==="reset"?p:i.get(l)??p)}return new Map([...i].map(([o,l])=>[o,l.toString("utf8").trim()]))}var HP={name:"ai:chats:remove",summary:"delete a chat: a turn still running is stopped, and its history no longer counts",usage:"<chat id>",args:{min:1,max:1},scope:"environment",async run(e){let t=om(e),n=await f(e);await v(e,n,`delete the chat ${t}`),await h(e,n,"DELETE",`/ai/chats/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`Deleted the chat ${t}.`))}},YP={name:"ai:endpoints:list",summary:"the environment's HTTPS tools and MCP servers an agent may be given by name (their signing secrets are never said again)",scope:"environment",async run(e){let t=await f(e),{endpoints:n}=await h(e,t,"GET","/ai/endpoints"),s=n.map(({signing_secret:a,...r})=>r);e.out.result({endpoints:s},()=>{s.length===0&&e.out.line("No endpoint."),e.out.table(s,[{header:"name",value:a=>a.name},{header:"kind",value:a=>a.kind},{header:"url",value:a=>a.url},{header:"approval",value:a=>a.approval?"yes":"no"},{header:"description",value:a=>a.description}])})}},XP={name:"ai:endpoints:remove",summary:"forget an HTTPS tool or an MCP server: an agent that names it is given it no more",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=dn(e,"an endpoint"),n=await f(e);await v(e,n,`forget the endpoint ${t}`),await h(e,n,"DELETE",`/ai/endpoints/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`Forgot the endpoint ${t}.`))}},ZP={name:"ai:repos:list",summary:"the repositories a chat's workspace works in, by name, and the machine that gives their tokens",usage:"[--limit <n>] [--cursor <cursor>]",flags:{limit:{type:"string",value:"n",description:"at most this many (1\u20131000, default 100)"},cursor:{type:"string",value:"cursor",description:"the next page, as the last one said it"}},scope:"environment",async run(e){let t=jt(e.flags.limit,"limit"),n=await f(e),s=await h(e,n,"GET",`/ai/repos${un({limit:t,cursor:e.flags.cursor})}`);e.out.result(s,()=>{s.repos.length===0&&e.out.line("No repository."),e.out.table(s.repos,[{header:"name",value:a=>a.name},{header:"repository",value:a=>a.repository},{header:"branch",value:a=>a.default_branch},{header:"tokens from",value:a=>a.backend}]),s.next_cursor&&e.out.line(`More: palbase ai:repos:list --cursor ${s.next_cursor}`)})}},QP={name:"ai:repos:remove",summary:"forget a repository: a chat's workspace checks it out no more",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=dn(e,"a repository"),n=await f(e);await v(e,n,`forget the repository ${t}`),await h(e,n,"DELETE",`/ai/repos/${k(t)}`),e.out.result({removed:t},()=>e.out.line(`Forgot the repository ${t}.`))}},lm=[_P,$P,kP,PP,AP,RP,EP,CP,OP,xP,NP,qP,KP,GP,UP,zP,BP,FP,WP,HP,YP,XP,ZP,QP];var eA={name:"docs:search",summary:"search Palbase's documentation by meaning: the sections that answer it, each with its address and its text",usage:'"<what you need>" [--limit <n>]',details:["Ask in English, the docs' language: a question finds the section it means by its meaning and by its words \u2014 say what you need, not the words of a heading.","Each result is a section of a page: its title, its address with the section's anchor, and its text (the first 4000 characters of a longer one \u2014 the address has the rest).","Anyone may search, signed in or not: 10 searches in ten seconds, 30 a minute, 300 an hour. A sign-in or PALBASE_TOKEN is sent when there is one and is counted as itself; with none, your address is. When many search at once, a search waits its turn for a few seconds, or is told to search again in a moment."],args:{min:1,max:1},flags:{limit:{type:"string",value:"n",description:"how many sections (1 to 10, default 5)"}},scope:"none",async run(e){let t=e.args[0].trim();if([...t].length<2||[...t].length>500)throw m("say what you need from the docs: 2 to 500 characters");let n=e.flags.limit,s=n===void 0?5:Number(n);if(!Number.isInteger(s)||s<1||s>10)throw m("--limit is how many sections: 1 to 10");let{results:a}=await e.studio.open(`/docs/search?q=${encodeURIComponent(t)}&limit=${s}`);e.out.result(a,()=>{if(a.length===0){e.out.line('No section of the docs answers it: say it another way, or ask Palbase: palbase support:ask "<question>"');return}a.forEach((r,i)=>{i>0&&e.out.line(),e.out.line(`${i+1}. ${r.title===r.heading?r.title:`${r.title} \u203A ${r.heading}`}`),e.out.line(` ${r.url}`),e.out.line(),e.out.line(r.text)})})}},pm=[eA];var dm=3e4,Ho=3,tA=80;async function nA(e,t){let n=[],s;try{s=(await h(e,t,"GET","/machines?limit=100")).machines}catch{return"machines: not readable with this sign-in or token"}if(s.length===0)return"machines: none";for(let r of s.slice(0,Ho)){n.push(`machine ${r.name}: ${r.state}, size ${r.size}, version ${r.running_version===r.version?r.version:`${r.running_version} \u2192 ${r.version}`}`);let i;try{i=(await h(e,t,"GET",`/machines/${D("a machine's name",r.name)}/builds?limit=3`)).builds}catch{n.push(" builds: not readable with this sign-in or token");continue}for(let l of i)n.push(` build ${l.id} ${l.commit.slice(0,7)} ${l.state}${l.running?" (serving)":""}${l.reason?` \u2014 ${l.reason}`:""} ${l.created_at} "${l.message.split(`
|
|
643
643
|
`)[0].slice(0,80)}"`);let o=i.find(l=>l.state==="failed");if(o)try{let l=await h(e,t,"GET",`/machines/${D("a machine's name",r.name)}/builds/${D("a build's id",o.id)}/log`),d=typeof l=="string"?l:JSON.stringify(l);n.push(` the end of build ${o.id}'s log:`,...d.trimEnd().split(`
|
|
644
644
|
`).slice(-tA).map(p=>` ${p}`))}catch{n.push(` build ${o.id}'s log: not readable with this sign-in or token`)}}s.length>Ho&&n.push(`(${s.length-Ho} more machines not shown)`);let a=n.join(`
|