palbase 0.45.0 → 0.45.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/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/{VRDOXAEV.js → 46RDJQ7Z.js} +2 -2
- package/dist/chunks/{NWREL5P6.js → 4YWROGXE.js} +2 -2
- package/dist/chunks/{SKIQT56T.js → 5QUX45OF.js} +1 -1
- package/dist/chunks/{PLWYBIC2.js → A2DBBW6I.js} +1 -1
- package/dist/chunks/{MLKWX35V.js → D2O7FP4Z.js} +2 -2
- package/dist/chunks/{JNDALWBO.js → DLQW3KGQ.js} +2 -2
- package/dist/chunks/{VTTAOVYK.js → F6IZFYXE.js} +2 -2
- package/dist/chunks/{WC2OBMWI.js → F6VBM3I5.js} +2 -2
- package/dist/chunks/{I3QUVQSV.js → FYZDRZ4J.js} +2 -2
- package/dist/chunks/{L7EECRLE.js → GLHJ3MIB.js} +1 -1
- package/dist/chunks/{SJCCOM55.js → IL6DK7HN.js} +2 -2
- package/dist/chunks/{YRXIPG5E.js → J6F6MSZM.js} +2 -2
- package/dist/chunks/{DPF3B362.js → N35KDL6X.js} +1 -1
- package/dist/chunks/{YDAGPKJZ.js → P4YE64Q3.js} +2 -2
- package/dist/chunks/{5MQR5IMS.js → QEE3UYFB.js} +1 -1
- package/dist/chunks/{IGRKIX4X.js → QSGYQNNM.js} +2 -2
- package/dist/chunks/{OHC2I2MW.js → T5NWLVNS.js} +1 -1
- package/dist/chunks/{242WNIKX.js → TCJ4HHXC.js} +2 -2
- package/dist/chunks/{ZAKNBO7Q.js → TSGQB4OT.js} +2 -2
- package/dist/chunks/{AHQOIIM4.js → WN2QJN5Z.js} +1 -1
- package/dist/chunks/{ZGVX7WRI.js → XP3VGN24.js} +2 -2
- package/dist/chunks/{RJOAR4UI.js → Z3MV6757.js} +2 -2
- package/dist/chunks/{R3F3FRU5.js → Z4OXYSYQ.js} +2 -2
- package/dist/cli.js +3 -3
- 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 -2
- package/types/internal/src/modules/machines.d.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! palbase 0.45.
|
|
2
|
-
import{b as M,c as N,d as T}from"./NWREL5P6.js";import{a as z,f as Q,g as Y,j as Z,k as X,l as ee,o as G,p as te,q as ne}from"./SKIQT56T.js";import{d as A}from"./SJCCOM55.js";import{a as b,b as V,c as _,e as j}from"./IGRKIX4X.js";var l=class extends Error{kind;code;status;requestId;fields;retryAfter;data;constructor(e,n){super(n.message,n.cause===void 0?void 0:{cause:n.cause}),this.name="BackendError",this.kind=e,this.code=n.code,this.status=n.status??0,this.requestId=n.requestId,this.fields=n.fields,this.retryAfter=n.retryAfter,this.data=n.data}};function oe(t){return t instanceof l||typeof t=="object"&&t!==null&&t.name==="BackendError"&&typeof t.kind=="string"}function q(t){let e=t.body,n={code:t.code,message:t.message,status:t.status,requestId:t.requestId??qe(e,"request_id"),data:E(e,"data"),cause:t};if(t.code==="network_error")return new l("network",n);if(t.status===401&&t.code==="insufficient_user_authentication")return new l("stepUpRequired",n);if(t.status===401&&t.code==="app_attest_required")return new l("attestationRequired",n);if(t.status===401)return new l("unauthorized",n);if(t.status===429)return new l("rateLimited",{...n,retryAfter:Ne(e)??t.retryAfter});let s=Me(e);return t.status===400&&s?new l("validation",{...n,fields:s}):new l("server",n)}function R(t){return t instanceof V||t instanceof Error&&t.name==="PalbaseError"}function se(t){return Array.isArray(t)&&t.length>0&&t.every(e=>typeof e=="object"&&e!==null&&typeof e.field=="string"&&typeof e.message=="string")}function Me(t){let e=E(E(t,"data"),"fields");if(se(e))return e;let n=E(t,"fields");return se(n)?n:void 0}function Ne(t){return re(t,"retry_after")??re(E(t,"data"),"retryAfter")}function E(t,e){if(typeof t=="object"&&t!==null)return t[e]}function re(t,e){let n=E(t,e);return typeof n=="number"?n:void 0}function qe(t,e){let n=E(t,e);return typeof n=="string"?n:void 0}var De=/^[A-Za-z_$][A-Za-z0-9_$]*$/,Ke=Object.freeze(["adoptSession","analytics","appContext","appVersion","auth","callOnSignOut","cancelCallOnSignOut","configuration","consent","currentSession","flags","installation","module","moduleDirectory","notify","onSessionChange","onSignOut","preferences","realtime","scope","send","socket","storage","storedUserId","telemetry","transport","version","palbe","data","stores","purchases","messaging","notifications","perf","debug","equals","hashCode","toString","self","Type","init","deinit","subscript","then","constructor","__proto__"]),Be=new Set(Ke),Le=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function P(t){return t==="then"||t==="toJSON"||Le.has(t)}function ae(t){return t==="call"||t==="join"||P(t)}function ie(t){return typeof t=="string"&&De.test(t)&&!Be.has(t)&&!P(t)}function de(t){return typeof t=="string"&&/^[a-z][a-z0-9-]{0,62}$/.test(t)}function le(t){j(t)}function ce(t){return j(t).origin}var I=class{me;reader;messages=new Map;responses=new Map;order=[];turn=null;goal=null;goalWords=new Set;voiceWords=new Set;voice=null;history=new Map;forkedFrom=null;calls=new Map;taken=[];older=[];oldestLive;olderCursor;olderLeft=!0;loading;chatId;listeners=new Set;enders=new Set;snapshot;told=!1;ended=!1;constructor(e=()=>{},n,s){this.me=e,this.reader=n,this.chatId=s}get hasOlder(){return this.olderLeft}loadOlder(e={}){return this.loading??=this.readOlder(e).finally(()=>this.loading=void 0),this.loading}async readOlder(e){if(!this.olderLeft)return!1;let s={...this.olderCursor!==void 0?{cursor:this.olderCursor}:this.oldestLive!==void 0?{before:this.oldestLive}:{},...e.limit!==void 0?{limit:e.limit}:{},...e.signal?{signal:e.signal}:{}},r;if(e.load)r=await e.load(s);else{let a=this.idFor("loadOlder",e.chatId);if(!this.reader)throw new TypeError("loadOlder: this chat reads its past through the app's way: give load");r=await this.reader.parts(a,s)}if(this.ended)return!1;let o=(r?.parts??[]).map(a=>M(a)).filter(a=>a!==void 0);return this.older=[...o,...this.older],this.olderCursor=r?.nextCursor,this.olderLeft=typeof r?.nextCursor=="string"&&r.nextCursor!=="",this.refold(),this.olderLeft}idFor(e,n){let s=n??this.chatId;if(typeof s!="string"||s==="")throw new TypeError(`${e}: the chat's id is not known yet (no chatId in the join, no message heard): give { chatId }`);return s}refold(){this.messages.clear(),this.responses.clear(),this.order.length=0,this.turn=null,this.goal=null,this.goalWords.clear(),this.voiceWords.clear(),this.voice=null,this.history.clear(),this.forkedFrom=null,this.calls.clear();for(let e of this.older)this.fold(e);for(let e of this.taken)this.fold(e);this.changed()}get state(){return this.snapshot??=this.build()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}async*[Symbol.asyncIterator](){let e,n,s=this.subscribe(()=>e?.()),r=()=>e?.();this.enders.add(r);try{for(;;){let o=this.state;if(o!==n){n=o,yield o;continue}if(this.ended)return;await new Promise(a=>e=a)}}finally{s(),this.enders.delete(r)}}end(){if(!this.ended){this.ended=!0;for(let e of[...this.enders])e()}}takePayload(e){let n=M(e);n&&this.take(n)}take(e){if(this.ended)return;this.taken.push(e);let n=e.seq;typeof n=="number"&&n>0&&(this.oldestLive===void 0||n<this.oldestLive)&&(this.oldestLive=n),this.fold(e)&&this.changed()}fold(e){switch(e.type){case"message":return this.chatId===void 0&&typeof e.message.chat_id=="string"&&e.message.chat_id!==""&&(this.chatId=e.message.chat_id),this.heard(e.message),!1;case"turn":{let n=this.response(e.response_id);n.status=e.status,n.waiting=e.waiting,n.error=e.error,n.end=typeof e.end=="string"?e.end:null,e.user_id!==void 0&&(n.startedBy=e.user_id),e.goal&&(n.goal={goalId:e.goal.goal_id,role:e.goal.role,seq:e.goal.seq}),this.turn=e;break}case"thinking":{let n=this.response(e.response_id);if(e.seq>0){if(n.thought.has(e.seq))return!1;n.thought.add(e.seq)}if(e.unavailable){n.marks.set("thinking:unavailable",{type:"thinking",text_offset:N(n.text),thinking:{stepId:null,runId:e.run_id,text:"",unavailable:!0,model:e.model??null,reason:e.reason??null}});break}let s=`thinking:${e.step_id??`seq:${e.seq}`}`,r=n.marks.get(s);r?r.thinking={...r.thinking,text:r.thinking.text+e.text}:n.marks.set(s,{type:"thinking",text_offset:N(n.text),thinking:{stepId:e.step_id??null,runId:e.run_id,text:e.text,unavailable:!1,model:null,reason:null}});break}case"goal":if(e.seq>0){if(this.goalWords.has(e.seq))return!1;this.goalWords.add(e.seq)}this.goalSaid(e),this.order.push({type:"goal",event:e});break;case"voice":{let n=`${e.voice_id}\0${e.status}`;if(this.voiceWords.has(n))return!1;this.voiceWords.add(n),(e.status==="closed"?this.voice===null||this.voice.voice_id===e.voice_id:!this.voiceWords.has(`${e.voice_id}\0closed`))&&(this.voice=e),this.order.push({type:"voice",event:e});break}case"transcript":return this.said(e),!1;case"history":e.forked_from&&(this.forkedFrom={chatId:e.forked_from.chat_id,beforeMessage:e.forked_from.before_message}),this.history.set(e.part,e.items);break;case"text":{let n=this.response(e.response_id);n.text=n.text.slice(0,T(n.text,e.offset))+e.text;break}case"reset":{let n=this.response(e.response_id);n.text=n.text.slice(0,T(n.text,e.offset));break}case"tool":{this.response(typeof e.agent_response_id=="string"&&e.agent_response_id!==""?e.agent_response_id:e.response_id).marks.set(`tool:${e.call_id}`,e),this.calls.set(e.call_id,e);break}case"agent":{if(this.response(e.parent_response_id).marks.set(e.run_id?`agent:${e.run_id}`:`agent:seq:${e.seq}`,e),!e.response_id)break;let n=this.response(e.response_id);if(n.runId=e.run_id,n.agent=e.name,n.parentId===null){n.parentId=e.parent_response_id;let s=this.order.findIndex(r=>r.type==="response"&&r.key===e.response_id);s>=0&&this.order.splice(s,1)}break}}return!0}goalSaid(e){if(e.kind==="set"&&e.goal){this.goal={id:e.goal_id,objective:e.goal.objective,rubric:e.goal.rubric,status:e.goal.status,stopCode:e.goal.stop_code,stopReason:e.goal.stop_reason,phase:e.goal.phase,maxTurns:e.goal.max_turns??null,maxCostUsd:e.goal.max_cost_usd??null,events:[e]};return}let n=this.goal;if(!n||n.id!==e.goal_id)return;let s={...n,events:[...n.events,e]};e.kind==="updated"&&e.goal?Object.assign(s,{objective:e.goal.objective,rubric:e.goal.rubric,status:e.goal.status,stopCode:e.goal.stop_code,stopReason:e.goal.stop_reason,phase:e.goal.phase,maxTurns:e.goal.max_turns??null,maxCostUsd:e.goal.max_cost_usd??null}):e.kind==="status_changed"&&typeof e.to=="string"?Object.assign(s,{status:e.to,stopCode:e.code??"",stopReason:e.reason??""}):e.kind==="cleared"&&Object.assign(s,{status:"cleared",stopReason:e.reason??""}),this.goal=s}heard(e){let n=Fe(e),s=this.messages.get(n),r=e.status==="delivered"||e.status==="failed"?e.status:"pending";this.messages.set(n,{clientId:e.client_id,id:e.id??s?.id??null,text:e.text,author:e.author??null,authorName:e.author_name??"",delivery:e.delivery??"next",status:s?.status==="delivered"?"delivered":r,error:e.error??null,responseId:e.response_id??s?.responseId??null,createdAt:e.created_at??s?.createdAt??"",mine:!1,metadata:e.metadata??s?.metadata??{},history:!1,truncated:!1,kind:e.kind??s?.kind??"text",voice:e.voice?{id:e.voice.id,role:e.voice.role}:s?.voice??null,attachments:e.attachments?ue(e.attachments):s?.attachments??[]}),s||this.place(e,n),this.changed()}said(e){let n=`voice\0${e.id}`,s=this.messages.get(n);this.messages.set(n,{clientId:"",id:e.id,text:e.text,author:e.user_id,authorName:e.author_name,delivery:"next",status:"delivered",error:null,responseId:null,createdAt:e.created_at,mine:!1,metadata:{},history:!1,truncated:!1,kind:"voice",voice:{id:e.voice_id,role:e.role},attachments:[]}),s||this.order.push({type:"message",key:n}),this.changed()}place(e,n){let s=e.response_id??null,o=s!==null&&![...this.messages.entries()].some(([a,i])=>a!==n&&i.responseId===s)?this.order.findIndex(a=>a.type==="response"&&a.key===s):-1;o>=0?this.order.splice(o,0,{type:"message",key:n}):this.order.push({type:"message",key:n})}response(e){let n=this.responses.get(e);return n||(n={id:e,status:null,waiting:null,end:null,error:null,text:"",marks:new Map,thought:new Set,runId:null,agent:null,parentId:null,startedBy:void 0,goal:null},this.responses.set(e,n),this.order.push({type:"response",key:e})),n}changed(){this.snapshot=void 0,!this.told&&(this.told=!0,queueMicrotask(()=>{this.told=!1;let e=this.state;for(let n of[...this.listeners])try{n(e)}catch{}}))}build(){let e=this.me(),n=d=>e!==void 0&&d.startedBy===e,s=new Map;for(let d of this.responses.values()){let u=d.parentId!==null?this.responses.get(d.parentId)??d:d;s.set(d.id,We(d,u.startedBy??null,n(u),d.goal??u.goal))}let r=new Map([...this.messages].map(([d,u])=>[d,e!==void 0&&u.author===e?{...u,mine:!0}:u])),o=[...this.history.entries()].sort(([d],[u])=>d-u).flatMap(([d,u])=>u.map((f,m)=>He(f,`${d}:${m}`,e))),a=[...o];for(let d of this.order)d.type==="goal"?a.push({type:"goal",goal:d.event}):d.type==="voice"?a.push({type:"voice",voice:d.event}):d.type==="message"?a.push({type:"message",message:r.get(d.key)}):this.responses.get(d.key).parentId===null&&a.push({type:"response",response:s.get(d.key)});let i=o.flatMap(d=>d.type==="message"?[d.message]:[]),c=o.flatMap(d=>d.type==="response"?[d.response]:[]),p=[...this.calls.values()].filter(d=>s.get(d.response_id)?.mine===!0);return{items:a,messages:[...i,...r.values()],responses:[...c,...s.values()],turn:this.turn,waiting:{approvals:p.filter(d=>d.status==="awaiting_approval"),outputs:p.filter(d=>d.status==="awaiting_output")},goal:this.goal,forkedFrom:this.forkedFrom,voice:this.voice?Je(this.voice,e):null}}};function Je(t,e){return{id:t.voice_id,userId:t.user_id,mine:e!==void 0&&t.user_id===e,delegation:t.delegation,status:t.status,seconds:typeof t.seconds=="number"?t.seconds:null,closeReason:t.close_reason??""}}function ue(t){return t.map((e,n)=>({kind:e.kind,name:e.name,contentType:e.content_type,size:e.size,sha256:e.sha256,position:n,bucket:e.bucket,path:e.path,workspacePath:e.workspace_path??null}))}function Fe(t){return`${t.author??""}\0${t.client_id}`}function We(t,e,n,s){let r=[...t.marks.values()].map((c,p)=>({m:c,i:p,at:T(t.text,Math.max(0,Number(c.text_offset)||0))}));r.sort((c,p)=>c.at-p.at||c.i-p.i);let o=[],a=[],i=0;for(let{m:c,at:p}of r)p>i&&o.push({type:"text",text:t.text.slice(i,p)}),i=Math.max(i,p),c.type==="thinking"?(o.push({type:"thinking",thinking:c.thinking}),a.push(c.thinking)):o.push(c.type==="tool"?{type:"tool",tool:c}:{type:"agent",agent:c});return i<t.text.length&&o.push({type:"text",text:t.text.slice(i)}),{id:t.id,status:t.status,waiting:t.waiting,end:t.end,error:t.error,text:t.text,parts:o,runId:t.runId,agent:t.agent,parentId:t.parentId,startedBy:e,mine:n,thinking:a,goal:s,history:!1,truncated:!1}}function He(t,e,n){let s=t.text_truncated===!0;if(t.role==="user"){let r=t.author??null;return{type:"message",message:{clientId:t.client_id??"",id:t.message_id??null,text:t.text,author:r,authorName:t.author_name??"",delivery:"next",status:"delivered",error:null,responseId:null,createdAt:t.created_at??"",mine:n!==void 0&&r===n,metadata:t.metadata??{},history:!0,truncated:s,kind:"text",voice:null,attachments:ue(t.attachments??[])}}}return{type:"response",response:{id:t.response_id??`history:${e}`,status:"completed",waiting:null,end:"completed",error:null,text:t.text,parts:t.text===""?[]:[{type:"text",text:t.text}],runId:null,agent:null,parentId:null,startedBy:null,mine:!1,thinking:[],goal:null,history:!0,truncated:s}}}var D=[["ApiKeyAuth","userJWT"]];function pe(t){return{async parts(e,n){let s={};n.limit!==void 0&&(s.limit=n.limit),n.cursor!==void 0?s.cursor=n.cursor:n.before!==void 0&&(s.before=n.before);let r=(await t.send({op:"ListChatParts",method:"GET",path:`/ai/chats/${_("ListChatParts","chat_id",e)}/parts`,auth:D,answers:{200:"json"},query:s,signal:n.signal})).data;return r.next_cursor===void 0?{parts:r.parts}:{parts:r.parts,nextCursor:r.next_cursor}},async toolCall(e,n,s){if(typeof n!="string"||n==="")throw new TypeError("chatToolCall: callId is a tool part's call_id");let r=(await t.send({op:"GetChatToolCall",method:"GET",path:`/ai/chats/${_("GetChatToolCall","chat_id",e)}/tool-calls/${_("GetChatToolCall","call_id",n)}`,auth:D,answers:{200:"json"},signal:s})).data;return{callId:r.call_id,name:r.name,status:r.status,runId:r.run_id,responseId:r.response_id,input:r.input,output:r.output,outputBytes:r.output_bytes,imageCount:r.image_count,isError:r.is_error}},async workspaceFile(e,n,s,r){if(typeof n!="string"||n==="")throw new TypeError("chatWorkspaceFile: path is the file as the workspace's tree names it, relative to its root");let o={path:n};return s!==void 0&&(o.manifest=s),(await t.send({op:"GetChatWorkspaceFile",method:"GET",path:`/ai/chats/${_("GetChatWorkspaceFile","chat_id",e)}/workspace/files`,auth:D,answers:{200:"raw"},query:o,signal:r})).data.blob()}}}var $=class extends Error{reason;constructor(e,n){super(e),this.name="GatewayError",this.reason=n}},he=new WeakMap;async function fe(t,e,n,s={},r=()=>{},o){let{pattern:a,events:i,messages:c,durable:p,chat:d}=Ue(e),u=Ve(a,n),f=he.get(t);f||he.set(t,f=new Map);let m=f,g=m.get(u)??{count:0};m.set(u,g),g.count++;let y=t.channel(u,p&&s.history!==!1?{after:0}:{});if(d&&!g.chat){let h=n.chatId,k=new I(r,o,typeof h=="string"&&h!==""?h:void 0);g.chat=k,g.unchat=y.on("broadcast",w=>{w.event==="chat"&&k.takePayload(w.payload)})}let x=()=>{--g.count>0||(m.delete(u),g.unchat?.(),g.chat?.end(),y.leave())};try{await y.join()}catch{let h=y.state==="refused"?ge(y.refusal,"unavailable"):"socket closed";throw x(),new $(`gateway ${JSON.stringify(u)} was not joined: ${h}`,h)}let C=!1,S=new Set,W=()=>{if(!C){C=!0,Ge();for(let h of[...S])h();x()}},Ge=y.onClosed(W),H=h=>{if(C)return()=>{};let k=y.on("broadcast",v=>{typeof v.event=="string"&&i.has(v.event)&&h(v.event,b(v.payload))}),w=()=>{S.delete(w),k()};return S.add(w),w},U={topic:u,get seq(){return y.logSeq},on(h,k){if(typeof h=="function")return H((w,v)=>h({event:w,payload:v}));if(!i.has(h))throw new TypeError(`gateway ${JSON.stringify(a)} declares no event ${JSON.stringify(String(h))}`);if(typeof k!="function")throw new TypeError(`gateway ${JSON.stringify(a)}: on(${JSON.stringify(h)}, \u2026) takes the function the event is handed to`);return H((w,v)=>{w===h&&k(v)})},off(h){S.has(h)&&h()},async send(h,k){if(!c.has(h))throw new TypeError(`gateway ${JSON.stringify(a)} declares no message ${JSON.stringify(String(h))}`);let w=C?{status:"error",response:{reason:"not joined"}}:await y.push("broadcast",{type:"broadcast",event:h,payload:k});if(w.status==="ok")return b(w.response.reply);let v=ge(w.response,"unavailable");throw new $(`gateway ${JSON.stringify(u)} did not take ${JSON.stringify(h)}: ${v}`,v)},leave:W};return d&&Object.defineProperty(U,"chat",{value:g.chat,enumerable:!0}),U}function Ue(t){let e=typeof t=="object"&&t!==null?t:{},n=s=>Array.isArray(s)&&s.every(r=>typeof r=="string");if(typeof e.pattern!="string"||e.pattern===""||!n(e.events)||!n(e.messages))throw new TypeError("join takes a gateway of the backend's generated module: its pattern, its events and its messages");return{pattern:e.pattern,events:new Set(e.events),messages:new Set(e.messages),durable:e.durable===!0,chat:e.chat===!0}}function Ve(t,e){let n=typeof e=="object"&&e!==null?e:{};return t.split(":").map(s=>{if(!(s.startsWith("{")&&s.endsWith("}")&&s.length>2))return s;let r=s.slice(1,-1),o=Object.hasOwn(n,r)?n[r]:void 0;if(typeof o!="string"||o==="")throw new TypeError(`gateway ${JSON.stringify(t)} needs a value for ${JSON.stringify(r)}`);if(o.includes(":"))throw new TypeError(`gateway ${JSON.stringify(t)}: the value of ${JSON.stringify(r)} is one segment of the channel's name and cannot hold ":"`);return o}).join(":")}function ge(t,e){let n=t?.reason;return typeof n=="string"&&n!==""?n:e}var ze=3600*1e3,O=new Map;function Qe(t,e,n=!1){let s=`${e.projectId}\0${e.signer}`,r=O.get(s);if(!n&&r&&Date.now()-r.at<ze)return r.list;let o=(async()=>{let a=te(),i;try{i=(await t.send({op:"GetTransitPublicKeys",method:"GET",path:"/secrets/transit/public",query:{nonce:a},auth:[["ApiKeyAuth"]],answers:{200:"json"}})).data}catch(c){throw new l("network",{code:"encryption_keys_unavailable",message:"the project's encryption keys could not be fetched: no encrypted field is sent without them",cause:c})}try{return await ne(i,e.signer,e.projectId,a)}catch(c){throw new l("decode",{code:"encryption_keys_unverified",message:"the project's list of encryption keys does not verify with the key this app was built with: nothing is sealed to it",cause:c})}})();return O.set(s,{list:o,at:Date.now()}),o.catch(()=>{O.get(s)?.list===o&&O.delete(s)}),o}async function ye(t,e,n,s,r,o){let i=(await Qe(t,e,o)).keys[0];return Y(i.kid,i.key,Q(e.projectId,n,s,r))}function K(t,e,n,s){return e==null||!s||s.length===0?Promise.resolve(e):G(e,n,s,(r,o)=>Z(t.key,r,o))}function me(t,e,n,s){return e===void 0||!n||n.length===0?Promise.resolve(e):G(e,"/response",n,async(r,o)=>{if(!X(o))throw new l("decode",{code:"field_not_encrypted",message:`${s}: ${r} came plain where the backend's contract says it is encrypted`});try{return await ee(t.key,r,o)}catch(a){throw new l("decode",{code:"field_unopenable",message:`${s}: ${r} does not open with this call's key`,cause:a})}})}function we(t){return t instanceof l&&(t.code==="encryption_key_retired"||t.code==="encryption_key_unknown")}var B=class{pending="";dataLines=[];eventName;consume(e){this.pending+=e.replace(/\r\n/g,`
|
|
1
|
+
/*! palbase 0.45.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{b as M,c as N,d as T}from"./4YWROGXE.js";import{a as z,f as Q,g as Y,j as Z,k as X,l as ee,o as G,p as te,q as ne}from"./5QUX45OF.js";import{d as A}from"./IL6DK7HN.js";import{a as b,b as V,c as _,e as j}from"./QSGYQNNM.js";var l=class extends Error{kind;code;status;requestId;fields;retryAfter;data;constructor(e,n){super(n.message,n.cause===void 0?void 0:{cause:n.cause}),this.name="BackendError",this.kind=e,this.code=n.code,this.status=n.status??0,this.requestId=n.requestId,this.fields=n.fields,this.retryAfter=n.retryAfter,this.data=n.data}};function oe(t){return t instanceof l||typeof t=="object"&&t!==null&&t.name==="BackendError"&&typeof t.kind=="string"}function q(t){let e=t.body,n={code:t.code,message:t.message,status:t.status,requestId:t.requestId??qe(e,"request_id"),data:E(e,"data"),cause:t};if(t.code==="network_error")return new l("network",n);if(t.status===401&&t.code==="insufficient_user_authentication")return new l("stepUpRequired",n);if(t.status===401&&t.code==="app_attest_required")return new l("attestationRequired",n);if(t.status===401)return new l("unauthorized",n);if(t.status===429)return new l("rateLimited",{...n,retryAfter:Ne(e)??t.retryAfter});let s=Me(e);return t.status===400&&s?new l("validation",{...n,fields:s}):new l("server",n)}function R(t){return t instanceof V||t instanceof Error&&t.name==="PalbaseError"}function se(t){return Array.isArray(t)&&t.length>0&&t.every(e=>typeof e=="object"&&e!==null&&typeof e.field=="string"&&typeof e.message=="string")}function Me(t){let e=E(E(t,"data"),"fields");if(se(e))return e;let n=E(t,"fields");return se(n)?n:void 0}function Ne(t){return re(t,"retry_after")??re(E(t,"data"),"retryAfter")}function E(t,e){if(typeof t=="object"&&t!==null)return t[e]}function re(t,e){let n=E(t,e);return typeof n=="number"?n:void 0}function qe(t,e){let n=E(t,e);return typeof n=="string"?n:void 0}var De=/^[A-Za-z_$][A-Za-z0-9_$]*$/,Ke=Object.freeze(["adoptSession","analytics","appContext","appVersion","auth","callOnSignOut","cancelCallOnSignOut","configuration","consent","currentSession","flags","installation","module","moduleDirectory","notify","onSessionChange","onSignOut","preferences","realtime","scope","send","socket","storage","storedUserId","telemetry","transport","version","palbe","data","stores","purchases","messaging","notifications","perf","debug","equals","hashCode","toString","self","Type","init","deinit","subscript","then","constructor","__proto__"]),Be=new Set(Ke),Le=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function P(t){return t==="then"||t==="toJSON"||Le.has(t)}function ae(t){return t==="call"||t==="join"||P(t)}function ie(t){return typeof t=="string"&&De.test(t)&&!Be.has(t)&&!P(t)}function de(t){return typeof t=="string"&&/^[a-z][a-z0-9-]{0,62}$/.test(t)}function le(t){j(t)}function ce(t){return j(t).origin}var I=class{me;reader;messages=new Map;responses=new Map;order=[];turn=null;goal=null;goalWords=new Set;voiceWords=new Set;voice=null;history=new Map;forkedFrom=null;calls=new Map;taken=[];older=[];oldestLive;olderCursor;olderLeft=!0;loading;chatId;listeners=new Set;enders=new Set;snapshot;told=!1;ended=!1;constructor(e=()=>{},n,s){this.me=e,this.reader=n,this.chatId=s}get hasOlder(){return this.olderLeft}loadOlder(e={}){return this.loading??=this.readOlder(e).finally(()=>this.loading=void 0),this.loading}async readOlder(e){if(!this.olderLeft)return!1;let s={...this.olderCursor!==void 0?{cursor:this.olderCursor}:this.oldestLive!==void 0?{before:this.oldestLive}:{},...e.limit!==void 0?{limit:e.limit}:{},...e.signal?{signal:e.signal}:{}},r;if(e.load)r=await e.load(s);else{let a=this.idFor("loadOlder",e.chatId);if(!this.reader)throw new TypeError("loadOlder: this chat reads its past through the app's way: give load");r=await this.reader.parts(a,s)}if(this.ended)return!1;let o=(r?.parts??[]).map(a=>M(a)).filter(a=>a!==void 0);return this.older=[...o,...this.older],this.olderCursor=r?.nextCursor,this.olderLeft=typeof r?.nextCursor=="string"&&r.nextCursor!=="",this.refold(),this.olderLeft}idFor(e,n){let s=n??this.chatId;if(typeof s!="string"||s==="")throw new TypeError(`${e}: the chat's id is not known yet (no chatId in the join, no message heard): give { chatId }`);return s}refold(){this.messages.clear(),this.responses.clear(),this.order.length=0,this.turn=null,this.goal=null,this.goalWords.clear(),this.voiceWords.clear(),this.voice=null,this.history.clear(),this.forkedFrom=null,this.calls.clear();for(let e of this.older)this.fold(e);for(let e of this.taken)this.fold(e);this.changed()}get state(){return this.snapshot??=this.build()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}async*[Symbol.asyncIterator](){let e,n,s=this.subscribe(()=>e?.()),r=()=>e?.();this.enders.add(r);try{for(;;){let o=this.state;if(o!==n){n=o,yield o;continue}if(this.ended)return;await new Promise(a=>e=a)}}finally{s(),this.enders.delete(r)}}end(){if(!this.ended){this.ended=!0;for(let e of[...this.enders])e()}}takePayload(e){let n=M(e);n&&this.take(n)}take(e){if(this.ended)return;this.taken.push(e);let n=e.seq;typeof n=="number"&&n>0&&(this.oldestLive===void 0||n<this.oldestLive)&&(this.oldestLive=n),this.fold(e)&&this.changed()}fold(e){switch(e.type){case"message":return this.chatId===void 0&&typeof e.message.chat_id=="string"&&e.message.chat_id!==""&&(this.chatId=e.message.chat_id),this.heard(e.message),!1;case"turn":{let n=this.response(e.response_id);n.status=e.status,n.waiting=e.waiting,n.error=e.error,n.end=typeof e.end=="string"?e.end:null,e.user_id!==void 0&&(n.startedBy=e.user_id),e.goal&&(n.goal={goalId:e.goal.goal_id,role:e.goal.role,seq:e.goal.seq}),this.turn=e;break}case"thinking":{let n=this.response(e.response_id);if(e.seq>0){if(n.thought.has(e.seq))return!1;n.thought.add(e.seq)}if(e.unavailable){n.marks.set("thinking:unavailable",{type:"thinking",text_offset:N(n.text),thinking:{stepId:null,runId:e.run_id,text:"",unavailable:!0,model:e.model??null,reason:e.reason??null}});break}let s=`thinking:${e.step_id??`seq:${e.seq}`}`,r=n.marks.get(s);r?r.thinking={...r.thinking,text:r.thinking.text+e.text}:n.marks.set(s,{type:"thinking",text_offset:N(n.text),thinking:{stepId:e.step_id??null,runId:e.run_id,text:e.text,unavailable:!1,model:null,reason:null}});break}case"goal":if(e.seq>0){if(this.goalWords.has(e.seq))return!1;this.goalWords.add(e.seq)}this.goalSaid(e),this.order.push({type:"goal",event:e});break;case"voice":{let n=`${e.voice_id}\0${e.status}`;if(this.voiceWords.has(n))return!1;this.voiceWords.add(n),(e.status==="closed"?this.voice===null||this.voice.voice_id===e.voice_id:!this.voiceWords.has(`${e.voice_id}\0closed`))&&(this.voice=e),this.order.push({type:"voice",event:e});break}case"transcript":return this.said(e),!1;case"history":e.forked_from&&(this.forkedFrom={chatId:e.forked_from.chat_id,beforeMessage:e.forked_from.before_message}),this.history.set(e.part,e.items);break;case"text":{let n=this.response(e.response_id);n.text=n.text.slice(0,T(n.text,e.offset))+e.text;break}case"reset":{let n=this.response(e.response_id);n.text=n.text.slice(0,T(n.text,e.offset));break}case"tool":{this.response(typeof e.agent_response_id=="string"&&e.agent_response_id!==""?e.agent_response_id:e.response_id).marks.set(`tool:${e.call_id}`,e),this.calls.set(e.call_id,e);break}case"agent":{if(this.response(e.parent_response_id).marks.set(e.run_id?`agent:${e.run_id}`:`agent:seq:${e.seq}`,e),!e.response_id)break;let n=this.response(e.response_id);if(n.runId=e.run_id,n.agent=e.name,n.parentId===null){n.parentId=e.parent_response_id;let s=this.order.findIndex(r=>r.type==="response"&&r.key===e.response_id);s>=0&&this.order.splice(s,1)}break}}return!0}goalSaid(e){if(e.kind==="set"&&e.goal){this.goal={id:e.goal_id,objective:e.goal.objective,rubric:e.goal.rubric,status:e.goal.status,stopCode:e.goal.stop_code,stopReason:e.goal.stop_reason,phase:e.goal.phase,maxTurns:e.goal.max_turns??null,maxCostUsd:e.goal.max_cost_usd??null,events:[e]};return}let n=this.goal;if(!n||n.id!==e.goal_id)return;let s={...n,events:[...n.events,e]};e.kind==="updated"&&e.goal?Object.assign(s,{objective:e.goal.objective,rubric:e.goal.rubric,status:e.goal.status,stopCode:e.goal.stop_code,stopReason:e.goal.stop_reason,phase:e.goal.phase,maxTurns:e.goal.max_turns??null,maxCostUsd:e.goal.max_cost_usd??null}):e.kind==="status_changed"&&typeof e.to=="string"?Object.assign(s,{status:e.to,stopCode:e.code??"",stopReason:e.reason??""}):e.kind==="cleared"&&Object.assign(s,{status:"cleared",stopReason:e.reason??""}),this.goal=s}heard(e){let n=Fe(e),s=this.messages.get(n),r=e.status==="delivered"||e.status==="failed"?e.status:"pending";this.messages.set(n,{clientId:e.client_id,id:e.id??s?.id??null,text:e.text,author:e.author??null,authorName:e.author_name??"",delivery:e.delivery??"next",status:s?.status==="delivered"?"delivered":r,error:e.error??null,responseId:e.response_id??s?.responseId??null,createdAt:e.created_at??s?.createdAt??"",mine:!1,metadata:e.metadata??s?.metadata??{},history:!1,truncated:!1,kind:e.kind??s?.kind??"text",voice:e.voice?{id:e.voice.id,role:e.voice.role}:s?.voice??null,attachments:e.attachments?ue(e.attachments):s?.attachments??[]}),s||this.place(e,n),this.changed()}said(e){let n=`voice\0${e.id}`,s=this.messages.get(n);this.messages.set(n,{clientId:"",id:e.id,text:e.text,author:e.user_id,authorName:e.author_name,delivery:"next",status:"delivered",error:null,responseId:null,createdAt:e.created_at,mine:!1,metadata:{},history:!1,truncated:!1,kind:"voice",voice:{id:e.voice_id,role:e.role},attachments:[]}),s||this.order.push({type:"message",key:n}),this.changed()}place(e,n){let s=e.response_id??null,o=s!==null&&![...this.messages.entries()].some(([a,i])=>a!==n&&i.responseId===s)?this.order.findIndex(a=>a.type==="response"&&a.key===s):-1;o>=0?this.order.splice(o,0,{type:"message",key:n}):this.order.push({type:"message",key:n})}response(e){let n=this.responses.get(e);return n||(n={id:e,status:null,waiting:null,end:null,error:null,text:"",marks:new Map,thought:new Set,runId:null,agent:null,parentId:null,startedBy:void 0,goal:null},this.responses.set(e,n),this.order.push({type:"response",key:e})),n}changed(){this.snapshot=void 0,!this.told&&(this.told=!0,queueMicrotask(()=>{this.told=!1;let e=this.state;for(let n of[...this.listeners])try{n(e)}catch{}}))}build(){let e=this.me(),n=d=>e!==void 0&&d.startedBy===e,s=new Map;for(let d of this.responses.values()){let u=d.parentId!==null?this.responses.get(d.parentId)??d:d;s.set(d.id,We(d,u.startedBy??null,n(u),d.goal??u.goal))}let r=new Map([...this.messages].map(([d,u])=>[d,e!==void 0&&u.author===e?{...u,mine:!0}:u])),o=[...this.history.entries()].sort(([d],[u])=>d-u).flatMap(([d,u])=>u.map((f,m)=>He(f,`${d}:${m}`,e))),a=[...o];for(let d of this.order)d.type==="goal"?a.push({type:"goal",goal:d.event}):d.type==="voice"?a.push({type:"voice",voice:d.event}):d.type==="message"?a.push({type:"message",message:r.get(d.key)}):this.responses.get(d.key).parentId===null&&a.push({type:"response",response:s.get(d.key)});let i=o.flatMap(d=>d.type==="message"?[d.message]:[]),c=o.flatMap(d=>d.type==="response"?[d.response]:[]),p=[...this.calls.values()].filter(d=>s.get(d.response_id)?.mine===!0);return{items:a,messages:[...i,...r.values()],responses:[...c,...s.values()],turn:this.turn,waiting:{approvals:p.filter(d=>d.status==="awaiting_approval"),outputs:p.filter(d=>d.status==="awaiting_output")},goal:this.goal,forkedFrom:this.forkedFrom,voice:this.voice?Je(this.voice,e):null}}};function Je(t,e){return{id:t.voice_id,userId:t.user_id,mine:e!==void 0&&t.user_id===e,delegation:t.delegation,status:t.status,seconds:typeof t.seconds=="number"?t.seconds:null,closeReason:t.close_reason??""}}function ue(t){return t.map((e,n)=>({kind:e.kind,name:e.name,contentType:e.content_type,size:e.size,sha256:e.sha256,position:n,bucket:e.bucket,path:e.path,workspacePath:e.workspace_path??null}))}function Fe(t){return`${t.author??""}\0${t.client_id}`}function We(t,e,n,s){let r=[...t.marks.values()].map((c,p)=>({m:c,i:p,at:T(t.text,Math.max(0,Number(c.text_offset)||0))}));r.sort((c,p)=>c.at-p.at||c.i-p.i);let o=[],a=[],i=0;for(let{m:c,at:p}of r)p>i&&o.push({type:"text",text:t.text.slice(i,p)}),i=Math.max(i,p),c.type==="thinking"?(o.push({type:"thinking",thinking:c.thinking}),a.push(c.thinking)):o.push(c.type==="tool"?{type:"tool",tool:c}:{type:"agent",agent:c});return i<t.text.length&&o.push({type:"text",text:t.text.slice(i)}),{id:t.id,status:t.status,waiting:t.waiting,end:t.end,error:t.error,text:t.text,parts:o,runId:t.runId,agent:t.agent,parentId:t.parentId,startedBy:e,mine:n,thinking:a,goal:s,history:!1,truncated:!1}}function He(t,e,n){let s=t.text_truncated===!0;if(t.role==="user"){let r=t.author??null;return{type:"message",message:{clientId:t.client_id??"",id:t.message_id??null,text:t.text,author:r,authorName:t.author_name??"",delivery:"next",status:"delivered",error:null,responseId:null,createdAt:t.created_at??"",mine:n!==void 0&&r===n,metadata:t.metadata??{},history:!0,truncated:s,kind:"text",voice:null,attachments:ue(t.attachments??[])}}}return{type:"response",response:{id:t.response_id??`history:${e}`,status:"completed",waiting:null,end:"completed",error:null,text:t.text,parts:t.text===""?[]:[{type:"text",text:t.text}],runId:null,agent:null,parentId:null,startedBy:null,mine:!1,thinking:[],goal:null,history:!0,truncated:s}}}var D=[["ApiKeyAuth","userJWT"]];function pe(t){return{async parts(e,n){let s={};n.limit!==void 0&&(s.limit=n.limit),n.cursor!==void 0?s.cursor=n.cursor:n.before!==void 0&&(s.before=n.before);let r=(await t.send({op:"ListChatParts",method:"GET",path:`/ai/chats/${_("ListChatParts","chat_id",e)}/parts`,auth:D,answers:{200:"json"},query:s,signal:n.signal})).data;return r.next_cursor===void 0?{parts:r.parts}:{parts:r.parts,nextCursor:r.next_cursor}},async toolCall(e,n,s){if(typeof n!="string"||n==="")throw new TypeError("chatToolCall: callId is a tool part's call_id");let r=(await t.send({op:"GetChatToolCall",method:"GET",path:`/ai/chats/${_("GetChatToolCall","chat_id",e)}/tool-calls/${_("GetChatToolCall","call_id",n)}`,auth:D,answers:{200:"json"},signal:s})).data;return{callId:r.call_id,name:r.name,status:r.status,runId:r.run_id,responseId:r.response_id,input:r.input,output:r.output,outputBytes:r.output_bytes,imageCount:r.image_count,isError:r.is_error}},async workspaceFile(e,n,s,r){if(typeof n!="string"||n==="")throw new TypeError("chatWorkspaceFile: path is the file as the workspace's tree names it, relative to its root");let o={path:n};return s!==void 0&&(o.manifest=s),(await t.send({op:"GetChatWorkspaceFile",method:"GET",path:`/ai/chats/${_("GetChatWorkspaceFile","chat_id",e)}/workspace/files`,auth:D,answers:{200:"raw"},query:o,signal:r})).data.blob()}}}var $=class extends Error{reason;constructor(e,n){super(e),this.name="GatewayError",this.reason=n}},he=new WeakMap;async function fe(t,e,n,s={},r=()=>{},o){let{pattern:a,events:i,messages:c,durable:p,chat:d}=Ue(e),u=Ve(a,n),f=he.get(t);f||he.set(t,f=new Map);let m=f,g=m.get(u)??{count:0};m.set(u,g),g.count++;let y=t.channel(u,p&&s.history!==!1?{after:0}:{});if(d&&!g.chat){let h=n.chatId,k=new I(r,o,typeof h=="string"&&h!==""?h:void 0);g.chat=k,g.unchat=y.on("broadcast",w=>{w.event==="chat"&&k.takePayload(w.payload)})}let x=()=>{--g.count>0||(m.delete(u),g.unchat?.(),g.chat?.end(),y.leave())};try{await y.join()}catch{let h=y.state==="refused"?ge(y.refusal,"unavailable"):"socket closed";throw x(),new $(`gateway ${JSON.stringify(u)} was not joined: ${h}`,h)}let C=!1,S=new Set,W=()=>{if(!C){C=!0,Ge();for(let h of[...S])h();x()}},Ge=y.onClosed(W),H=h=>{if(C)return()=>{};let k=y.on("broadcast",v=>{typeof v.event=="string"&&i.has(v.event)&&h(v.event,b(v.payload))}),w=()=>{S.delete(w),k()};return S.add(w),w},U={topic:u,get seq(){return y.logSeq},on(h,k){if(typeof h=="function")return H((w,v)=>h({event:w,payload:v}));if(!i.has(h))throw new TypeError(`gateway ${JSON.stringify(a)} declares no event ${JSON.stringify(String(h))}`);if(typeof k!="function")throw new TypeError(`gateway ${JSON.stringify(a)}: on(${JSON.stringify(h)}, \u2026) takes the function the event is handed to`);return H((w,v)=>{w===h&&k(v)})},off(h){S.has(h)&&h()},async send(h,k){if(!c.has(h))throw new TypeError(`gateway ${JSON.stringify(a)} declares no message ${JSON.stringify(String(h))}`);let w=C?{status:"error",response:{reason:"not joined"}}:await y.push("broadcast",{type:"broadcast",event:h,payload:k});if(w.status==="ok")return b(w.response.reply);let v=ge(w.response,"unavailable");throw new $(`gateway ${JSON.stringify(u)} did not take ${JSON.stringify(h)}: ${v}`,v)},leave:W};return d&&Object.defineProperty(U,"chat",{value:g.chat,enumerable:!0}),U}function Ue(t){let e=typeof t=="object"&&t!==null?t:{},n=s=>Array.isArray(s)&&s.every(r=>typeof r=="string");if(typeof e.pattern!="string"||e.pattern===""||!n(e.events)||!n(e.messages))throw new TypeError("join takes a gateway of the backend's generated module: its pattern, its events and its messages");return{pattern:e.pattern,events:new Set(e.events),messages:new Set(e.messages),durable:e.durable===!0,chat:e.chat===!0}}function Ve(t,e){let n=typeof e=="object"&&e!==null?e:{};return t.split(":").map(s=>{if(!(s.startsWith("{")&&s.endsWith("}")&&s.length>2))return s;let r=s.slice(1,-1),o=Object.hasOwn(n,r)?n[r]:void 0;if(typeof o!="string"||o==="")throw new TypeError(`gateway ${JSON.stringify(t)} needs a value for ${JSON.stringify(r)}`);if(o.includes(":"))throw new TypeError(`gateway ${JSON.stringify(t)}: the value of ${JSON.stringify(r)} is one segment of the channel's name and cannot hold ":"`);return o}).join(":")}function ge(t,e){let n=t?.reason;return typeof n=="string"&&n!==""?n:e}var ze=3600*1e3,O=new Map;function Qe(t,e,n=!1){let s=`${e.projectId}\0${e.signer}`,r=O.get(s);if(!n&&r&&Date.now()-r.at<ze)return r.list;let o=(async()=>{let a=te(),i;try{i=(await t.send({op:"GetTransitPublicKeys",method:"GET",path:"/secrets/transit/public",query:{nonce:a},auth:[["ApiKeyAuth"]],answers:{200:"json"}})).data}catch(c){throw new l("network",{code:"encryption_keys_unavailable",message:"the project's encryption keys could not be fetched: no encrypted field is sent without them",cause:c})}try{return await ne(i,e.signer,e.projectId,a)}catch(c){throw new l("decode",{code:"encryption_keys_unverified",message:"the project's list of encryption keys does not verify with the key this app was built with: nothing is sealed to it",cause:c})}})();return O.set(s,{list:o,at:Date.now()}),o.catch(()=>{O.get(s)?.list===o&&O.delete(s)}),o}async function ye(t,e,n,s,r,o){let i=(await Qe(t,e,o)).keys[0];return Y(i.kid,i.key,Q(e.projectId,n,s,r))}function K(t,e,n,s){return e==null||!s||s.length===0?Promise.resolve(e):G(e,n,s,(r,o)=>Z(t.key,r,o))}function me(t,e,n,s){return e===void 0||!n||n.length===0?Promise.resolve(e):G(e,"/response",n,async(r,o)=>{if(!X(o))throw new l("decode",{code:"field_not_encrypted",message:`${s}: ${r} came plain where the backend's contract says it is encrypted`});try{return await ee(t.key,r,o)}catch(a){throw new l("decode",{code:"field_unopenable",message:`${s}: ${r} does not open with this call's key`,cause:a})}})}function we(t){return t instanceof l&&(t.code==="encryption_key_retired"||t.code==="encryption_key_unknown")}var B=class{pending="";dataLines=[];eventName;consume(e){this.pending+=e.replace(/\r\n/g,`
|
|
3
3
|
`);let n=[],s=this.pending.indexOf(`
|
|
4
4
|
`);for(;s!==-1;){let r=this.pending.slice(0,s);this.pending=this.pending.slice(s+1);let o=r.endsWith("\r")?r.slice(0,-1):r;if(o==="")this.dataLines.length>0&&n.push({event:this.eventName,payload:this.dataLines.join(`
|
|
5
5
|
`)}),this.dataLines=[],this.eventName=void 0;else if(!o.startsWith(":")){if(o.startsWith("data:")){let a=o.slice(5);this.dataLines.push(a.startsWith(" ")?a.slice(1):a)}else if(o.startsWith("event:")){let a=o.slice(6);this.eventName=a.startsWith(" ")?a.slice(1):a}}s=this.pending.indexOf(`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.45.
|
|
2
|
-
import{a as s,f as a,g as i}from"./
|
|
1
|
+
/*! palbase 0.45.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a as s,f as a,g as i}from"./XP3VGN24.js";import"./5QUX45OF.js";import"./A2DBBW6I.js";import{pathToFileURL as m}from"node:url";import{z as u}from"zod";async function w(e){let t=new Map,n=new Map;for(let r of e){let d=`${r.className}.${r.method}`,o=n.get(r.className);if(o!==void 0&&o!==r.file)throw new Error(`two controllers are named ${r.className} (${o}, ${r.file}): the contract tells routes apart by their class's name \u2014 rename one`);n.set(r.className,r.file),t.set(d,{returns:await h(r),throws:r.throws})}return r=>t.get(`${r.controller.name}.${r.fn}`)}async function h(e){let t=`${e.file} \u2014 ${e.className}.${e.method}`;switch(e.returns.kind){case"void":return{kind:"void"};case"raw":return{kind:"raw"};case"schema":{let r=(await import(m(e.returns.file).href))[e.returns.exportName];if(!c(r))throw new Error(`${t}: its return type names ${e.returns.exportName}, which ${e.returns.file} exports as no schema (a zod, valibot or arktype schema)`);if(i(a(r)))throw new Error(`${t}: ${e.returns.exportName} marks an encrypted field (encrypted()) \u2014 an answer's is encrypt(value) in what the handler returns, its type Encrypted<T>, which the contract reads`);return{kind:"json",schema:r,array:e.returns.array}}case"expression":{let n=new Function("z","encrypted",`return (${e.returns.expression});`)(u,s);if(!c(n))throw new Error(`${t}: the schema made of its return type is not one`);return{kind:"json",schema:n,array:!1}}}}function c(e){return typeof e=="object"&&e!==null&&typeof e["~standard"]?.validate=="function"}export{w as routeContracts};
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! palbase 0.45.
|
|
2
|
+
/*! palbase 0.45.1 — Palbase SDK License (see LICENSE) */
|
|
3
3
|
var zw=Object.defineProperty;var Ye=(e,t)=>{for(var n in t)zw(e,n,{get:t[n],enumerable:!0})};import{parseArgs as Rv}from"node:util";var Ww=Symbol.for("palbase.data"),Fw=globalThis,Id=Fw[Ww]??=new WeakSet;function Jw(e){return e instanceof Date||ArrayBuffer.isView(e)||e instanceof ArrayBuffer}function Ya(e){let t=[e];for(;t.length>0;){let n=t.pop();if(!(typeof n!="object"||n===null||Id.has(n)||Jw(n))){Id.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var Vw="X-PoW-Challenge-ID",Hw="X-PoW-Nonce";function Dd(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 Yw(e){return 8*2**e}var ds;async function Xw(){if(ds)return ds;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 ds=a=>new Uint8Array(s("sha256").update(a).digest()),ds}catch{}let t=new TextEncoder;return ds=async n=>new Uint8Array(await crypto.subtle.digest("SHA-256",t.encode(n))),ds}function Zw(e){let t=0;for(let n of e){if(n===0){t+=8;continue}return t+Math.clz32(n)-24}return t}var Qw=4096,ev=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));async function xd(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 Xw(),s=Yw(e.difficulty),a=Date.now()+(t.timeLimitMs??12e4);for(let i=0;i<s;i++){if(i%Qw===0&&i>0){if(await ev(),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 ${i} attempts`)}let r=n(e.prefix+i),o=r instanceof Uint8Array?r:await r;if(Zw(o)>=e.difficulty)return{[Vw]:e.id,[Hw]:String(i)}}throw new Error(`proof-of-work: no nonce for difficulty ${e.difficulty} within ${s} attempts`)}var C=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 C({op:e,status:0,code:"invalid_path_parameter",message:`${e}: ${t} cannot be ${s===""?"empty":JSON.stringify(s)}`});return encodeURIComponent(s)}var tv=new Set(["apikey","authorization"]),nv=64*1024;function Jr(e){let t=Xa(e.baseUrl),n=e.fetch??globalThis.fetch,s=e.solveProofOfWork??!0,a=async(i,r)=>{let o=iv(t,i),l=new Headers;for(let[y,w]of Object.entries(i.headers??{}))w!=null&&!tv.has(y.toLowerCase())&&l.set(y,String(w));for(let[y,w]of Object.entries(r))l.set(y,w);await rv(i,e.credentials,l),l.has("accept")||l.set("accept","application/json");let d={method:i.method,headers:l,redirect:"manual",signal:i.signal,...i.keepalive?{keepalive:!0}:{}};i.body&&i.body.value!==void 0&&(l.set("content-type",i.body.contentType),d.body=ov(i.body.kind,i.body.value),ps(d.body)||(d.duplex="half"));let p;try{p=i.onUploadProgress&&e.xmlHttpRequest?await sv(e.xmlHttpRequest,i.op,o,d,i.onUploadProgress):await n(o,d)}catch(y){throw i.signal?.aborted||y instanceof C?y:new C({op:i.op,status:0,code:"network_error",message:`${i.op}: the request did not reach ${t.origin}`,cause:y})}let u=i.answers[p.status]??(i.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 C({op:i.op,status:p.status,code:"undeclared_answer",message:`${i.op}: the server answered ${p.status}, which its contract does not declare; the request may have taken effect`})):await dv(i.op,p);let g;switch(u){case"json":try{g=Ya(await p.json())}catch(y){throw new C({op:i.op,status:p.status,code:"invalid_json",message:`${i.op}: the answer is not JSON`,cause:y})}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(i){try{return await a(i,{})}catch(r){let o=s&&r instanceof C&&r.status===403?Dd(r.body):null,l=i.body?.value;if(!o||i.body?.kind==="raw"&&!ps(l))throw r;let d=r,p;try{p=await xd(o,{signal:i.signal})}catch(u){throw i.signal?.aborted?u:new C({op:i.op,status:403,code:"pow_unsolved",message:`${i.op}: ${u instanceof Error?u.message:String(u)}`,requestId:d.requestId,body:d.body,cause:u})}return a(i,p)}}}}function sv(e,t,n,s,a){return new Promise((i,r)=>{let o=s.signal;if(o?.aborted)return r(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){r(new C({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 r(new TypeError(`${t}: answered ${l.status}`));let u=l.status===204||l.status===205||l.status===304;i(new Response(u?null:l.response,{status:l.status,statusText:l.statusText,headers:av(l.getAllResponseHeaders())}))}),l.onerror=()=>p(()=>r(new TypeError(`${t}: the request failed`))),l.onabort=()=>p(()=>r(o?.reason??new DOMException("The request was aborted","AbortError"))),l.send(s.body)})}function av(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 Xa(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 iv(e,t){if(!t.path.startsWith("/")||t.path.startsWith("//"))throw new C({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+lv(t.query),e);if(n.origin!==e.origin)throw new C({op:t.op,status:0,code:"invalid_path",message:`${t.op}: path leaves the project's origin`});return n}async function rv(e,t,n){if(e.auth.length===0)return;let s=e.auth.some(r=>r.includes("userJWT"))?await t.accessToken?.():void 0,a=r=>{switch(r){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}},i=e.auth.filter(r=>r.every(a)).reduce((r,o)=>r===void 0||o.length>r.length?o:r,void 0);if(!i)throw new C({op:e.op,status:0,code:"missing_credential",message:`${e.op} needs one of: ${e.auth.map(r=>r.length===0?"nothing":r.join(" + ")).join(" | ")}`});for(let r of i)r==="ApiKeyAuth"?n.set("apikey",t.publishableKey??t.secretKey):(r==="SecretKeyAuth"||r==="AdminAuth"||r==="PalbaseBearer")&&t.token?n.set("authorization",`Bearer ${t.token}`):r==="SecretKeyAuth"||r==="AdminAuth"?n.set("apikey",t.secretKey):r==="userJWT"&&n.set("authorization",`Bearer ${s}`)}function ov(e,t){switch(e){case"json":return JSON.stringify(t);case"form":{let n=new URLSearchParams;for(let[s,a]of Object.entries(t))Vr(n,s,a);return n}case"raw":return t}}function ps(e){return!!(e==null||typeof e=="string"||e instanceof URLSearchParams||e instanceof Blob||e instanceof FormData||e instanceof ArrayBuffer||ArrayBuffer.isView(e))}function lv(e){if(!e)return"";let t=new URLSearchParams;for(let[s,a]of Object.entries(e))Vr(t,s,a);let n=t.toString();return n===""?"":`?${n}`}function Vr(e,t,n){if(n!=null){if(Array.isArray(n)){for(let s of n)Vr(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 dv(e,t){let n=await pv(t,nv).catch(()=>""),s;try{s=n===""?void 0:JSON.parse(n)}catch{s=n}let a=uv(s)?s:void 0,i=Number(t.headers.get("retry-after")),r=t.status>=300&&t.status<400;return new C({op:e,status:t.status,code:a?.error??(r?"undeclared_redirect":`http_${t.status}`),message:a?.error_description??(r?`${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(i)&&i>0?i:void 0),body:s})}async function pv(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 i=new Uint8Array(Math.min(a,t)),r=0;for(let o of s){let l=Math.min(o.byteLength,i.byteLength-r);if(i.set(o.subarray(0,l),r),r+=l,r>=i.byteLength)break}return new TextDecoder().decode(i)}function uv(e){return typeof e=="object"&&e!==null&&typeof e.error=="string"}var Za={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 f=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 f("usage",e,{hint:t,exitCode:2})}var Qa=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(`${N(t)}
|
|
5
5
|
`)}note(t){this.io.stderr(`${N(t)}
|
|
@@ -9,7 +9,7 @@ var zw=Object.defineProperty;var Ye=(e,t)=>{for(var n in t)zw(e,n,{get:t[n],enum
|
|
|
9
9
|
`);let r;try{r=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 f("network_error",`${new URL(t).origin} could not be reached: ${l instanceof Error?l.message:String(l)}`)}if(this.debug&&this.io.stderr(`< ${r.status} ${r.headers.get("x-request-id")??""}
|
|
10
10
|
`),r.status!==503||!s||i>=$v||!Sv.has(await Xr(r)??""))return r;await r.body?.cancel();let o=Number(r.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(),i=await this.send(t,n,s,a.value);if(i.status===401&&a.renewable){let r=await this.renew(await this.currentSession());i=await this.send(t,n,s,r.access_token)}return{res:i,renewable:a.renewable}}async answered(t,n,s,a){if(s.status===204)return;let i=await s.text(),r=i===""?void 0:Pv(i);if(s.ok)return r;let o=r??{};throw s.status===401?o.error==="token_not_for_machines"?Qd(new URL(this.cloud.studioApi).host):a?us():this.tokenRefused("Studio"):new C({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:r})}tokenRefused(t){return(this.io.env.PALBASE_TOKEN??"").startsWith("pbe_")?new f("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 f("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 f("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 Hd(this.io,this.cloud,t);return await this.state.writeSession(n),this.session=n,n}async send(t,n,s,a){let i=`${this.cloud.studioApi}${kv}${n}`;this.debug&&this.io.stderr(`> ${t} ${i}
|
|
11
11
|
`);try{let r=await this.io.fetch(i,{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(`< ${r.status} ${r.headers.get("x-request-id")??""}
|
|
12
|
-
`),r}catch(r){throw new f("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${r instanceof Error?r.message:String(r)}`)}}};function Qd(e){return new f("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 Xr(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 Pv(e){try{return JSON.parse(e)}catch{return e}}var ep={version:"0.45.
|
|
12
|
+
`),r}catch(r){throw new f("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${r instanceof Error?r.message:String(r)}`)}}};function Qd(e){return new f("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 Xr(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 Pv(e){try{return JSON.parse(e)}catch{return e}}var ep={version:"0.45.1"};var ai=ep.version;async function ip(e,t,n){let s=new Map(n.map(r=>[r.name,r])),a=e.includes("--json"),i=e.includes("--debug");try{let{name:r,at:o}=Tv(e);if(r===void 0)return e.includes("--version")?(t.stdout(`palbase ${ai}
|
|
13
13
|
`),0):(t.stdout(ap(n)),e.length===0||e.includes("--help")||e.includes("-h")?0:2);if(r==="help"){let S=e[o+1],$=S===void 0?void 0:s.get(S);if(S!==void 0&&!$)throw tp(S,n);return t.stdout($?sp($):ap(n)),0}let l=s.get(r);if(!l)throw tp(r,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}=Ev(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`}`,Qr(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`,Qr(l));let y={project:p.project,env:p.env,json:p.json===!0,nonInteractive:p["non-interactive"]===!0,yes:p.yes===!0,debug:p.debug===!0},w=Bd(t.env),_=new ei(Fd(t.configDir,w)),P={io:t,state:_,out:new Qa(t,y.json),global:y,args:u,flags:p,interactive:t.interactive&&!y.nonInteractive,cloud:w,studio:new si(t,w,_,y.debug)};return await l.run(P)??0}catch(r){return Cv(r,t,a,i)}}function Tv(e){let t=new Set;for(let[n,s]of Object.entries(Za))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 Ev(e,t){let n={...Za,...e.flags??{}},s={};for(let[a,i]of Object.entries(n))s[a]={type:i.type,...i.short?{short:i.short}:{},...i.multiple?{multiple:!0}:{}};try{let{values:a,positionals:i}=Rv({args:t,options:s,strict:!0,allowPositionals:!0});return{flags:a,positionals:i}}catch(a){throw m(a instanceof Error?a.message.replace(/^.*?: /,""):String(a),`palbase help ${e.name}`)}}function Cv(e,t,n,s){let a="error",i,r,o=1,l;return e instanceof f?(a=e.code,i=e.message,r=e.hint,o=e.exitCode):e instanceof C?(a=e.code,i=e.message,l=e.requestId,e.code==="token_out_of_scope"&&t.env.PALBASE_TOKEN&&(i=`${e.op} refused PALBASE_TOKEN: ${e.message}`,r="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")):i=e instanceof Error?e.message:String(e),n&&e instanceof f&&e.reported||(n&&t.stdout(`${JSON.stringify({error:a,message:i,...r?{hint:r}:{},...l?{request_id:l}:{}},null,2)}
|
|
14
14
|
`),t.stderr(`error: ${N(i)}${l?` (request ${N(l)})`:""}
|
|
15
15
|
`),r&&t.stderr(` ${N(r)}
|
|
@@ -691,7 +691,7 @@ ${t}}`}var tn=e=>["",e.toUpperCase(),"-".repeat(e.length)],wT=[{title:"One packa
|
|
|
691
691
|
`),e.out.result({file:s,revision:n.revision},()=>{e.out.line(`Wrote revision ${n.revision} of the catalog to ${s}.`),e.out.line(`Save an edit of it: palbase purchases:catalog:put --file ${s} --revision ${n.revision}`)});return}e.out.result(n,()=>Tb(e,n))}},oO={name:"purchases:catalog:put",summary:"save a new revision of the whole catalog, on the revision it was edited from; nothing is written to a store",usage:"--file <catalog.json> --revision <n>",details:["The module checks it first: a catalog that breaks a rule is refused with every problem, and nothing is saved.","Of two people editing at once, the second is told \u2014 the catalog moved past the revision they edited \u2014 never overwritten.","The stores are brought to it by a plan: palbase purchases:catalog:plan."],flags:{file:{type:"string",value:"catalog.json",description:"the catalog, as catalog:get --out wrote it ('-' or none: stdin)"},revision:{type:"string",value:"n",description:"the revision it was edited from (0 for the first), as catalog:get printed it"}},scope:"environment",async run(e){let t=await b(e),n=se(e.flags.revision,"revision");if(n===void 0)throw m("--revision is the revision the catalog was edited from (0 for the first): of two edits at once, the second is told, not saved over the first");let s=await Ab(e);await v(e,t,`save the catalog (${s.products?.length??0} products) over revision ${n}`);let a;try{a=await h(e,t,"PUT","/purchases/catalog",s,{"if-match":Pb(n),...j()})}catch(i){if(i instanceof C&&i.status===412)throw new f("catalog_changed",`the catalog is no longer at revision ${n}: someone saved a newer one since it was read \u2014 nothing was saved`,{hint:"read it again (palbase purchases:catalog:get --out <file>), make the edit on it, and save it with that revision"});cd(e,i,"the catalog was not saved")}e.out.result(a,()=>{e.out.line(`Saved revision ${a.revision} of the catalog.`),e.out.line("Bring the stores to it: palbase purchases:catalog:plan")})}},lO={name:"purchases:catalog:validate",summary:"check a catalog without saving it: every problem, with its path and the store whose rule it breaks",usage:"--file <catalog.json>",flags:{file:{type:"string",value:"catalog.json",description:"the catalog ('-' or none: stdin)"}},scope:"environment",async run(e){let t=await b(e),n=await Ab(e),s;try{s=await h(e,t,"POST","/purchases/catalog/validate",n,j())}catch(a){cd(e,a,"the catalog cannot be read")}if(e.out.result(s,()=>{s.problems.length>0&&Rb(e,s.problems),e.out.line(s.valid?`The catalog can be saved${s.problems.length>0?`, with ${s.problems.length} warning(s)`:""}.`:"The catalog cannot be saved as it is.")}),!s.valid)throw new f("invalid_catalog",`the catalog breaks ${s.problems.filter(a=>!a.warning).length} rule(s)`)}};function ld(e,t,n){e.out.line(`${t}${n.store_env?` (${n.store_env})`:""}: ${n.steps.length===0?"nothing to write":`${n.steps.length} step(s)`}`);for(let s of n.steps)e.out.line(` ${s.op} ${s.product}${s.plan?`/${s.plan}`:""}${s.store_id?` (${s.store_id})`:""}${s.irreversible?" \u2014 permanent":""}${s.note?`: ${s.note}`:""}`);n.note&&e.out.line(` ${n.note}`)}function hd(e){let t=ce(e,0,"the plan's id");if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t))throw m(`${t} is no plan's id: it is the id purchases:catalog:plan printed`);return t}function Eb(e){return e.apple_result!=null&&!e.apple_result.ok||e.google_result!=null&&!e.google_result.pending&&!e.google_result.ok}function dO(e,t){if(e.out.line(`Plan ${t.id} for revision ${t.revision}: ${t.status}`),t.stripe?ld(e,xr.stripe,t.stripe):e.out.line(`${xr.stripe}: no account connected (palbase purchases:stripe:grant connects one)`),ld(e,xr.apple,t.apple),ld(e,xr.google,t.google),t.problems.length>0){e.out.line("Warnings of the revision:");for(let i of t.problems)e.out.line(` ${i.path}: ${i.message}${i.channel?` (${i.channel})`:""}`)}t.status==="failed"&&t.error&&e.out.line(Eb(t)||!t.stripe?`It failed: ${t.error}`:`Stripe answered: ${t.error}`);let n=t.apple_result;if(!n&&t.apple.steps.length>0&&(t.status==="applying"||t.status==="applied")&&e.out.line("App Store Connect: waiting for the stores module"),n){e.out.line(`App Store Connect (${n.bundle_id}): ${n.ok?"written":`not all written${n.error?`: ${n.error}`:""}`}`);for(let i of n.steps){let r=i.price?`, ${i.price.customer_price} ${i.price.currency}`:"",o=i.next_price?`, ${i.next_price.customer_price} ${i.next_price.currency} from ${i.next_price.from}`:"";e.out.line(` ${i.op} ${i.store_id}${i.state?`: ${i.state}`:""}${r}${o}${i.error?` \u2014 ${i.error}`:""}`)}}let s=t.google_result;if(s){e.out.line(`Google Play (${s.package_name}): ${s.pending?"waiting for the stores module":s.ok?"written":`not all written${s.error?`: ${s.error}`:""}`}`);for(let i of s.steps)e.out.line(` ${i.op} ${i.store_id}${i.state?`: ${i.state}`:""}${i.error?` \u2014 ${i.error}`:""}`)}let a=t.stripe?t.stripe.hash:t.apple.hash||t.google.hash;t.status==="planned"&&a&&(e.out.line(` hash: ${a}`),e.out.line(t.stripe?`The Stripe part is written only when confirmed${t.apple.steps.length>0?" (the App Store part goes with it, to the stores module)":""}:`:t.apple.hash?"The App Store part is written only when confirmed (the stores module writes it to App Store Connect):":"The Google Play part is written only when confirmed (the stores module writes it to Google Play):"),e.out.line(` palbase purchases:catalog:confirm ${t.id} --hash ${a}`))}var pO={name:"purchases:catalog:plan",summary:"plan bringing the stores to the catalog's newest revision, store by store, before anything is written",usage:"[<plan id>]",details:["A plan is confirmed with its Stripe part's hash (purchases:catalog:confirm) \u2014 with no Stripe account connected, its App Store part's; the App Store and Google Play parts are written by the stores module once their accounts are connected.","With a plan's id it makes none: it shows that plan, and once confirmed what each store answered \u2014 the state of every product written, and a price that changes on a later day."],args:{min:0,max:1},scope:"environment",async run(e){let t=await b(e),n=e.args.length===0?await h(e,t,"POST","/purchases/catalog/plans",void 0,j()):await h(e,t,"GET",`/purchases/catalog/plans/${k(hd(e))}`);e.out.result(n,()=>dO(e,n))}},uO={name:"purchases:catalog:confirm",summary:"write a plan \u2014 its Stripe part now, its App Store part through the stores module: it names the hash of the plan that was shown, and is asked about first",usage:"<plan id> --hash <hash>",details:["What it writes is shown, then asked about \u2014 in every environment: a price written to Stripe is never changed or removed there, a product made in App Store Connect never goes away. -y confirms without asking."],args:{min:1,max:1},flags:{hash:{type:"string",value:"hash",description:"the hash the plan printed (its Stripe part's; with no Stripe account, its App Store part's)"}},scope:"environment",async run(e){let t=await b(e),n=hd(e),s=e.flags.hash;if(s===void 0||s.trim()==="")throw m("--hash is the hash purchases:catalog:plan printed: a confirmation names the plan it saw");let a=await h(e,t,"GET",`/purchases/catalog/plans/${k(n)}`);if(a.status!=="planned")throw new f("plan_not_open",`the plan ${n} is ${a.status}: only a planned one is confirmed`,{hint:"make a new one: palbase purchases:catalog:plan"});let i=a.apple.steps.length,r=a.google.steps.length,o=[i>0?`${i} to App Store Connect`:"",r>0?`${r} to Google Play`:""].filter(Boolean);if(a.stripe)await v(e,t,`write ${a.stripe.steps.length} step(s) of revision ${a.revision} to Stripe${o.length>0?`, and ${o.join(" and ")}`:""}`,"It writes the project's Stripe account, where a price is never changed or removed");else if(a.apple.hash&&i>0)await v(e,t,`write ${i} step(s) of revision ${a.revision} to App Store Connect${r>0?`, and ${r} to Google Play`:""}`,"It makes the products in App Store Connect, where a product id never goes away");else if(a.google.hash&&r>0)await v(e,t,`write ${r} step(s) of revision ${a.revision} to Google Play`,"It makes the products in Google Play, where a product id never goes away");else throw new f("no_stripe_part",`the plan ${n} has no Stripe part (no Stripe account was connected when it was made), and nothing for App Store Connect or Google Play to confirm`);let l=await h(e,t,"POST",`/purchases/catalog/plans/${k(n)}/confirm`,{hash:s},j());if(e.out.result(l,()=>{e.out.line(`Plan ${l.id}: ${l.status}${l.applied_at?` (${I(l.applied_at)})`:""}.`),l.status==="failed"&&l.error&&e.out.line(Eb(l)||!l.stripe?`It failed: ${l.error}`:`Stripe answered: ${l.error}`);let d=[i>0&&!l.apple_result?"App Store Connect":"",r>0&&(!l.google_result||l.google_result.pending)?"Google Play":""].filter(Boolean);d.length>0&&e.out.line(`The stores module is writing to ${d.join(" and ")}: palbase purchases:catalog:plan ${l.id} shows what it wrote.`)}),l.status==="failed")throw new f("plan_failed",`the plan ${l.id} failed: a new plan picks up where it stopped`)}},cO={name:"purchases:catalog:decline",summary:"decline a plan: its Stripe part is never written",usage:"<plan id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=hd(e);await v(e,t,`decline the plan ${n}`);let s=await h(e,t,"POST",`/purchases/catalog/plans/${k(n)}/decline`,void 0,j());e.out.result(s,()=>e.out.line(`Declined the plan ${s.id}.`))}};function Cb(e){let t=e.args[0],n=Number(t);if(t===void 0||!Number.isInteger(n)||n<1)throw m("the revision's number is required (purchases:catalog:revisions lists them)");return n}var hO={name:"purchases:catalog:revisions",summary:"every revision the catalog was saved as, the newest first: who saved it, when, and when a plan of it was written to the stores",usage:"[--limit <n>] [--cursor <next>]",details:["purchases:catalog:revision <n> reads one; purchases:catalog:restore <n> puts it back as the next revision."],flags:{limit:{type:"string",value:"n",description:"revisions a page (default 50, at most 200)"},cursor:{type:"string",value:"next",description:"the next page, as the last one printed it"}},scope:"environment",async run(e){let t=await b(e),n=new URLSearchParams,s=se(e.flags.limit,"limit");s!==void 0&&n.set("limit",String(s)),e.flags.cursor!==void 0&&n.set("cursor",String(e.flags.cursor));let a=await h(e,t,"GET",`/purchases/catalog/revisions${n.size?`?${n}`:""}`);e.out.result(a,()=>{if(a.revisions.length===0){e.out.line("No catalog yet: palbase purchases:catalog:put --file catalog.json --revision 0");return}e.out.table(a.revisions,[{header:"revision",value:i=>`${i.revision}${i.current?" (now)":""}`},{header:"saved",value:i=>I(i.created_at)},{header:"by",value:i=>i.created_by},{header:"products",value:i=>String(i.products)},{header:"entitlements",value:i=>String(i.entitlements)},{header:"offers",value:i=>String(i.offers)},{header:"written to stores",value:i=>I(i.applied_at)}]),a.next_cursor&&e.out.line(`Older: palbase purchases:catalog:revisions --cursor ${a.next_cursor}`)})}},mO={name:"purchases:catalog:revision",summary:"the catalog as it was saved at a revision",usage:"<revision> [--out <catalog.json>]",details:["--out writes that revision's catalog to a file, as catalog:get --out writes the newest."],args:{min:1,max:1},flags:{out:{type:"string",value:"catalog.json",description:"write the revision's catalog to this file"}},scope:"environment",async run(e){let t=await b(e),n=Cb(e),s=await h(e,t,"GET",`/purchases/catalog/revisions/${n}`),a=e.flags.out;if(a!==void 0){await ud(za(e.io.cwd,a),`${JSON.stringify(s.catalog,null,2)}
|
|
692
692
|
`),e.out.result({file:a,revision:s.revision},()=>e.out.line(`Wrote revision ${s.revision} of the catalog to ${a}.`));return}e.out.result(s,()=>Tb(e,s))}},gO={name:"purchases:catalog:restore",summary:"put an older revision of the catalog back, as its next revision; nothing is written to a store",usage:"<revision>",details:["It is saved as an edit is: checked against today's rules, what customers bought kept in it \u2014 a revision that breaks one is refused with every problem.","The stores are brought to it by a plan, confirmed: palbase purchases:catalog:plan."],args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=Cb(e),s=await h(e,t,"GET","/purchases/catalog");await v(e,t,`put revision ${n} of the catalog back, over revision ${s.revision}`);let a;try{a=await h(e,t,"POST",`/purchases/catalog/revisions/${n}/restore`,void 0,{"if-match":Pb(s.revision),...j()})}catch(i){if(i instanceof C&&i.status===412)throw new f("catalog_changed",`the catalog moved past revision ${s.revision} while this ran \u2014 nothing was saved`,{hint:`run it again: palbase purchases:catalog:restore ${n}`});cd(e,i,`revision ${n} was not put back`)}e.out.result(a,()=>{e.out.line(`Saved revision ${n}'s catalog as revision ${a.revision}.`),e.out.line("Bring the stores to it: palbase purchases:catalog:plan")})}};function dd(e){return e===null?"for life":I(e)}function fO(e,t){e.out.line(`Customer ${t.customer_id}${t.guest?" (guest)":""}: revision ${t.revision}, as of ${I(t.requested_at)}`);let n=Object.entries(t.entitlements).sort(([s],[a])=>s<a?-1:s>a?1:0);e.out.line(n.length===0?"No entitlement.":"Entitlements:"),e.out.table(n,[{header:"entitlement",value:([s])=>s},{header:"active",value:([,s])=>s.active?"yes":"no"},{header:"until",value:([,s])=>dd(s.expires_at)},{header:"store",value:([,s])=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"renews",value:([,s])=>s.will_renew?"yes":"no"},{header:"product",value:([,s])=>s.product??"\u2014"}]),t.subscriptions.length>0&&(e.out.line("Subscriptions:"),e.out.table(t.subscriptions,[{header:"product",value:s=>`${s.product??"\u2014"}${s.plan?`/${s.plan}`:""}`},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"until",value:s=>dd(s.expires_at)},{header:"renews",value:s=>s.will_renew?"yes":"no"},{header:"id",value:s=>s.id}])),t.purchases.length>0&&(e.out.line("Purchases:"),e.out.table(t.purchases,[{header:"product",value:s=>s.product??"\u2014"},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"bought",value:s=>I(s.purchased_at)}])),t.credits.length>0&&(e.out.line("Credits:"),e.out.table(t.credits,[{header:"credit",value:s=>s.currency},{header:"balance",value:s=>String(s.balance)},{header:"available",value:s=>String(s.available)},{header:"held",value:s=>String(s.reserved)},{header:"owed",value:s=>String(s.debt)},{header:"next expiry",value:s=>I(s.next_expiry)}])),t.limits.length>0&&(e.out.line("Limits:"),e.out.table(t.limits,[{header:"limit",value:s=>s.limit},{header:"used",value:s=>`${s.used} of ${s.max}`},{header:"held",value:s=>String(s.reserved)},{header:"left",value:s=>String(s.remaining)},{header:"resets",value:s=>I(s.resets_at)}]))}var yO={name:"purchases:customer:get",summary:"what a customer has, whatever store they bought in: entitlements, subscriptions, purchases, credits and limits",usage:"<customer id>",details:["The customer is the user's id; a guest merged into an account answers as the account."],args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=await h(e,t,"GET",`/purchases/customers/${k(n)}`);e.out.result(s,()=>fO(e,s))}};function md(e){let t=e.flags.until,n=e.flags.for;if(t!==void 0&&n!==void 0)throw m("--until or --for, not both");if(t!==void 0){let s=Date.parse(t);if(!Number.isFinite(s))throw m(`--until ${t}: a date or time (2026-12-31, 2026-12-31T18:00:00Z)`);if(s<=e.io.now())throw m(`--until ${t} is not in the future`);return new Date(s).toISOString()}if(n!==void 0){let s=/^([1-9][0-9]{0,4})([hdw])$/.exec(n.trim());if(!s)throw m(`--for ${n}: a number of hours, days or weeks (12h, 30d, 2w)`);let a=Number(s[1])*{h:1,d:24,w:168}[s[2]];return new Date(e.io.now()+a*36e5).toISOString()}}var bO={name:"purchases:customer:grant",summary:"give a customer one of the catalog's entitlements, for a while or for life, saying why \u2014 a promotion, a support case, a tester",usage:"<customer id> <entitlement> --reason <why> [--for 30d | --until <date>]",details:["The reason is the record. Without --for or --until the grant is for life. It prints the grant's id: purchases:customer:revoke ends it.","A customer not seen before is made; nothing is charged, in any store."],args:{min:2,max:2},flags:{reason:{type:"string",value:"why",description:"why it is given (500 characters at most): the record"},for:{type:"string",value:"span",description:"how long: 12h, 30d, 2w"},until:{type:"string",value:"date",description:"when it ends (2026-12-31, or a time)"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the entitlement"),a=e.flags.reason?.trim();if(!a)throw m("--reason says why it is given: the grant's record");if(a.length>500)throw m("--reason is 500 characters at most");let i=md(e);await v(e,t,`give ${n} ${s} ${i?`until ${I(i)}`:"for life"}`);let r={entitlement:s,reason:a,...i?{expires_at:i}:{}},o=await h(e,t,"POST",`/purchases/customers/${k(n)}/grants`,r,j());e.out.result(o,()=>{e.out.line(`Gave ${o.customer_id} ${o.entitlement} ${o.expires_at?`until ${I(o.expires_at)}`:"for life"} (grant ${o.id}).`),e.out.line(`End it: palbase purchases:customer:revoke ${o.customer_id} ${o.id}`)})}},wO={name:"purchases:customer:revoke",summary:"end a granted entitlement now; what the customer bought is not touched",usage:"<customer id> <grant id>",details:["The grant's id is the one purchases:customer:grant printed. The entitlement stays on while a purchase still gives it."],args:{min:2,max:2},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the grant's id");await v(e,t,`end the grant ${s} of ${n}`),await h(e,t,"DELETE",`/purchases/customers/${k(n)}/grants/${k(s)}`,void 0,j()),e.out.result({revoked:s,customer_id:n},()=>e.out.line(`Ended the grant ${s} of ${n}.`))}};function vO(e,t){return e==null?"":`${(e/1e6).toFixed(2)}${t?` ${t}`:""}`}var _O={name:"purchases:customers",summary:"find customers by what a person writing in has: their id, an id their store gave a purchase, or the email they bought with on the web",usage:"<query>",details:["The query is a customer's id; Apple's original transaction id or any transaction id; a Google order id (GPA.\u2026, a renewal's ..N or not); Stripe's subscription, payment intent, invoice or customer id; or an email address.","An email finds a buyer who had no account at checkout, matched whole (case aside): the address is never kept, an HMAC of it is."],args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"what to look for (an id, a store id, an email)"),s=await h(e,t,"GET",`/purchases/customers?q=${encodeURIComponent(n)}`);e.out.result(s,()=>{if(s.customers.length===0){e.out.line(`No customer found by ${s.query}.`);return}e.out.table(s.customers,[{header:"customer",value:i=>i.customer_id??"\u2014 (not redeemed yet)"},{header:"found by",value:i=>i.matched_by},{header:"store",value:i=>i.store?`${i.store}${i.is_sandbox?" (sandbox)":""}`:""},{header:"product",value:i=>i.product??""},{header:"since",value:i=>I(i.at)}]);let a=s.customers.find(i=>i.customer_id);a&&e.out.line(`What happened to them: palbase purchases:customer:history ${a.customer_id}`)})}},kO={name:"purchases:customer:history",summary:"what happened to a customer, the newest first: payments, refunds, cancellations, grants, credits, moves \u2014 with who did each, and why",usage:"<customer id> [--limit <n>] [--cursor <next>]",details:["Credits spent are the credits' own entries; here are the credits given, taken back, expired and forgiven."],args:{min:1,max:1},flags:{limit:{type:"string",value:"n",description:"events a page (default 50, at most 200)"},cursor:{type:"string",value:"next",description:"the next page, as the last one printed it"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=new URLSearchParams,a=se(e.flags.limit,"limit");a!==void 0&&s.set("limit",String(a)),e.flags.cursor!==void 0&&s.set("cursor",String(e.flags.cursor));let i=await h(e,t,"GET",`/purchases/customers/${k(n)}/history${s.size?`?${s}`:""}`);e.out.result(i,()=>{e.out.line(`History of ${i.customer_id}, the newest first:`),i.events.length===0&&e.out.line("Nothing yet."),e.out.table(i.events,[{header:"when",value:r=>I(r.at)},{header:"what",value:r=>r.kind},{header:"store",value:r=>r.store?`${r.store}${r.is_sandbox?" (sandbox)":""}`:""},{header:"product",value:r=>[r.product,r.plan].filter(Boolean).join("/")||r.entitlement||""},{header:"amount",value:r=>r.credits?`${r.credits.delta>0?"+":""}${r.credits.delta} ${r.credits.currency}`:vO(r.amount_micros,r.currency)},{header:"why",value:r=>r.credits?[r.credits.kind,r.reason].filter(Boolean).join(": "):r.reason??""},{header:"by",value:r=>r.actor}]),i.next_cursor&&e.out.line(`Older: palbase purchases:customer:history ${i.customer_id} --cursor ${i.next_cursor}`)})}},SO={name:"purchases:customer:grants",summary:"the entitlements given a customer \u2014 active, ended and revoked \u2014 with each grant's id, reason and who gave it",usage:"<customer id>",details:["End one: palbase purchases:customer:revoke <customer id> <grant id>."],args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=await h(e,t,"GET",`/purchases/customers/${k(n)}/grants`);e.out.result(s,()=>{if(s.grants.length===0){e.out.line(`${s.customer_id} was given nothing.`);return}e.out.table(s.grants,[{header:"grant",value:a=>a.id},{header:"entitlement",value:a=>a.entitlement},{header:"active",value:a=>a.active?"yes":"no"},{header:"ends",value:a=>a.revoked_at?`revoked ${I(a.revoked_at)}`:a.expires_at?I(a.expires_at):"for life"},{header:"why",value:a=>a.reason},{header:"by",value:a=>a.revoked_by?`${a.granted_by}, revoked by ${a.revoked_by}`:a.granted_by}])})}};function gd(e){let t=e.flags.note?.trim();if(t!==void 0&&t.length>500)throw m("--note is 500 characters at most");return t||void 0}function fd(e,t,n){let s=t.subscription;e.out.line(`${n} (${t.action.store}, by ${t.action.actor}).`),e.out.line(`${s.product??"\u2014"}${s.plan?`/${s.plan}`:""}: ${s.state}, ${s.active?"on":"off"} until ${dd(s.expires_at)}, ${s.will_renew?"renews":"does not renew"}.`)}var yd={note:{type:"string",value:"why",description:"what support writes of it (500 characters at most): the record"}},$O={name:"purchases:customer:extend",summary:"move a customer's subscription renewal days later, for free: the App Store's extension, Google Play's deferral, Stripe's next charge moved",usage:"<customer id> <subscription id> --days <1-90> --reason customer_satisfaction|service_issue|other [--note <why>]",details:["The subscription's id is the one purchases:customer:get prints. The App Store extends a paid, active subscription, at most 90 days and twice a year.","The store is asked again after: the new expiry is printed, and the customer's history keeps it with who did it."],args:{min:2,max:2},flags:{days:{type:"string",value:"n",description:"how many days (1 to 90)"},reason:{type:"string",value:"why",description:"customer_satisfaction, service_issue or other: as the App Store asks it"},...yd},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the subscription's id (purchases:customer:get prints it)"),a=se(e.flags.days,"days");if(a===void 0||a<1||a>90)throw m("--days is 1 to 90");let i=e.flags.reason;if(i!=="customer_satisfaction"&&i!=="service_issue"&&i!=="other")throw m("--reason is customer_satisfaction, service_issue or other");let r=gd(e);await v(e,t,`extend ${n}'s subscription ${s} by ${a} day(s)`);let o={days:a,reason:i,...r?{note:r}:{}},l=await h(e,t,"POST",`/purchases/customers/${k(n)}/subscriptions/${k(s)}/extend`,o,j());e.out.result(l,()=>fd(e,l,`Extended ${n}'s subscription by ${a} day(s)`))}},PO={name:"purchases:customer:refund",summary:"refund a customer's subscription \u2014 its latest payment, or what is left of its period \u2014 and end it now: Google Play and Stripe",usage:"<customer id> <subscription id> --mode full|prorated [--note <why>]",details:["The customer's access ends as it answers. The App Store decides its own refunds: the customer asks Apple, which asks the app what they used \u2014 Palbase answers it when they consented in the app."],args:{min:2,max:2},flags:{mode:{type:"string",value:"kind",description:"full: the latest payment; prorated: what is left of its period"},...yd},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the subscription's id (purchases:customer:get prints it)"),a=e.flags.mode;if(a!=="full"&&a!=="prorated")throw m("--mode is full (the latest payment) or prorated (what is left of its period)");let i=gd(e);await v(e,t,`refund ${n}'s subscription ${s} (${a}) and end it now`);let r={mode:a,...i?{note:i}:{}},o=await h(e,t,"POST",`/purchases/customers/${k(n)}/subscriptions/${k(s)}/refund`,r,j());e.out.result(o,()=>fd(e,o,`Refunded ${n}'s subscription (${a}); it ended`))}},AO={name:"purchases:customer:cancel",summary:"turn a customer's subscription renewal off: it ends at its period's end, the customer keeping what they paid for \u2014 Google Play and Stripe",usage:"<customer id> <subscription id> [--note <why>]",details:["An App Store subscription's renewal is the customer's to turn off (Settings \u203A Apple ID \u203A Subscriptions)."],args:{min:2,max:2},flags:{...yd},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the subscription's id (purchases:customer:get prints it)"),a=gd(e);await v(e,t,`turn the renewal of ${n}'s subscription ${s} off`);let i=a?{note:a}:{},r=await h(e,t,"POST",`/purchases/customers/${k(n)}/subscriptions/${k(s)}/cancel`,i,j());e.out.result(r,()=>fd(e,r,`Turned the renewal of ${n}'s subscription off`))}},jb={source:{type:"string",value:"kind",description:"support (the default) or promotion"}};function Ob(e){let t=e.flags.source??"support";if(t!=="support"&&t!=="promotion")throw m("--source is support or promotion");return t}function Nr(e,t){let n=e.flags.reason?.trim();if(!n)throw m(`--reason says why ${t}: the record`);if(n.length>500)throw m("--reason is 500 characters at most");return n}function Mb(e,t,n){let s=ce(e,t,n),a=Number(s);if(!Number.isInteger(a)||a<1)throw m(`${n} is a whole number above 0: ${s}`);return a}var RO={name:"purchases:customer:credits",summary:"a customer's credit entries, the newest first: given, spent, taken back, expired, forgiven \u2014 with who did each, and why",usage:"<customer id> [--credits <key>] [--limit <n>] [--cursor <next>]",args:{min:1,max:1},flags:{credits:{type:"string",value:"key",description:"one of the catalog's credits alone"},limit:{type:"string",value:"n",description:"entries a page (default 50, at most 200)"},cursor:{type:"string",value:"next",description:"the next page, as the last one printed it"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=new URLSearchParams;e.flags.credits!==void 0&&s.set("credits",String(e.flags.credits));let a=se(e.flags.limit,"limit");a!==void 0&&s.set("limit",String(a)),e.flags.cursor!==void 0&&s.set("cursor",String(e.flags.cursor));let i=await h(e,t,"GET",`/purchases/customers/${k(n)}/credits/entries${s.size?`?${s}`:""}`);e.out.result(i,()=>{i.entries.length===0&&e.out.line(`${n} has no credit entry.`),e.out.table(i.entries,[{header:"when",value:r=>I(r.created_at)},{header:"credit",value:r=>r.currency},{header:"what",value:r=>r.kind},{header:"amount",value:r=>`${r.delta>0?"+":""}${r.delta}`},{header:"owed",value:r=>r.debt_delta?`${r.debt_delta>0?"+":""}${r.debt_delta}`:""},{header:"why",value:r=>r.reason??""},{header:"by",value:r=>r.actor??""}]),i.next_cursor&&e.out.line(`Older: palbase purchases:customer:credits ${n} --cursor ${i.next_cursor}`)})}},TO={name:"purchases:customer:credits:give",summary:"give a customer credits, saying why \u2014 a support case, a promotion \u2014 for a while or until spent",usage:"<customer id> <credits> <amount> --reason <why> [--source support|promotion] [--for 30d | --until <date>]",details:["What they owe is paid first from what is given. Nothing is charged, in any store."],args:{min:3,max:3},flags:{reason:{type:"string",value:"why",description:"why they are given (500 characters at most): the record"},...jb,for:{type:"string",value:"span",description:"how long they last: 12h, 30d, 2w"},until:{type:"string",value:"date",description:"when they expire (2026-12-31, or a time)"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the credits (the catalog's key)"),a=Mb(e,2,"the amount"),i=Nr(e,"they are given"),r=Ob(e),o=md(e);await v(e,t,`give ${n} ${a} ${s}${o?` until ${I(o)}`:""}`);let l={credits:s,amount:a,source:r,reason:i,...o?{expires_at:o}:{}},d=await h(e,t,"POST",`/purchases/customers/${k(n)}/credits`,l,j());e.out.result(d,()=>e.out.line(`Gave ${n} ${d.original} ${d.currency}${d.expires_at?` until ${I(d.expires_at)}`:""}; ${d.remaining} left of it.`))}},EO={name:"purchases:customer:credits:forgive",summary:"forgive what a customer owes of a credit \u2014 all of it, or an amount \u2014 saying why",usage:"<customer id> <credits> --reason <why> [--amount <n>]",args:{min:2,max:2},flags:{reason:{type:"string",value:"why",description:"why it is forgiven (500 characters at most): the record"},amount:{type:"string",value:"n",description:"how much of it (default: all they owe)"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the credits (the catalog's key)"),a=Nr(e,"it is forgiven"),i=se(e.flags.amount,"amount");if(i===0)throw m("--amount is above 0");await v(e,t,`forgive ${i??"all"} of what ${n} owes of ${s}`);let r={credits:s,reason:a,...i!==void 0?{amount:i}:{}},o=await h(e,t,"POST",`/purchases/customers/${k(n)}/credits/forgive`,r,j());e.out.result(o,()=>e.out.line(`Forgave ${o.forgiven} ${o.credits} ${n} owed; they owe ${o.debt}, ${o.available} available.`))}};function bd(e,t,n){e.out.line(`${t}: ${n.used} of ${n.max} used, ${n.remaining} left, resets ${I(n.resets_at)}.`)}var CO={name:"purchases:customer:limit:set",summary:"set a customer's own maximum of a limit \u2014 above or below the catalog's \u2014 saying why, for a while or until cleared",usage:"<customer id> <limit> <max> --reason <why> [--for 30d | --until <date>]",args:{min:3,max:3},flags:{reason:{type:"string",value:"why",description:"why (500 characters at most): the record"},for:{type:"string",value:"span",description:"how long: 12h, 30d, 2w"},until:{type:"string",value:"date",description:"when it ends (2026-12-31, or a time)"}},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the limit (the catalog's key)"),a=ce(e,2,"the maximum"),i=Number(a);if(!Number.isInteger(i)||i<0)throw m(`the maximum is a whole number: ${a}`);let r=Nr(e,"it is set"),o=md(e);await v(e,t,`set ${n}'s ${s} to ${i}${o?` until ${I(o)}`:""}`);let l={max:i,reason:r,...o?{expires_at:o}:{}},d=await h(e,t,"PUT",`/purchases/customers/${k(n)}/limits/${k(s)}`,l,j());e.out.result(d,()=>bd(e,`Set ${n}'s ${d.limit} to ${d.max}`,d))}},jO={name:"purchases:customer:limit:clear",summary:"clear a customer's own maximum of a limit: the catalog's counts again",usage:"<customer id> <limit>",args:{min:2,max:2},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the limit (the catalog's key)");await v(e,t,`clear ${n}'s own ${s}`);let a=await h(e,t,"DELETE",`/purchases/customers/${k(n)}/limits/${k(s)}`,void 0,j());e.out.result(a,()=>bd(e,`Cleared ${n}'s own ${a.limit}; the catalog's ${a.max} counts`,a))}},OO={name:"purchases:customer:limit:give",summary:"give a customer more of a limit for its current period, saying why \u2014 a gift on top of their maximum",usage:"<customer id> <limit> <amount> --reason <why> [--source support|promotion]",args:{min:3,max:3},flags:{reason:{type:"string",value:"why",description:"why it is given (500 characters at most): the record"},...jb},scope:"environment",async run(e){let t=await b(e),n=ce(e,0,"the customer's id (the user's id)"),s=ce(e,1,"the limit (the catalog's key)"),a=Mb(e,2,"the amount"),i=Nr(e,"it is given"),r=Ob(e);await v(e,t,`give ${n} ${a} more ${s} this period`);let o={amount:a,source:r,reason:i},l=await h(e,t,"POST",`/purchases/customers/${k(n)}/limits/${k(s)}/grants`,o,j());e.out.result(l,()=>bd(e,`Gave ${n} ${a} more ${l.limit}`,l))}},MO={name:"purchases:stripe",summary:"the environment's Stripe account: whether one is connected, what Stripe last said of its key",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/purchases/stripe");e.out.result(n,()=>{if(!n.connected){e.out.line("No Stripe account is connected."),e.out.line("Connect one with one click: palbase purchases:stripe:install (Palbase's Stripe App), or with a restricted key: palbase purchases:stripe:grant");return}let s=n.via==="app"?"through Palbase's Stripe App":`key ${n.key_hint??"?"}`;e.out.line(`Stripe account ${n.account_id??"?"} (${n.livemode?"live":"test"} mode), ${s}`),e.out.line(`status: ${n.status??"not checked"}${n.checked_at?` (checked ${I(n.checked_at)})`:""}`),n.connected_at&&e.out.line(`connected ${I(n.connected_at)}${n.connected_by?` by ${n.connected_by}`:""}`),n.action&&e.out.line(`To do: ${n.action}`)})}},LO={name:"purchases:stripe:grant",summary:"a link that connects a Stripe restricted key: the key goes from the browser straight to the environment, never through Studio or this computer",details:["The link works once, for 30 minutes; a project holds 10 open at most."],scope:"environment",async run(e){let t=await b(e);await v(e,t,"make a link that connects a Stripe account");let n=await h(e,t,"POST","/purchases/stripe/grants",void 0,j());rn(e,t,n.connect_url,"the connect link"),e.out.result({connect_url:n.connect_url,expires_at:n.expires_at},()=>{e.out.line(n.connect_url),e.out.note(`Open it in a browser and paste the Stripe restricted key; it works once, until ${I(n.expires_at)}.`)})}},IO={name:"purchases:stripe:install",summary:"a link that connects the environment's Stripe account with one click: Palbase's Stripe App, installed at Stripe \u2014 no key pasted",details:["The link opens Palbase's install page, naming the project; from there Stripe: pick the account, accept what the App may do, and Stripe sends you back to Palbase, which connects it.","It works once, for 30 minutes; --live installs the App in live mode (Stripe test mode otherwise).","A restricted key connects an account too: palbase purchases:stripe:grant."],flags:{live:{type:"boolean",description:"install the App in live mode (test mode otherwise)"}},scope:"environment",async run(e){let t=await b(e),n=e.flags.live===!0;await v(e,t,`make a link that installs Palbase's Stripe App in ${n?"live":"test"} mode`);let s=await h(e,t,"POST","/purchases/stripe/app/grants",{livemode:n},j()),a=Wd(e.cloud),i;try{i=new URL(s.install_url)}catch{i=void 0}if(i?.origin!==a||i.pathname!=="/purchases/stripe/app/install"||!i.hash.startsWith("#grant="))throw new f("install_link_not_palbase",`the install link names ${i?i.origin+i.pathname:s.install_url}, not Palbase's install page at ${a}: it is not printed`);e.out.result({install_url:s.install_url,expires_at:s.expires_at},()=>{e.out.line(s.install_url),e.out.note(`Open it in a browser, pick the Stripe account and accept; it works once, until ${I(s.expires_at)}.`)})}};function DO(e){let t=n=>/^[\w./@:-]+$/.test(n)?n:JSON.stringify(n);return`${e.global.project!==void 0?` -P ${t(e.global.project)}`:""}${e.global.env!==void 0?` -E ${t(e.global.env)}`:""}`}async function wd(e,t){let n=(await et(e,t.project_id)).filter(o=>o.deleted_at===null),s=n.filter(o=>o.platform==="ios"),a=e.flags.app;if(a!==void 0){let o=ht(n,a);if(o.platform!=="ios")throw m(`${o.slug} is a ${o.platform} app: a StoreKit configuration is an iOS app's`,`the project's iOS apps: ${s.map(l=>l.slug).join(", ")||"none"}`);return o}let i=(await sr(e.io.cwd))?.apps.filter(o=>o.platform==="ios").map(o=>o.id)??[],r=s.filter(o=>i.includes(o.id));if(r.length===1)return r[0];if(s.length===1)return s[0];throw s.length===0?new f("no_ios_app",`${t.project_name} has no iOS app`,{hint:"register it: palbase apps:create ios <name> --team-id <team id>"}):m("name the iOS app the file is for: --app <app>",`the project's iOS apps: ${s.map(o=>o.slug).join(", ")}`)}function xO(e,t){let n=aO(e.io.cwd,za(e.io.cwd,t)).split(iO).join("/");if(!n.startsWith(`${z}/`)||n.split("/").includes(".."))throw m(`--out ${t}: the file goes under ${z}/, as the CLI writes nothing else in the checkout`,`palbase purchases:storekit --out ${z}/<name>.storekit`);if(!n.endsWith(".storekit"))throw m(`--out ${t}: Xcode takes a StoreKit configuration by its .storekit extension`);return n}function NO(e){let t=new Map;for(let n of e.products)for(let s of n.channels.ios?.plans??[])t.set(s.price.currency.toUpperCase(),(t.get(s.price.currency.toUpperCase())??0)+1);return[...t].sort(([n,s],[a,i])=>i-s||(n<a?-1:1))[0]?.[0]}var qO={name:"purchases:storekit",summary:"an Xcode StoreKit configuration of the environment's iOS products, under palbase/: a build Xcode runs buys them with no App Store",usage:"[--app <app>] [--out <palbase/\u2026.storekit>] [--storefront <code>]",details:["Each plan is the App Store product its store id names, with its text in every language the catalog has and its price; a subscription is in its group, at its tier's level, with its introductory offer and the catalog's offers on it as promotional offers (a grant has no store).","Prices are in the storefront's currency: the catalog's price, or the amount set by hand in that currency. The storefront is --storefront, else the one the file has, else the one the catalog's currency is the App Store's own price in (USD: USA, EUR: Germany, TRY: Turkey \u2026), else USA.","Made again, the file is made whole from the catalog \u2014 the same catalog, the same file \u2014 keeping the test settings Xcode's Editor menu wrote in it. Nothing outside palbase/ is written: Xcode is pointed at the file in the scheme."],flags:{app:{type:"string",value:"app",description:"the iOS app it is for (default: the one this checkout syncs, or the project's only one)"},out:{type:"string",value:"palbase/\u2026.storekit",description:"where under palbase/ (default: palbase/environments/<env>/<app>.storekit)"},storefront:{type:"string",value:"code",description:"the App Store storefront prices are in, by its three letters (USA, DEU, TUR, GBR, JPN \u2026)"}},scope:"environment",async run(e){let t=await b(e),n=e.flags.storefront?.trim().toUpperCase();if(n!==void 0&&!Is.has(n))throw m(`--storefront ${e.flags.storefront}: not an App Store storefront`,"a storefront is three letters, its country's ISO 3166-1 code: USA, DEU, TUR, GBR, JPN \u2026");let s=await wd(e,t);if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(t.env_slug))throw new f("unexpected_answer",`the environment's slug ${JSON.stringify(t.env_slug)} is not one`);if(!/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/.test(s.slug))throw new f("unexpected_answer",`the project answered app ${JSON.stringify(s.slug)}, which is not one`);let a=e.flags.out!==void 0?xO(e,e.flags.out):`${z}/environments/${t.env_slug}/${s.slug}.storekit`,i=await h(e,t,"GET","/purchases/offerings?channel=ios");if(!i.products.some(R=>(R.channels.ios?.plans.length??0)>0))throw new f("nothing_on_ios",`${t.env_name}'s catalog sells nothing on iOS yet: there is no product for Xcode to sell`,{hint:"give a product channels.ios in the catalog (palbase purchases:catalog:get --out catalog.json, then purchases:catalog:put)"});let r=await ge(e.io.cwd,a),o,l;try{o=await pd(r,"utf8");let R=JSON.parse(o);typeof R.settings=="object"&&R.settings!==null&&!Array.isArray(R.settings)&&(l=R.settings)}catch{}let d=typeof l?._storefront=="string"&&Is.has(l._storefront)?l._storefront:void 0,p=NO(i);n??=d??(p!==void 0?jf(p):void 0)??"USA";let{catalog:u}=await h(e,t,"GET","/purchases/catalog"),g=Lf(i,{seed:`${t.env_ref}/${s.id}`,storefront:n,settings:l,offers:u.offers??[]}),y=o!==g.text;y&&(await tO(sO(r),{recursive:!0}),await ge(e.io.cwd,a),await ud(r,g.text));for(let R of g.notes)e.out.warn(R);let w=t.env_slug===Be,_=DO(e),P=[`In Xcode: Product \u203A Scheme \u203A Edit Scheme\u2026 \u203A Run \u203A Options \u203A StoreKit Configuration \u2014 choose ${a} (not listed yet? add it from that menu).`,w?`${t.project_name} \u203A ${t.env_name} is the project's production environment: it never takes a purchase Xcode signs. Make the file for a test environment (-E <env>) to see a purchase reach the server.`:`Then select the file in Xcode, choose Editor \u203A Save Public Certificate, and let ${t.env_name} take the purchases Xcode signs: palbase purchases:apple:xcode-certificate <the saved .cer file>${_}`,`Run the app and buy: Xcode sells the products itself, nothing goes to the App Store. After the catalog changes: palbase purchases:storekit${e.flags.app!==void 0?` --app ${s.slug}`:""}${e.flags.out!==void 0?` --out ${a}`:""}${_}`],S=g.counts,$=[S.subscriptions>0?`${S.subscriptions} subscription${S.subscriptions===1?"":"s"} in ${S.groups} group${S.groups===1?"":"s"}`:"",S.products>0?`${S.products} in-app purchase${S.products===1?"":"s"}`:"",S.non_renewing>0?`${S.non_renewing} non-renewing subscription${S.non_renewing===1?"":"s"}`:""].filter(R=>R!=="").join(", ");e.out.result({file:a,app:s.slug,environment:t.env_slug,revision:i.revision,storefront:g.storefront,currency:g.currency,locale:g.locale,...S,written:y,next:P},()=>{e.out.line(`${y?"Wrote":"Up to date:"} ${a} \u2014 ${$}, priced in ${g.currency} (storefront ${g.storefront}), catalog revision ${i.revision}.`),e.out.line(""),P.forEach((R,T)=>e.out.line(`${T+1}. ${R}`))})}},KO=/^SubscriptionKey_([A-Z0-9]{10})\.p8$/,GO=/^AuthKey_[A-Z0-9]{10}\.p8$/,UO=8192,BO=16384;function Lb(e,t){if(!t.connected)e.out.line("No In-App Purchase key is connected: the App Store's purchases are not taken."),e.out.line("Connect one: palbase purchases:apple:connect --key-file SubscriptionKey_<KEYID>.p8");else{e.out.line(`In-App Purchase key ${t.key_id??"?"} (issuer ${t.issuer_id??"?"}) for ${t.bundle_id??"?"}${t.app_apple_id?`, Apple ID ${t.app_apple_id}`:", its Apple ID not known yet"}`),e.out.line(`status: ${t.status??"not checked"}${t.checked_at?` (checked ${I(t.checked_at)})`:""}`);let s=t.notifications;s&&e.out.line(`notifications: ${s.url} \u2014 ${s.bound?`bound ${I(s.bound_at)}`:`not bound${s.error?`: ${s.error}`:""}`}; Apple's TEST notification ${s.verified_at?`arrived ${I(s.verified_at)}`:"not arrived yet"}`),t.connected_at&&e.out.line(`connected ${I(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.action&&e.out.line(`To do: ${t.action}`)}let n=t.xcode_certificate;e.out.line(n?`Xcode's StoreKit Testing purchases: taken, signed with ${n.subject} until ${I(n.not_after)} (sha256 ${n.fingerprint.slice(0,16)}\u2026)`:"Xcode's StoreKit Testing purchases: not taken (palbase purchases:apple:xcode-certificate <file> takes them)")}var zO={name:"purchases:apple",summary:"the environment's App Store connection: the In-App Purchase key, where the App Store reports, whether Xcode's purchases are taken",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/purchases/apple");e.out.result(n,()=>Lb(e,n))}};function WO(e,t){let n=e.environments.find(s=>s.ref===t.env_ref)?.ios?.bundle;if(n===void 0)throw new f("app_not_in_environment",`${e.slug} is not set in ${t.env_name}: the key is connected for the app's bundle there`,{hint:`palbase apps:env:set ${e.slug} -E ${t.env_slug} --bundle <bundle id>`});return n}var FO={name:"purchases:apple:connect",summary:"connect an In-App Purchase key for an iOS app: it goes from this computer straight to the environment, never through Studio",usage:"--key-file <SubscriptionKey_KEYID.p8> [--key-id <KEYID>] [--issuer-id <issuer id>] [--app <app>] | --print-link",details:["App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes the key (SubscriptionKey_<KEYID>.p8) and shows the Issuer ID \u2014 the team's, the same as its App Store Connect API keys': left out, the one stores:apple connected is taken.","The environment verifies the app's purchases and reads its subscriptions with it, and has the stores module point the App Store's notifications at the environment.","Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead."],flags:{"key-file":{type:"string",value:"SubscriptionKey_KEYID.p8",description:"the .p8 file Apple gave when the key was made"},"key-id":{type:"string",value:"KEYID",description:"the key's id (default: from the file's name)"},"issuer-id":{type:"string",value:"issuer id",description:"the team's issuer id (default: the connected App Store Connect key's)"},app:{type:"string",value:"app",description:"the iOS app (default: the one this checkout syncs, or the project's only one)"},"print-link":{type:"boolean",description:"only print a link that takes the key in a browser (valid 30 minutes, once)"}},scope:"environment",async run(e){let t=await b(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw m("--print-link or --key-file, not both");await v(e,t,"make a link that connects an In-App Purchase key");let y=await h(e,t,"POST","/purchases/apple/grants",void 0,j());rn(e,t,y.connect_url,"the connect link"),e.out.result({connect_url:y.connect_url,expires_at:y.expires_at},()=>{e.out.line(y.connect_url),e.out.note(`Open it in a browser, pick the app and the .p8 file; it works once, until ${I(y.expires_at)}.`)});return}let n=e.flags["key-file"];if(n===void 0)throw m("--key-file names the In-App Purchase key's .p8 file","or --print-link for a link a browser opens");let s=nO(n),a=e.flags["key-id"];if(a===void 0){if(GO.test(s))throw m(`${s} is an App Store Connect API key (palbase stores:apple:connect takes it): this is the In-App Purchase key, SubscriptionKey_<KEYID>.p8`,"App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes one");if(a=KO.exec(s)?.[1],a===void 0)throw m(`--key-id is needed: ${s} is not named SubscriptionKey_<KEYID>.p8`)}let i;try{let y=za(e.io.cwd,n);if((await $b(y)).size>UO)throw new f("not_a_key",`${n} is larger than a .p8 key`);i=await pd(y,"utf8")}catch(y){throw y instanceof f?y:new f("unreadable_file",`${n} could not be read: ${y instanceof Error?y.message:String(y)}`)}if(!i.includes("-----BEGIN PRIVATE KEY-----"))throw new f("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);let r=e.flags["issuer-id"]?.trim();if(r===void 0||r===""){let y=await h(e,t,"GET","/stores/apple");if(r=y.connected?y.issuer_id:void 0,r===void 0)throw m("--issuer-id is the team's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations): no App Store Connect key is connected to read it from")}let o=await wd(e,t),l=WO(o,t);await v(e,t,`connect the In-App Purchase key ${a} for ${l}`);let d=await h(e,t,"POST","/purchases/apple/grants",void 0,j()),p=rn(e,t,d.connect_url,"the connect link"),u={grant:d.grant,issuer_id:r,key_id:a,private_key:i,bundle_id:l},g=await Ia(e,`${p}/purchases/apple/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(u)},"connecting the In-App Purchase key");e.out.result(g,()=>{e.out.line(`Connected key ${a} for ${l} to ${q(t)}.`),Lb(e,g),e.out.line("Check what else the App Store needs: palbase purchases:doctor")})}},JO={name:"purchases:apple:disconnect",summary:"forget the environment's In-App Purchase key: the App Store's purchases and notifications are refused from then on (what was bought stays)",scope:"environment",async run(e){let t=await b(e);await v(e,t,"disconnect the In-App Purchase key"),await h(e,t,"DELETE","/purchases/apple",void 0,j()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the In-App Purchase key from ${q(t)}.`))}};function Ib(e,t){if(t.service_account===void 0){e.out.line("The project has no Google Play connection: Palbase reads the app's purchases as the account your Play Console invites."),e.out.line("Connect it first: palbase stores:google:connect");return}if(e.out.line(`Play connection: ${t.service_account} (${t.play_status??"not checked"})`),!t.connected){e.out.line("Google Play purchases are not connected: palbase purchases:google:connect --app <android app>");return}e.out.line(`app: ${t.package_name??"?"}`),e.out.line(`topic: ${t.topic??"?"} (Play Console \u203A Monetize with Play \u203A Monetization setup \u203A Real-time developer notifications)`),e.out.line(`notifications: ${t.notification_url??"?"} \u2014 Play's test notification ${t.verified_at?`arrived ${I(t.verified_at)}`:"not arrived yet"}`),t.connected_at&&e.out.line(`connected ${I(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.action&&e.out.line(`To do: ${t.action}`)}var VO={name:"purchases:google",summary:"the environment's Google Play purchases: the app they are made in, the topic its Play Console names, whether Play's test notification arrived",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/purchases/google");e.out.result(n,()=>Ib(e,n))}};async function HO(e,t){let n=(await et(e,t.project_id)).filter(o=>o.deleted_at===null),s=n.filter(o=>o.platform==="android"),a=e.flags.app;if(a!==void 0){let o=ht(n,a);if(o.platform!=="android")throw m(`${o.slug} is a ${o.platform} app: Google Play purchases are an Android app's`,`the project's Android apps: ${s.map(l=>l.slug).join(", ")||"none"}`);return o}let i=(await sr(e.io.cwd))?.apps.filter(o=>o.platform==="android").map(o=>o.id)??[],r=s.filter(o=>i.includes(o.id));if(r.length===1)return r[0];if(s.length===1)return s[0];throw s.length===0?new f("no_android_app",`${t.project_name} has no Android app`,{hint:"register it: palbase apps:create android <name>"}):m("name the Android app: --app <app>",`the project's Android apps: ${s.map(o=>o.slug).join(", ")}`)}var YO={name:"purchases:google:connect",summary:"sell on Google Play in one of the project's Android apps: Palbase makes the app's notifications topic and has it pushed to this environment",usage:"[--app <app>]",details:["The project's Play connection (palbase stores:google:connect) is the account Palbase reads and acknowledges the app's purchases as: its Play Console invites it with View financial data and Manage orders and subscriptions.","Then, in Play Console: Monetize with Play \u203A Monetization setup \u203A Real-time developer notifications, the topic this prints, Save, and Send test notification \u2014 palbase purchases:google says when it arrived."],flags:{app:{type:"string",value:"app",description:"the Android app (default: the one this checkout syncs, or the project's only one)"}},scope:"environment",async run(e){let t=await b(e),n=await HO(e,t);await v(e,t,`connect Google Play purchases for ${n.slug}`);let s={app_id:n.id},a=await h(e,t,"POST","/purchases/google",s,j());e.out.result(a,()=>{e.out.line(`Google Play purchases connected for ${a.package_name??n.slug} in ${q(t)}.`),Ib(e,a)})}},XO={name:"purchases:google:disconnect",summary:"stop taking the environment's Google Play purchases: Play's notifications stop reaching it, and the app's reports are refused (what was bought stays)",scope:"environment",async run(e){let t=await b(e);await v(e,t,"disconnect Google Play purchases"),await h(e,t,"DELETE","/purchases/google",void 0,j()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected Google Play purchases from ${q(t)}.`))}};async function ZO(e,t){let n=(await et(e,t.project_id)).find(s=>s.deleted_at===null&&s.environments.some(a=>a.ref===t.env_ref));return n===void 0?t.env_slug===Be:(await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`)).environment.production}async function QO(e,t){let n;try{let i=za(t,e);if((await $b(i)).size>BO)throw new f("not_a_certificate",`${e} is larger than Xcode's certificate`);n=await pd(i)}catch(i){throw i instanceof f?i:new f("unreadable_file",`${e} could not be read: ${i instanceof Error?i.message:String(i)}`)}let s;try{s=new eO(n)}catch{throw new f("not_a_certificate",`${e} is not a certificate`,{hint:"the one Xcode saves: select the .storekit file, then Editor \u203A Save Public Certificate"})}let a=n.toString("latin1");return{certificate:a.includes("-----BEGIN CERTIFICATE-----")?a.trim():n.toString("base64"),x509:s}}var e1={name:"purchases:apple:xcode-certificate",summary:"take the purchases Xcode's StoreKit Testing signs with this certificate, as test purchases \u2014 never on production; --remove stops",usage:"<certificate.cer> | --remove",details:["Xcode saves the certificate: select the .storekit file (palbase purchases:storekit makes it), then Editor \u203A Save Public Certificate.","From then on what the app reports signed with it \u2014 Xcode's own purchases, on a developer's Mac \u2014 is a test purchase of the environment, with or without an In-App Purchase key; nothing of it is asked of Apple. Set again, it replaces the one before.","The production environment takes only what the App Store signed: it is refused there."],args:{min:0,max:1},flags:{remove:{type:"boolean",description:"stop taking Xcode's purchases (those taken stay)"}},scope:"environment",async run(e){let t=await b(e),n=e.flags.remove===!0,s=e.args[0];if(n===(s!==void 0))throw m("name the certificate file, or --remove","palbase purchases:apple:xcode-certificate StoreKitTestCertificate.cer");if(n){await v(e,t,"stop taking Xcode's StoreKit Testing purchases"),await h(e,t,"DELETE","/purchases/apple/xcode-certificate",void 0,j()),e.out.result({removed:!0},()=>e.out.line(`${q(t)} no longer takes Xcode's purchases; those it took stay.`));return}let a=await QO(s,e.io.cwd);if(await ZO(e,t))throw new f("production_environment",`${t.project_name} \u203A ${t.env_name} is the project's production environment: it takes only purchases the App Store signed, never Xcode's`,{hint:"set the certificate on a test environment: palbase purchases:apple:xcode-certificate <file> -E <env>"});await v(e,t,`take the purchases Xcode signs with ${a.x509.subject.replaceAll(`
|
|
693
693
|
`,", ")}`);let i=await h(e,t,"PUT","/purchases/apple/xcode-certificate",{certificate:a.certificate},j());e.out.result(i,()=>{e.out.line(`${q(t)} takes the purchases Xcode signs with ${i.subject}, until ${I(i.not_after)} (sha256 ${i.fingerprint.slice(0,16)}\u2026).`),e.out.line("Run the app from Xcode and buy: each purchase is a test purchase of this environment.")})}},t1=new Set(["sandbox_access","xcode_certificate"]),n1={name:"purchases:doctor",summary:"whether the environment sells in the App Store now, check by check \u2014 the App Store Connect side and the purchases side \u2014 with the fix of each that fails",usage:"[--app <app>]",details:["Apple is asked now: the keys, the agreements, the app's App Store Connect record, where the App Store reports, the sandbox testers, each product's state, the catalog's last confirmation; and whether Xcode's purchases are taken.","It exits 1 while a check that is not informational fails: run it again until every line is \u2713."],flags:{app:{type:"string",value:"app",description:"the iOS app (default: the environment's one linked app)"}},scope:"environment",async run(e){let t=await b(e),n=e.flags.app!==void 0?await wd(e,t):void 0,s=n?`?${new URLSearchParams({app:n.id})}`:"",a=u=>u.then(g=>({value:g}),g=>({error:g})),[i,r]=await Promise.all([a(h(e,t,"GET",`/stores/apple/readiness${s}`)),a(h(e,t,"GET","/purchases/apple/readiness"))]),o=(u,g)=>{if(!(g instanceof C))throw g;return{module:u,id:`${u}_unanswered`,ok:!1,informational:!1,message:`The ${u} module could not answer: ${g.message}`,fix:g.status===503||g.status===502?"palbase purchases:doctor (again, in a moment)":""}},l=[..."value"in i?i.value.checks.map(u=>({module:"stores",id:u.id,ok:u.ok,informational:!1,message:u.message,fix:u.fix})):[o("stores",i.error)],..."value"in r?r.value.checks.map(u=>({module:"purchases",id:u.id,ok:u.ok,informational:t1.has(u.id),message:u.message,fix:u.fix})):[o("purchases",r.error)]],d=l.filter(u=>!u.ok&&!u.informational),p=d.length===0;return e.out.result({ready:p,checks:l,stores:"value"in i?i.value:null,purchases:"value"in r?r.value:null},()=>{let u=(g,y)=>{e.out.line(y);for(let w of l.filter(_=>_.module===g))e.out.line(` ${w.informational?"i":w.ok?"\u2713":"\u2717"} ${w.message}`),(!w.ok||w.informational)&&w.fix&&e.out.line(` \u2192 ${w.fix}`)};u("stores","App Store Connect (stores)"),u("purchases","In-App Purchases (purchases)"),e.out.line(""),e.out.line(p?`Ready: ${q(t)} sells in the App Store.`:`${d.length} of ${l.filter(g=>!g.informational).length} checks fail: fix each, then run palbase purchases:doctor again.`)}),p?void 0:1}},Db=[rO,oO,lO,pO,uO,cO,hO,mO,gO,_O,yO,kO,SO,bO,wO,$O,PO,AO,RO,TO,EO,CO,jO,OO,MO,LO,IO,qO,zO,FO,JO,e1,VO,YO,XO,n1];import{readdir as s1,readFile as a1,stat as i1}from"node:fs/promises";import{join as r1,resolve as o1}from"node:path";var l1=1e3,xb=900*1024,d1="palbase-argon2id";function p1(e,t){let n=e.trim();if(n.startsWith("{"))try{let s=JSON.parse(n);if(Array.isArray(s.users)){if(s.hash?.pepper!==void 0)throw m(`${t} holds a pepper: it goes on stdin, not in a file`);let a=s.hash&&typeof s.hash.algorithm=="string"?{algorithm:s.hash.algorithm,...typeof s.hash.recovery_code_prefix=="string"?{recovery_code_prefix:s.hash.recovery_code_prefix}:{}}:void 0;return{users:s.users,hash:a}}}catch(s){if(s instanceof Error&&s.name==="CliError")throw s}return{users:c1(e,t)}}function u1(e){let t=[],n=[],s=0;for(let a of e){let i=Buffer.byteLength(JSON.stringify(a))+1;if(i>xb)throw new f("user_too_large",`the user ${String(a.id??"")} is larger than one call takes (${i} bytes)`);(n.length===l1||s+i>xb)&&(t.push(n),n=[],s=0),n.push(a),s+=i}return n.length>0&&t.push(n),t}function c1(e,t){let n=e.trim();if(n==="")return[];if(n.startsWith("["))try{let s=JSON.parse(n);if(Array.isArray(s)&&s.every(a=>typeof a=="object"&&a!==null&&!Array.isArray(a)))return s}catch{}return n.split(`
|
|
694
|
-
`).flatMap((s,a)=>{if(s.trim()==="")return[];try{let i=JSON.parse(s);if(typeof i!="object"||i===null||Array.isArray(i))throw new Error("not an object");return[i]}catch(i){throw m(`${t}:${a+1} is not a user (one JSON object a line): ${i instanceof Error?i.message:String(i)}`)}})}var Nb={name:"auth:import",summary:"bring users over with what they sign in with: ids, addresses, password hashes, providers, second factors, passkeys, open sessions",usage:"<file|folder> [--pepper-stdin]",details:["A folder holds import bodies, one a file (users-0001.json\u2026, each without its pepper); a file holds one such body, JSON lines (a user each) or one JSON array.","Password and recovery-code hashes keep the source's pepper: give it on stdin with --pepper-stdin \u2014 never on the command line.","Users go 1000 a call. One refused does not stop the others; a user whose id or address the project has is refused, never overwritten."],args:{min:1,max:1},flags:{"pepper-stdin":{type:"boolean",description:"read the source's pepper from stdin"}},scope:"environment",async run(e){let t=await b(e),n=e.args[0],s=o1(e.io.cwd,n),a;try{a=(await i1(s)).isDirectory()?(await s1(s)).filter(u=>/^users-\d+\.json$/.test(u)).sort().map(u=>r1(s,u)):[s]}catch(u){throw m(`${n} could not be read: ${u instanceof Error?u.message:String(u)}`)}let i=[],r;for(let u of a){let g=p1(await a1(u,"utf8"),u);if(i.push(...g.users),g.hash){if(r&&JSON.stringify(r)!==JSON.stringify(g.hash))throw m(`${u} names another hash than the files before it`);r=g.hash}}if(i.length===0)throw m(`${n} holds no user`);let o=i.some(u=>u.password_hash!==void 0||Array.isArray(u.recovery_code_hashes)&&u.recovery_code_hashes.length>0),l;if(o){if(e.flags["pepper-stdin"]!==!0)throw m("these users carry password hashes: give the source's pepper with --pepper-stdin",`palbase auth:import ${n} --pepper-stdin < pepper.txt`);l={...r??{algorithm:d1},pepper:await Me(e,"the source's pepper")}}await v(e,t,`bring ${i.length} user${i.length===1?"":"s"} into ${q(t)}'s auth`);let d={imported:0,errors:[],sessions_imported:0,sessions_skipped:0},p=0;for(let u of u1(i)){let g=await h(e,t,"POST","/admin/users/import",{...l?{hash:l}:{},users:u});d.imported+=g.imported,d.sessions_imported+=g.sessions_imported,d.sessions_skipped+=g.sessions_skipped;for(let y of g.errors??[])d.errors.push({...y,index:y.index+p});p+=u.length,p<i.length&&e.out.note(`${p} of ${i.length}\u2026`)}if(e.out.result(d,()=>{e.out.line(`Imported ${d.imported} of ${i.length} users (${d.sessions_imported} open sessions; ${d.sessions_skipped} ended ones left behind).`);for(let u of d.errors.slice(0,50))e.out.line(` ${u.id??`#${u.index+1}`}: ${u.error} \u2014 ${u.message}`);d.errors.length>50&&e.out.line(` \u2026and ${d.errors.length-50} more (--json lists every one)`)}),d.errors.length>0)throw new f("import_incomplete",`${d.errors.length} user${d.errors.length===1?" was":"s were"} not imported`)}};import{writeFile as Gb}from"node:fs/promises";import{resolve as Ub}from"node:path";var h1=/^usr_[A-Za-z0-9-]{1,64}$/;function on(e){if(!h1.test(e))throw m(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function ns(e,t,n,s){if(e===void 0)return;let a=Number(e);if(!Number.isInteger(a)||a<n||a>s)throw m(`--${t} is a whole number from ${n} to ${s}`);return a}function Bb(e,t){if(e===void 0)return;let n=/^(\d+)([smhd])$/.exec(String(e));if(!n)throw m(`--${t} is a span: 30m, 2h, 7d`);return Number(n[1])*{s:1,m:60,h:3600,d:86400}[n[2]]}var Tt=e=>e?e.slice(0,16).replace("T"," "):"";function vd(e){let t=[e.email_verified===!1?"unverified":"",e.banned?`banned${e.ban_reason?` (${A(e.ban_reason)})`:""}`:"",e.is_test?"test":"",e.has_mfa?"mfa":""].filter(Boolean);return`${A(e.id)} ${A(e.email??e.phone??"")}${t.length?` [${t.join(", ")}]`:""}`}var m1={name:"auth:users:list",summary:"the environment's users: id, email, verified, last sign-in; --banned for the banned alone",usage:"[--banned] [--limit <n>]",flags:{banned:{type:"boolean",description:"only the banned"},limit:{type:"string",value:"n",description:"how many at most (default 100)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,1e4)??100,s=[],a;do{let i=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...e.flags.banned===!0?{banned:"true"}:{}}),r=await h(e,t,"GET",`/admin/users?${i}`);s.push(...r.users),a=r.next_cursor||void 0}while(a&&s.length<n);e.out.result({users:s},()=>{s.length===0?e.out.line("No user yet."):e.out.table(s,[{header:"id",value:i=>i.id},{header:"email",value:i=>A(i.email??i.phone??"")},{header:"verified",value:i=>i.email_verified?"yes":"no"},{header:"last sign-in",value:i=>Tt(i.last_login_at)},{header:"notes",value:i=>[i.banned?"banned":"",i.is_test?"test":"",i.has_mfa?"mfa":""].filter(Boolean).join(", ")}])})}},g1={name:"auth:users:get",summary:"one user: their email and phone, how they sign in, whether verified or banned, their roles",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),[s,a]=await Promise.all([h(e,t,"GET",`/admin/users/${k(n)}`),h(e,t,"GET",`/admin/users/${k(n)}/roles`).then(i=>Array.isArray(i)?i:i.roles??[])]);e.out.result({...s,roles:a},()=>{e.out.line(vd(s)),e.out.line(` signs in with ${s.providers?.length?A(s.providers.join(", ")):"a password"}; made ${A(Tt(s.created_at))}, last ${A(Tt(s.last_login_at))||"never"}`),e.out.line(` roles: ${a.length?a.map(A).join(", "):"none"}`)})}},f1={name:"auth:users:create",summary:"make a user with an email and a password (read from stdin, never the command line)",usage:"<email> [--verified]",args:{min:1,max:1},flags:{verified:{type:"boolean",description:"their email is taken as verified"}},scope:"environment",async run(e){let t=await b(e),n=await Me(e,"the password");await v(e,t,`make the user ${e.args[0]}`);let s=await h(e,t,"POST","/admin/users",{email:e.args[0],password:n,...e.flags.verified===!0?{email_verified:!0}:{}},j());e.out.result(s,()=>e.out.line(`Made ${vd(s)}.`))}};function qr(e,t,n,s,a,i={}){return{name:e,summary:t,usage:"<user-id>"+(i&&"reason"in i?" [--reason <text>]":""),args:{min:1,max:1},flags:i,scope:"environment",async run(r){let o=await b(r),l=on(r.args[0]);await v(r,o,n(l)),await s(r,o,l),r.out.result({user:l,done:e},()=>r.out.line(a(l)))}}}var y1=qr("auth:users:delete","delete a user: their account, sessions and sign-ins go; backends hear user.deleted",e=>`delete the user ${e}`,(e,t,n)=>h(e,t,"DELETE",`/admin/users/${k(n)}`),e=>`Deleted ${e}.`),b1=qr("auth:users:ban","ban a user: their sessions end and they cannot sign in until unbanned",e=>`ban the user ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/ban`,e.flags.reason===void 0?{}:{reason:e.flags.reason}),e=>`Banned ${e}.`,{reason:{type:"string",value:"text",description:"why (kept with the ban)"}}),w1=qr("auth:users:unban","let a banned user sign in again",e=>`unban the user ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/unban`,{}),e=>`${e} may sign in again.`),v1=qr("auth:users:sign-out","end every session of a user's: each device signs in again",e=>`end every session of ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/sessions/revoke-all`,{}),e=>`Every session of ${e} is ended.`),_1=/^[0-9]{6}$/;function qb(e){let t={};e.email!==void 0&&(t.email=String(e.email)),e.password!==void 0&&(t.password=String(e.password)),e.passwordless===!0&&(t.passwordless=!0),e.phone!==void 0&&(t.phone=String(e.phone));for(let[n,s]of[["otp","otp"],["email-otp","email_otp"]])if(e[n]!==void 0){if(!_1.test(String(e[n])))throw m(`--${n} is six digits`);t[s]=String(e[n])}if(e.unverified===!0&&(t.email_verified=!1),e.mfa!==void 0){if(!["totp","sms","email"].includes(String(e.mfa)))throw m("--mfa is totp, sms or email");t.mfa=String(e.mfa)}if(t.passwordless&&t.password!==void 0)throw m("--passwordless makes a user with no password: leave --password out");if(t.otp!==void 0&&t.phone===void 0)throw m("--otp is the fixed code of --phone: give --phone too");if(t.mfa==="sms"&&t.phone===void 0)throw m("--mfa sms is the user's phone: give --phone too");return Object.keys(t).length>0?t:void 0}function k1(e){let t=[`${A(e.user_id)} ${A(e.email??"")} ${e.password?`password ${A(e.password)}`:"no password"}${e.email_verified===!1?" (unverified)":""}`];return e.phone&&t.push(` phone ${A(e.phone)}, its fixed code ${A(e.otp??"")} (texted nothing)`),e.email_otp&&t.push(` email's fixed code ${A(e.email_otp)}`),e.mfa&&t.push(` second factor ${A(e.mfa)}${e.totp_secret?`, its key ${A(e.totp_secret)}`:""}`),e.access_token&&t.push(` access token: ${A(e.access_token)}`),t}var S1=30*6e4,$1=15e3;async function zb(e,t,n,s){let a=e.io.now()+S1,i="";for(;;){try{return await s()}catch(o){if(!(o instanceof C)||o.status!==409||o.code!=="build_in_progress")throw o;i===""&&e.out.note(`${n} is not serving its newest code yet: no user is made until it does \u2014 waiting for its build`)}let r=e.io.now();for(;;){if(e.io.now()>=a)throw new f("timeout",`${n} was not serving its newest build after 30 minutes: no test user was made`,{hint:`palbase machines:builds ${n}`});await e.io.sleep(2e3);let o=await h(e,t,"GET",`/machines/${k(n)}`),{builds:l}=await h(e,t,"GET",`/machines/${k(n)}/builds?limit=1`),d=l[0],p=d!==void 0&&d.running&&(o.running_version===o.version||o.state==="asleep"),u=o.build_requested||!d?"a build of its head is asked for":`build ${A(d.id.slice(0,8))} (${A(d.commit.slice(0,7))}): ${A(d.state)}${p?", serving":d.running?", rolling out":""}`;if(u!==i&&e.out.note(` ${u}`),i=u,d&&!o.build_requested&&(d.state==="failed"||d.state==="cancelled"||d.state==="skipped"))throw new f("build_failed",`${n}'s build ${d.state}${d.reason?`: ${A(d.reason)}`:""} \u2014 no test user was made`,{hint:`palbase machines:builds:log ${d.id} --machine ${n}`});if(d&&!o.build_requested&&d.state==="waiting_config")throw new f("build_waiting_config",`${n}'s build waits for the variables its .env.example names${d.missing_env?.length?`: ${d.missing_env.map(A).join(", ")}`:""} \u2014 no test user was made`,{hint:`palbase machines:env:set <NAME>=<value> --machine ${n}`});if(d?.state==="succeeded"&&p&&!o.build_requested||e.io.now()-r>=$1)break}}}var P1=new Set(["no_template","not_a_test_user","template_failed"]);function A1(e,t,n){if(!e.deleted)return`palbase auth:test-users:delete ${e.user_id}`;let s=`then palbase auth:test-users:create --template ${n} --machine ${t}`,a=(e.template.error??"").split(":")[0];return a==="no_template"?`palbase machines:builds ${t} shows the code that serves \u2014 name a template it has, or push the one you added; ${s}`:a==="template_failed"?`palbase machines:logs ${t} --since 15m shows why it failed; ${s}`:`palbase machines:get ${t}; ${s}`}var R1=/^[a-z][a-z0-9_-]{0,63}$/,Wb={type:"string",value:"name",description:"the machine whose backend has the template (default: the environment's only one)"};function Fb(e){if(typeof e!="string"||!R1.test(e))throw m(`${JSON.stringify(e??"")} is not a template's name: lowercase letters, digits, _ and -, a letter first`,"the backend's @TestUserTemplate({ name }) says it");return e}var Jb=e=>e instanceof C&&e.status===403&&/scope|closed/.test(e.code);async function Vb(e,t){let{machines:n}=await h(e,t,"GET",`/machines?limit=100&environment=${encodeURIComponent(t.env_ref)}`);return n.map(s=>s.name)}async function Hb(e,t){let n=e.flags.machine;if(n!==void 0)return n;let s;try{s=await Vb(e,t)}catch(a){throw Jb(a)?m("this token does not list the machines: name the one whose backend has the template","--machine <name>"):a}if(s.length===1)return s[0];throw s.length===0?new f("no_machine","the environment has no machine: a template is written by a backend running on one",{hint:"palbase machines:create"}):m("which machine? name it",`the environment's machines: ${s.join(", ")} \u2014 --machine <name>`)}function Yb(e){if(e.error)return` template ${A(e.name)} not written: ${A(e.error)}`;let t=Object.entries(e.rows??{}).map(([s,a])=>`${A(s)} ${a}`),n=e.commit?` by commit ${A(e.commit.slice(0,7))}`:"";return` template ${A(e.name)}${e.again?` (written before${n})`:n?` (${n.trim()})`:""}: ${t.length>0?t.join(", "):"no rows"}`}var T1={name:"auth:test-users:list",summary:"the environment's test users (made with its secret key; their tokens say test: true)",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/admin/test-users"),s=Array.isArray(n)?n:n.users;e.out.result({users:s},()=>{s.length===0&&e.out.line("No test user yet: palbase auth:test-users:create makes some.");for(let a of s)e.out.line(vd(a))})}},E1={name:"auth:test-users:create",summary:"make test users for a suite, of the kind a sign-in asks for: their passwords (and a second factor's key), and with --token an access token each, shown this once; --template has the backend write the data a screen needs for each",usage:"[--count <n>] [--token] [--email <address>] [--password <pw> | --passwordless] [--phone <e164> [--otp <code>]] [--email-otp <code>] [--unverified] [--mfa totp|sms|email] [--template <name> [--machine <name>]]",details:["A test user signs in as any user does; its tokens say test: true, so a backend can tell them apart. --token gives each an access token at once, for a test that calls the backend as that user.","Its mail and texts are kept for it, read with palbase auth:test-users:messages <id> \u2014 a sign-in link, a verification or second factor's code: a made address (\u2026@test.invalid) is mailed nothing and a phone texted nothing, while --email with an address of yours gets its mail too.","Of the kind a sign-in asks for: --passwordless (a link, a code or a passkey alone), --unverified (its verification is tested), --phone with its fixed code (--otp, made when left out), --email-otp (a fixed code its email accepts: a reviewer reads no inbox), --mfa (a second factor from the start; totp answers its key once).","--template <name> has the backend write the data of its @TestUserTemplate({ name }) for each user \u2014 its seed for one account, in its own code (palbase/backend), run on the machine --machine names or the environment's only one, for test users alone. Each user is answered with the rows written, by table, and the commit whose code wrote them. A user is made with its template or not at all: one whose template was not written (no_template, not_a_test_user, template_failed, template_timeout, machine_unavailable) is deleted again, why and what to run next said on stderr and in the JSON's error, and the command ends with 1. At most 10 users at once with a template.","Right after a push the machine may still be building or rolling out its new code: the command waits until it serves \u2014 following the build, 30 minutes at most \u2014 and makes the users then, so a template you just added is found. A build that fails makes no user.","palbase auth:test-users:template writes another template for a user made with one; palbase auth:test-users:delete has the template undone before the user is deleted.","A pull request's preview is a machine of the environment's (<name>-pr<number>, as machines:list shows) and signs in the environment's users: --machine backend-pr1 has the preview's code write the template, into the preview's own database (<database>-pr<number>)."],flags:{count:{type:"string",value:"n",description:"how many (default 1, at most 100; 10 with --template)"},token:{type:"boolean",description:"an access token for each, too"},email:{type:"string",value:"address",description:"its address \u2014 one of yours gets its mail too (default: a made \u2026@test.invalid)"},password:{type:"string",value:"pw",description:"its password (default: a made one)"},passwordless:{type:"boolean",description:"no password: it signs in by a link, a code or a passkey"},phone:{type:"string",value:"e164",description:"a verified phone, texted nothing"},otp:{type:"string",value:"code",description:"the phone's fixed code, six digits (default: a made one)"},"email-otp":{type:"string",value:"code",description:"a fixed code its email accepts, six digits"},unverified:{type:"boolean",description:"its email unverified"},mfa:{type:"string",value:"totp|sms|email",description:"a second factor from the start"},template:{type:"string",value:"name",description:"a @TestUserTemplate of the backend's: its data written for each user"},machine:Wb},scope:"environment",async run(e){let t=await b(e);if(e.flags.template===void 0){if(e.flags.machine!==void 0)throw m("--machine names where --template is written: give --template too");let u=ns(e.flags.count,"count",1,100)??1,g=qb(e.flags);if(g&&(g.email!==void 0||g.phone!==void 0)&&u>1)throw m("--email and --phone are one user's: leave --count out");await v(e,t,`make ${u} test user(s)`);let y=g?{users:Array.from({length:u},()=>({...g})),with_tokens:e.flags.token===!0}:{count:u,with_tokens:e.flags.token===!0},w=await h(e,t,"POST","/admin/test-users",y,j());e.out.result(w,()=>{for(let _ of w.users)for(let P of k1(_))e.out.line(P);e.out.line("Shown this once. Their mail and texts: palbase auth:test-users:messages <id>.")});return}if(qb(e.flags)!==void 0)throw m("--template's users are made as its machine makes them: leave the kind flags out");let n=Fb(e.flags.template),s=ns(e.flags.count,"count",1,10)??1,a=await Hb(e,t);await v(e,t,`make ${s} test user(s) with ${a}'s template ${n}`);let i={users:[]},r;for(let u=0;u<s;u++)try{let g=await zb(e,t,a,()=>h(e,t,"POST",`/machines/${k(a)}/test-users`,{count:1,template:n,with_tokens:e.flags.token===!0},j()));i.users.push(...g.users);let y=(g.users[0]?.template?.error??"").split(":")[0];if(u<s-1&&P1.has(y)){r=new f(y,`${y} would be the same for the rest`);break}}catch(g){if(i.users.length===0)throw g;r=g;break}for(let u of i.users)u.template??={name:n,error:"template_failed: the platform said nothing of it"};let o=i.users.filter(u=>u.template.error),l=o.filter(u=>!u.deleted).length,d=r===void 0?"":` \u2014 stopped after ${i.users.length} of ${s}: ${r instanceof Error?r.message:String(r)}`,p=o.length===0&&r===void 0?void 0:{code:o.length>0?(o[0].template.error??"").split(":")[0]||"template_failed":r instanceof f||r instanceof C?r.code:"stopped",message:o.length>0?`${o.length} of ${i.users.length} test user(s) not made with ${a}'s template ${n}${l>0?` (${l} kept: see each)`:": none was kept"}${d}`:`${i.users.length} of ${s} test user(s) made with ${a}'s template ${n}${d}`};if(e.out.result({machine:a,...i,...p?{error:p}:{}},()=>{for(let u of i.users)u.deleted?e.out.line(`${A(u.user_id)} ${A(u.email??"")} deleted again: its template was not written`):(e.out.line(`${A(u.user_id)} ${A(u.email??"")} password ${A(u.password??"")}`),u.access_token&&e.out.line(` access token: ${A(u.access_token)}`)),e.out.line(Yb(u.template));i.users.some(u=>!u.deleted)&&e.out.line("Shown this once.")}),p){e.out.note(`error: ${p.message}`);for(let u of o)e.out.note(` ${u.user_id}: ${u.template.error}${u.deleted?" \u2014 the user was deleted again":""}`),e.out.note(` next: ${A1(u,a,n)}`);return 1}}},C1={name:"auth:test-users:messages",summary:"a test user's mail and texts of the last 7 days, the 50 newest first: to whom, the code and the link each carried",usage:"<user-id> [--channel email|sms]",details:["What a flow sent the user \u2014 a sign-in link, a verification or second factor's code, the project's own mail \u2014 is kept for it: a test reads the code or link here. --json answers each whole: its subject, text, HTML and the variables its template had.","A user who is no test user is refused (not_a_test_user): no one else's mail is kept."],args:{min:1,max:1},flags:{channel:{type:"string",value:"email|sms",description:"only this channel's"}},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=new URLSearchParams;if(e.flags.channel!==void 0){if(e.flags.channel!=="email"&&e.flags.channel!=="sms")throw m("--channel is email or sms");s.set("channel",String(e.flags.channel))}let a=s.toString(),i=await h(e,t,"GET",`/notify/test-users/${k(n)}/messages${a?`?${a}`:""}`);e.out.result(i,()=>{i.messages.length===0&&e.out.line(`Nothing was sent to ${A(n)} in the last 7 days.`);for(let r of i.messages)e.out.line(`${A(r.created_at)} ${A(r.channel)} to ${A(r.to)}${r.template?` ${A(r.template)}`:""}${r.delivered?" (sent too)":""}`),r.subject&&e.out.line(` subject: ${A(r.subject)}`),r.code&&e.out.line(` code: ${A(r.code)}`),r.link&&e.out.line(` link: ${A(r.link)}`)})}},j1={name:"auth:test-users:address",summary:"an address for an app's sign-up to be tested with: the account a sign-up with it opens is a test user, its mail kept for palbase auth:test-users:messages",usage:"",details:["The address (signup-\u2026@test.invalid) is typed into the app's sign-up form: the account it opens is a test user \u2014 its tokens say test: true, it is not counted as an active user, its verification mail is kept, never sent. Used once.","It holds a place of the environment's test-user cap until it is used, or lapses a day after it was made. A sign-up with any other address opens an ordinary user."],scope:"environment",async run(e){let t=await b(e);await v(e,t,"make a sign-up address for a test");let n=await h(e,t,"POST","/admin/test-users/addresses");e.out.result(n,()=>{e.out.line(A(n.email)),e.out.line(`Sign up with it once; unused, it lapses at ${A(n.expires_at)}. Its mail: palbase auth:test-users:messages <the new user's id>.`)})}},O1={name:"auth:test-users:template",summary:"have the backend write a @TestUserTemplate's data for a test user made with a template \u2014 the same one again answers what it wrote then",usage:"<user-id> <template> [--machine <name>]",details:["The user is one auth:test-users:create --template made on that machine: a test user is the machine's that made it. Asked again for a template it wrote, the platform answers what it wrote then, and the backend is not called: a template runs once for a user. The answer names the commit that wrote it \u2014 and when the machine serves other code now, says so: its rows are the older code's, and a new test user has the serving code's data.","Right after a push it waits, as auth:test-users:create does, until the machine serves its newest code."],args:{min:2,max:2},flags:{machine:Wb},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=Fb(e.args[1]),a=await Hb(e,t);await v(e,t,`write ${a}'s template ${s} for the test user ${n}`);let i=await zb(e,t,a,()=>h(e,t,"POST",`/machines/${k(a)}/test-users/${k(n)}/templates/${k(s)}`));if(e.out.result({user:n,machine:a,template:i},()=>e.out.line(`${A(n)}${Yb(i)}`)),i.note&&e.out.note(`note: ${i.note}`),i.error)return 1}},M1={name:"auth:test-users:delete",summary:"delete a test user \u2014 one made with a template has the backend undo it first",usage:"<user-id> [--machine <name>]",details:["A test user made with --template is deleted through the machine that made it: the backend's undo of each template runs first \u2014 a template with none, or one the serving code no longer has, leaves its data to user.deleted (what ownedByUser() owns goes with the user), and a warning says that undo did not run \u2014 and an undo that fails keeps the user, for the delete to be asked again. Any other test user is deleted at once.","A pull request preview's test users (made with --machine <name>-pr<number>) are deleted with the preview: its own database, their data, went with it.","Without --machine the environment's machines are asked which made the user; a token that does not list the machines deletes it at once, its templates left to user.deleted."],args:{min:1,max:1},flags:{machine:{type:"string",value:"name",description:"the machine that made the user with a template (default: whichever did)"}},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]);await v(e,t,`delete the test user ${n}`);let s=e.flags.machine,a=[],i=!1;if(s!==void 0)a=[s];else try{a=await Vb(e,t)}catch(d){if(!Jb(d))throw d;i=!0}let r,o=[];for(let d of a)try{o=(await h(e,t,"DELETE",`/machines/${k(d)}/test-users/${k(n)}`,void 0,{prefer:"return=representation"}))?.undo??[],r=d;break}catch(p){if(p instanceof C&&p.status===404)continue;throw p}if(r===void 0&&s!==void 0)throw new f("not_found",`${n} is no test user ${s} made with a template`,{hint:"leave --machine out: the machine that made it is found, and a test user made without a template is deleted at once"});r===void 0&&await h(e,t,"DELETE",`/admin/test-users/${k(n)}`);let l=o.filter(d=>!d.ran);for(let d of l)e.out.warn(`the undo of template ${d.template} did not run: ${d.note??"the program did not say why"}`);e.out.result({user:n,done:"auth:test-users:delete",...r!==void 0?{machine:r,undo:o}:{}},()=>{i&&e.out.note("this token does not list the machines: no template's undo was asked (user.deleted forgets what the user owned) \u2014 --machine <name> asks it"),r===void 0?e.out.line(`Deleted the test user ${n}.`):l.length===0?e.out.line(`Deleted the test user ${n}: ${r}'s backend undid its templates first.`):e.out.line(`Deleted the test user ${n} through ${r}: the undo of ${l.map(d=>d.template).join(", ")} did not run (see the warning).`)})}},L1={name:"flags:users:get",summary:"every flag as one user gets it now \u2014 their own values, the targeting, the defaults \u2014 as their client reads them",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=await h(e,t,"GET",`/flags/users/${k(n)}`);e.out.result({user:n,values:s.values},()=>{let a=Object.keys(s.values??{});a.length===0&&e.out.line("The environment has no flag yet.");for(let i of a.sort())e.out.line(`${A(i)} = ${JSON.stringify(s.values[i])}`)})}},I1={name:"flags:users:set",summary:"give a user a flag's value of their own, whatever its default and targeting say",usage:"<user-id> <key> <value>",args:{min:3,max:3},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=e.args[1],a=Ct(e.args[2]);await v(e,t,`give ${n} the value ${JSON.stringify(a)} of ${s}`),await h(e,t,"PUT",`/flags/users/${k(n)}/${k(s)}`,{value:a}),e.out.result({user:n,key:s,value:a},()=>e.out.line(`${n} gets ${s} = ${JSON.stringify(a)}.`))}},D1={name:"flags:users:unset",summary:"take a user's own value of a flag away, or every one with no key: the flag decides for them again",usage:"<user-id> [key]",args:{min:1,max:2},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=e.args[1];await v(e,t,s?`take ${n}'s own value of ${s} away`:`take every value of ${n}'s own away`),await h(e,t,"DELETE",s?`/flags/users/${k(n)}/${k(s)}`:`/flags/users/${k(n)}`),e.out.result({user:n,key:s??null},()=>e.out.line(s?`${n} gets ${s} as the flag says.`:`${n} gets every flag as it says.`))}};function x1(e){let t=e===void 0?[]:Array.isArray(e)?e:[String(e)];if(t.length===0)return;let n={};for(let s of t){let a=s.indexOf("=");if(a<=0)throw m(`--var is name=value, not ${JSON.stringify(s)}`);n[s.slice(0,a)]=s.slice(a+1)}return n}var N1={name:"notify:send",summary:"send an email or a text through the environment's providers: written here, or a template with its variables",usage:"<email|sms> <to> [--template <slug> --var name=value\u2026] [--subject <s> --text <t> --html <h>] [--body <text>]",details:["A message is sent as a backend sends it (POST /notify/email, /notify/sms): its delivery is in palbase notify:messages."],args:{min:2,max:2},flags:{template:{type:"string",value:"slug",description:"a template of the environment (notify:templates:list)"},var:{type:"string",value:"name=value",multiple:!0,description:"a template's variable (again for each)"},locale:{type:"string",value:"locale",description:"the template's locale"},subject:{type:"string",value:"text",description:"an email's subject"},text:{type:"string",value:"text",description:"an email's text"},html:{type:"string",value:"html",description:"an email's HTML"},body:{type:"string",value:"text",description:"a text's words"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="email"&&t!=="sms")throw m("notify:send email <to> or notify:send sms <to>");let s=await b(e),a=e.flags,i=x1(a.var),r={to:n,...a.template?{template:a.template}:{},...i?{variables:i}:{},...a.locale?{locale:a.locale}:{}};if(t==="email"){if(!a.template&&(a.subject===void 0||a.text===void 0&&a.html===void 0))throw m("an email is a --template, or a --subject with --text or --html");Object.assign(r,a.subject!==void 0?{subject:a.subject}:{},a.text!==void 0?{text:a.text}:{},a.html!==void 0?{html:a.html}:{})}else{if(!a.template&&a.body===void 0)throw m("a text is a --template or a --body");a.body!==void 0&&(r.body=a.body)}await v(e,s,`send ${t==="email"?"an email":"a text"} to ${n}`);let o=await h(e,s,"POST",`/notify/${t}`,r,j());e.out.result(o,()=>e.out.line(o.skipped?`Not sent to ${A(n)}: the user does not take this category (${A(o.message_id??"")}).`:`Queued to ${A(n)} (${A(o.message_id??"")}): palbase notify:messages says what became of it.`))}},q1={name:"notify:messages",summary:"the messages the environment sent, newest first, and what became of each",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,100)??20,s=await h(e,t,"GET",`/notify/messages?limit=${n}`);e.out.result(s,()=>{s.messages.length===0?e.out.line("Nothing sent yet."):e.out.table(s.messages,[{header:"sent",value:a=>Tt(a.created_at)},{header:"channel",value:a=>a.channel},{header:"to",value:a=>A(a.recipient_hint??"")},{header:"template",value:a=>A(a.template??"")},{header:"status",value:a=>`${a.status}${a.error?` (${A(a.error)})`:""}`},{header:"id",value:a=>a.id}])})}},K1={name:"notify:deliverability",summary:"whether each channel can send now, through which providers, and what to do when it cannot",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/notify/deliverability");e.out.result(n,()=>e.out.table(n.channels,[{header:"channel",value:s=>s.channel},{header:"sends",value:s=>s.ok?"yes":"no"},{header:"via",value:s=>A(s.via.join(" \u2192 "))||"-"},{header:"reports",value:s=>s.reports?"yes":"no"},{header:"why / what to do",value:s=>A([s.because,s.remedy].filter(Boolean).join(" \u2014 "))}]))}},Kb=["android","ios","ios_voip","web"],G1={name:"notify:devices:list",summary:"the devices a push reaches, newest first: each user's, and the installations no one is signed in on \u2014 never a token",usage:"[--user <id>] [--platform android|ios|ios_voip|web] [--limit <n>]",flags:{user:{type:"string",value:"id",description:"one user's devices"},platform:{type:"string",value:"platform",description:"one platform's: android, ios, ios_voip or web"},limit:{type:"string",value:"n",description:"how many (default 50, at most 500)"}},scope:"environment",async run(e){let t=e.flags;if(t.platform!==void 0&&!Kb.includes(String(t.platform)))throw m(`the platforms: ${Kb.join(", ")}`);let n=ns(t.limit,"limit",1,500)??50,s=await b(e),a=new URLSearchParams({limit:String(n)});t.user!==void 0&&a.set("user_id",String(t.user)),t.platform!==void 0&&a.set("platform",String(t.platform));let i=await h(e,s,"GET",`/notify/devices?${a}`);e.out.result(i,()=>{i.devices.length===0?e.out.line("No device: an app registers its device when it asks for push."):e.out.table(i.devices,[{header:"device",value:r=>A(r.device_id)},{header:"platform",value:r=>r.platform},{header:"user",value:r=>A(r.user_id??"(no one signed in)")},{header:"app",value:r=>A(r.app_version??"")},{header:"registered",value:r=>Tt(r.created_at)},{header:"last used",value:r=>Tt(r.last_used_at)}]),i.next_cursor&&e.out.line(`More than ${n}: --limit shows more, --user or --platform fewer.`)})}},U1={name:"notify:devices:delete",summary:"forget a device \u2014 every token it registered, whoever is signed in on it, no one included: a push reaches it no more",usage:"<device_id>",details:["The device's id is the app's own name for it (notify:devices:list). The app registers it again when it next asks for push."],args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await b(e);await v(e,n,`forget the device ${t}: a push reaches it no more`);try{await h(e,n,"DELETE",`/notify/devices/${k(t)}`,void 0,j())}catch(s){throw s instanceof C&&s.status===404?new f("device_not_found",`the environment has no device ${A(t)}`,{hint:"palbase notify:devices:list shows its devices"}):s}e.out.result({deleted:t},()=>e.out.line(`Forgot the device ${A(t)}: a push reaches it no more.`))}},B1={name:"auth:audit",summary:"who did what in the environment's auth, newest first: sign-ins, changes to users and settings, admin actions",usage:"[--event <type>] [--limit <n>]",flags:{event:{type:"string",value:"type",description:"only this kind of event (user.login, admin.user.update\u2026)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,1e4)??50,s=[],a;do{let i=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...typeof e.flags.event=="string"?{event_type:e.flags.event}:{}}),r=await h(e,t,"GET",`/admin/audit?${i}`);s.push(...r.events),a=r.next_cursor||void 0}while(a&&s.length<n);e.out.result({events:s},()=>{s.length===0?e.out.line("Nothing recorded yet."):e.out.table(s,[{header:"when",value:i=>Tt(i.created_at)},{header:"event",value:i=>A(i.event_type)},{header:"result",value:i=>A(i.result??"")},{header:"by",value:i=>A(i.actor?.email??i.actor?.user_id??"")},{header:"from",value:i=>A(i.actor?.ip??"")},{header:"on",value:i=>A([i.target_type,i.target_id].filter(Boolean).join(" "))}])})}},z1={name:"auth:audit:verify",summary:"whether the audit trail is whole: each event chained to the one before, none changed or taken out",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/admin/audit/verify");if(!n.valid){let s=n.broken_at?` at event ${A(n.broken_at.event_id??"?")} (number ${(n.broken_at.index??0)+1} of ${n.total}, oldest first)`:"";throw new f("audit_trail_broken",`the audit trail is broken${s}: an event there was changed or taken out`)}e.out.result(n,()=>e.out.line(`The audit trail is whole: ${n.total} events, each chained to the one before.`))}},W1={name:"auth:audit:export",summary:"the audit trail as a file: JSON lines or CSV, a span of it, one kind of event",usage:"[--format ndjson|csv] [--from <time>] [--to <time>] [--event <type>] [--out <file>]",flags:{format:{type:"string",value:"ndjson|csv",description:"JSON lines (default) or CSV"},from:{type:"string",value:"time",description:"from this time on (RFC 3339: 2026-10-01T00:00:00Z)"},to:{type:"string",value:"time",description:"up to this time"},event:{type:"string",value:"type",description:"only this kind of event"},out:{type:"string",value:"file",description:"write it here instead of to standard output"}},scope:"environment",async run(e){let t=e.flags.format??"ndjson";if(t!=="ndjson"&&t!=="csv")throw m("--format is ndjson or csv");for(let r of["from","to"]){let o=e.flags[r];if(o!==void 0&&Number.isNaN(Date.parse(String(o))))throw m(`--${r} is a time: 2026-10-01T00:00:00Z`)}let n=await b(e),s=new URLSearchParams({format:String(t)});for(let[r,o]of[["from","from"],["to","to"],["event","event_type"]])typeof e.flags[r]=="string"&&s.set(o,e.flags[r]);let a=await h(e,n,"GET",`/admin/audit/export?${s}`),i=typeof a=="string"?a:t==="ndjson"&&Array.isArray(a)?a.map(r=>JSON.stringify(r)).join(`
|
|
694
|
+
`).flatMap((s,a)=>{if(s.trim()==="")return[];try{let i=JSON.parse(s);if(typeof i!="object"||i===null||Array.isArray(i))throw new Error("not an object");return[i]}catch(i){throw m(`${t}:${a+1} is not a user (one JSON object a line): ${i instanceof Error?i.message:String(i)}`)}})}var Nb={name:"auth:import",summary:"bring users over with what they sign in with: ids, addresses, password hashes, providers, second factors, passkeys, open sessions",usage:"<file|folder> [--pepper-stdin]",details:["A folder holds import bodies, one a file (users-0001.json\u2026, each without its pepper); a file holds one such body, JSON lines (a user each) or one JSON array.","Password and recovery-code hashes keep the source's pepper: give it on stdin with --pepper-stdin \u2014 never on the command line.","Users go 1000 a call. One refused does not stop the others; a user whose id or address the project has is refused, never overwritten."],args:{min:1,max:1},flags:{"pepper-stdin":{type:"boolean",description:"read the source's pepper from stdin"}},scope:"environment",async run(e){let t=await b(e),n=e.args[0],s=o1(e.io.cwd,n),a;try{a=(await i1(s)).isDirectory()?(await s1(s)).filter(u=>/^users-\d+\.json$/.test(u)).sort().map(u=>r1(s,u)):[s]}catch(u){throw m(`${n} could not be read: ${u instanceof Error?u.message:String(u)}`)}let i=[],r;for(let u of a){let g=p1(await a1(u,"utf8"),u);if(i.push(...g.users),g.hash){if(r&&JSON.stringify(r)!==JSON.stringify(g.hash))throw m(`${u} names another hash than the files before it`);r=g.hash}}if(i.length===0)throw m(`${n} holds no user`);let o=i.some(u=>u.password_hash!==void 0||Array.isArray(u.recovery_code_hashes)&&u.recovery_code_hashes.length>0),l;if(o){if(e.flags["pepper-stdin"]!==!0)throw m("these users carry password hashes: give the source's pepper with --pepper-stdin",`palbase auth:import ${n} --pepper-stdin < pepper.txt`);l={...r??{algorithm:d1},pepper:await Me(e,"the source's pepper")}}await v(e,t,`bring ${i.length} user${i.length===1?"":"s"} into ${q(t)}'s auth`);let d={imported:0,errors:[],sessions_imported:0,sessions_skipped:0},p=0;for(let u of u1(i)){let g=await h(e,t,"POST","/admin/users/import",{...l?{hash:l}:{},users:u});d.imported+=g.imported,d.sessions_imported+=g.sessions_imported,d.sessions_skipped+=g.sessions_skipped;for(let y of g.errors??[])d.errors.push({...y,index:y.index+p});p+=u.length,p<i.length&&e.out.note(`${p} of ${i.length}\u2026`)}if(e.out.result(d,()=>{e.out.line(`Imported ${d.imported} of ${i.length} users (${d.sessions_imported} open sessions; ${d.sessions_skipped} ended ones left behind).`);for(let u of d.errors.slice(0,50))e.out.line(` ${u.id??`#${u.index+1}`}: ${u.error} \u2014 ${u.message}`);d.errors.length>50&&e.out.line(` \u2026and ${d.errors.length-50} more (--json lists every one)`)}),d.errors.length>0)throw new f("import_incomplete",`${d.errors.length} user${d.errors.length===1?" was":"s were"} not imported`)}};import{writeFile as Gb}from"node:fs/promises";import{resolve as Ub}from"node:path";var h1=/^usr_[A-Za-z0-9-]{1,64}$/;function on(e){if(!h1.test(e))throw m(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function ns(e,t,n,s){if(e===void 0)return;let a=Number(e);if(!Number.isInteger(a)||a<n||a>s)throw m(`--${t} is a whole number from ${n} to ${s}`);return a}function Bb(e,t){if(e===void 0)return;let n=/^(\d+)([smhd])$/.exec(String(e));if(!n)throw m(`--${t} is a span: 30m, 2h, 7d`);return Number(n[1])*{s:1,m:60,h:3600,d:86400}[n[2]]}var Tt=e=>e?e.slice(0,16).replace("T"," "):"";function vd(e){let t=[e.email_verified===!1?"unverified":"",e.banned?`banned${e.ban_reason?` (${A(e.ban_reason)})`:""}`:"",e.is_test?"test":"",e.has_mfa?"mfa":""].filter(Boolean);return`${A(e.id)} ${A(e.email??e.phone??"")}${t.length?` [${t.join(", ")}]`:""}`}var m1={name:"auth:users:list",summary:"the environment's users: id, email, verified, last sign-in; --banned for the banned alone",usage:"[--banned] [--limit <n>]",flags:{banned:{type:"boolean",description:"only the banned"},limit:{type:"string",value:"n",description:"how many at most (default 100)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,1e4)??100,s=[],a;do{let i=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...e.flags.banned===!0?{banned:"true"}:{}}),r=await h(e,t,"GET",`/admin/users?${i}`);s.push(...r.users),a=r.next_cursor||void 0}while(a&&s.length<n);e.out.result({users:s},()=>{s.length===0?e.out.line("No user yet."):e.out.table(s,[{header:"id",value:i=>i.id},{header:"email",value:i=>A(i.email??i.phone??"")},{header:"verified",value:i=>i.email_verified?"yes":"no"},{header:"last sign-in",value:i=>Tt(i.last_login_at)},{header:"notes",value:i=>[i.banned?"banned":"",i.is_test?"test":"",i.has_mfa?"mfa":""].filter(Boolean).join(", ")}])})}},g1={name:"auth:users:get",summary:"one user: their email and phone, how they sign in, whether verified or banned, their roles",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),[s,a]=await Promise.all([h(e,t,"GET",`/admin/users/${k(n)}`),h(e,t,"GET",`/admin/users/${k(n)}/roles`).then(i=>Array.isArray(i)?i:i.roles??[])]);e.out.result({...s,roles:a},()=>{e.out.line(vd(s)),e.out.line(` signs in with ${s.providers?.length?A(s.providers.join(", ")):"a password"}; made ${A(Tt(s.created_at))}, last ${A(Tt(s.last_login_at))||"never"}`),e.out.line(` roles: ${a.length?a.map(A).join(", "):"none"}`)})}},f1={name:"auth:users:create",summary:"make a user with an email and a password (read from stdin, never the command line)",usage:"<email> [--verified]",args:{min:1,max:1},flags:{verified:{type:"boolean",description:"their email is taken as verified"}},scope:"environment",async run(e){let t=await b(e),n=await Me(e,"the password");await v(e,t,`make the user ${e.args[0]}`);let s=await h(e,t,"POST","/admin/users",{email:e.args[0],password:n,...e.flags.verified===!0?{email_verified:!0}:{}},j());e.out.result(s,()=>e.out.line(`Made ${vd(s)}.`))}};function qr(e,t,n,s,a,i={}){return{name:e,summary:t,usage:"<user-id>"+(i&&"reason"in i?" [--reason <text>]":""),args:{min:1,max:1},flags:i,scope:"environment",async run(r){let o=await b(r),l=on(r.args[0]);await v(r,o,n(l)),await s(r,o,l),r.out.result({user:l,done:e},()=>r.out.line(a(l)))}}}var y1=qr("auth:users:delete","delete a user: their account, sessions and sign-ins go; backends hear user.deleted",e=>`delete the user ${e}`,(e,t,n)=>h(e,t,"DELETE",`/admin/users/${k(n)}`),e=>`Deleted ${e}.`),b1=qr("auth:users:ban","ban a user: their sessions end and they cannot sign in until unbanned",e=>`ban the user ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/ban`,e.flags.reason===void 0?{}:{reason:e.flags.reason}),e=>`Banned ${e}.`,{reason:{type:"string",value:"text",description:"why (kept with the ban)"}}),w1=qr("auth:users:unban","let a banned user sign in again",e=>`unban the user ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/unban`,{}),e=>`${e} may sign in again.`),v1=qr("auth:users:sign-out","end every session of a user's: each device signs in again",e=>`end every session of ${e}`,(e,t,n)=>h(e,t,"POST",`/admin/users/${k(n)}/sessions/revoke-all`,{}),e=>`Every session of ${e} is ended.`),_1=/^[0-9]{6}$/;function qb(e){let t={};e.email!==void 0&&(t.email=String(e.email)),e.password!==void 0&&(t.password=String(e.password)),e.passwordless===!0&&(t.passwordless=!0),e.phone!==void 0&&(t.phone=String(e.phone));for(let[n,s]of[["otp","otp"],["email-otp","email_otp"]])if(e[n]!==void 0){if(!_1.test(String(e[n])))throw m(`--${n} is six digits`);t[s]=String(e[n])}if(e.unverified===!0&&(t.email_verified=!1),e.mfa!==void 0){if(!["totp","sms","email"].includes(String(e.mfa)))throw m("--mfa is totp, sms or email");t.mfa=String(e.mfa)}if(t.passwordless&&t.password!==void 0)throw m("--passwordless makes a user with no password: leave --password out");if(t.otp!==void 0&&t.phone===void 0)throw m("--otp is the fixed code of --phone: give --phone too");if(t.mfa==="sms"&&t.phone===void 0)throw m("--mfa sms is the user's phone: give --phone too");return Object.keys(t).length>0?t:void 0}function k1(e){let t=[`${A(e.user_id)} ${A(e.email??"")} ${e.password?`password ${A(e.password)}`:"no password"}${e.email_verified===!1?" (unverified)":""}`];return e.phone&&t.push(` phone ${A(e.phone)}, its fixed code ${A(e.otp??"")} (texted nothing)`),e.email_otp&&t.push(` email's fixed code ${A(e.email_otp)}`),e.mfa&&t.push(` second factor ${A(e.mfa)}${e.totp_secret?`, its key ${A(e.totp_secret)}`:""}`),e.access_token&&t.push(` access token: ${A(e.access_token)}`),t}var S1=30*6e4,$1=15e3;async function zb(e,t,n,s){let a=e.io.now()+S1,i="";for(;;){try{return await s()}catch(o){if(!(o instanceof C)||o.status!==409||o.code!=="build_in_progress")throw o;i===""&&e.out.note(`${n} is not serving its newest code yet: no user is made until it does \u2014 waiting for its build`)}let r=e.io.now();for(;;){if(e.io.now()>=a)throw new f("timeout",`${n} was not serving its newest build after 30 minutes: no test user was made`,{hint:`palbase machines:builds ${n}`});await e.io.sleep(2e3);let o=await h(e,t,"GET",`/machines/${k(n)}`),{builds:l}=await h(e,t,"GET",`/machines/${k(n)}/builds?limit=1`),d=l[0],p=d!==void 0&&d.running&&o.running_version===o.version,u=o.build_requested||!d?"a build of its head is asked for":`build ${A(d.id.slice(0,8))} (${A(d.commit.slice(0,7))}): ${A(d.state)}${p?", serving":d.running?", rolling out":""}`;if(u!==i&&e.out.note(` ${u}`),i=u,d&&!o.build_requested&&(d.state==="failed"||d.state==="cancelled"||d.state==="skipped"))throw new f("build_failed",`${n}'s build ${d.state}${d.reason?`: ${A(d.reason)}`:""} \u2014 no test user was made`,{hint:`palbase machines:builds:log ${d.id} --machine ${n}`});if(d&&!o.build_requested&&d.state==="waiting_config")throw new f("build_waiting_config",`${n}'s build waits for the variables its .env.example names${d.missing_env?.length?`: ${d.missing_env.map(A).join(", ")}`:""} \u2014 no test user was made`,{hint:`palbase machines:env:set <NAME>=<value> --machine ${n}`});if(d?.state==="succeeded"&&p&&!o.build_requested||e.io.now()-r>=$1)break}}}var P1=new Set(["no_template","not_a_test_user","template_failed"]);function A1(e,t,n){if(!e.deleted)return`palbase auth:test-users:delete ${e.user_id}`;let s=`then palbase auth:test-users:create --template ${n} --machine ${t}`,a=(e.template.error??"").split(":")[0];return a==="no_template"?`palbase machines:builds ${t} shows the code that serves \u2014 name a template it has, or push the one you added; ${s}`:a==="template_failed"?`palbase machines:logs ${t} --since 15m shows why it failed; ${s}`:`palbase machines:get ${t}; ${s}`}var R1=/^[a-z][a-z0-9_-]{0,63}$/,Wb={type:"string",value:"name",description:"the machine whose backend has the template (default: the environment's only one)"};function Fb(e){if(typeof e!="string"||!R1.test(e))throw m(`${JSON.stringify(e??"")} is not a template's name: lowercase letters, digits, _ and -, a letter first`,"the backend's @TestUserTemplate({ name }) says it");return e}var Jb=e=>e instanceof C&&e.status===403&&/scope|closed/.test(e.code);async function Vb(e,t){let{machines:n}=await h(e,t,"GET",`/machines?limit=100&environment=${encodeURIComponent(t.env_ref)}`);return n.map(s=>s.name)}async function Hb(e,t){let n=e.flags.machine;if(n!==void 0)return n;let s;try{s=await Vb(e,t)}catch(a){throw Jb(a)?m("this token does not list the machines: name the one whose backend has the template","--machine <name>"):a}if(s.length===1)return s[0];throw s.length===0?new f("no_machine","the environment has no machine: a template is written by a backend running on one",{hint:"palbase machines:create"}):m("which machine? name it",`the environment's machines: ${s.join(", ")} \u2014 --machine <name>`)}function Yb(e){if(e.error)return` template ${A(e.name)} not written: ${A(e.error)}`;let t=Object.entries(e.rows??{}).map(([s,a])=>`${A(s)} ${a}`),n=e.commit?` by commit ${A(e.commit.slice(0,7))}`:"";return` template ${A(e.name)}${e.again?` (written before${n})`:n?` (${n.trim()})`:""}: ${t.length>0?t.join(", "):"no rows"}`}var T1={name:"auth:test-users:list",summary:"the environment's test users (made with its secret key; their tokens say test: true)",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/admin/test-users"),s=Array.isArray(n)?n:n.users;e.out.result({users:s},()=>{s.length===0&&e.out.line("No test user yet: palbase auth:test-users:create makes some.");for(let a of s)e.out.line(vd(a))})}},E1={name:"auth:test-users:create",summary:"make test users for a suite, of the kind a sign-in asks for: their passwords (and a second factor's key), and with --token an access token each, shown this once; --template has the backend write the data a screen needs for each",usage:"[--count <n>] [--token] [--email <address>] [--password <pw> | --passwordless] [--phone <e164> [--otp <code>]] [--email-otp <code>] [--unverified] [--mfa totp|sms|email] [--template <name> [--machine <name>]]",details:["A test user signs in as any user does; its tokens say test: true, so a backend can tell them apart. --token gives each an access token at once, for a test that calls the backend as that user.","Its mail and texts are kept for it, read with palbase auth:test-users:messages <id> \u2014 a sign-in link, a verification or second factor's code: a made address (\u2026@test.invalid) is mailed nothing and a phone texted nothing, while --email with an address of yours gets its mail too.","Of the kind a sign-in asks for: --passwordless (a link, a code or a passkey alone), --unverified (its verification is tested), --phone with its fixed code (--otp, made when left out), --email-otp (a fixed code its email accepts: a reviewer reads no inbox), --mfa (a second factor from the start; totp answers its key once).","--template <name> has the backend write the data of its @TestUserTemplate({ name }) for each user \u2014 its seed for one account, in its own code (palbase/backend), run on the machine --machine names or the environment's only one, for test users alone. Each user is answered with the rows written, by table, and the commit whose code wrote them. A user is made with its template or not at all: one whose template was not written (no_template, not_a_test_user, template_failed, template_timeout, machine_unavailable) is deleted again, why and what to run next said on stderr and in the JSON's error, and the command ends with 1. At most 10 users at once with a template.","Right after a push the machine may still be building or rolling out its new code: the command waits until it serves \u2014 following the build, 30 minutes at most \u2014 and makes the users then, so a template you just added is found. A build that fails makes no user.","palbase auth:test-users:template writes another template for a user made with one; palbase auth:test-users:delete has the template undone before the user is deleted.","A pull request's preview is a machine of the environment's (<name>-pr<number>, as machines:list shows) and signs in the environment's users: --machine backend-pr1 has the preview's code write the template, into the preview's own database (<database>-pr<number>)."],flags:{count:{type:"string",value:"n",description:"how many (default 1, at most 100; 10 with --template)"},token:{type:"boolean",description:"an access token for each, too"},email:{type:"string",value:"address",description:"its address \u2014 one of yours gets its mail too (default: a made \u2026@test.invalid)"},password:{type:"string",value:"pw",description:"its password (default: a made one)"},passwordless:{type:"boolean",description:"no password: it signs in by a link, a code or a passkey"},phone:{type:"string",value:"e164",description:"a verified phone, texted nothing"},otp:{type:"string",value:"code",description:"the phone's fixed code, six digits (default: a made one)"},"email-otp":{type:"string",value:"code",description:"a fixed code its email accepts, six digits"},unverified:{type:"boolean",description:"its email unverified"},mfa:{type:"string",value:"totp|sms|email",description:"a second factor from the start"},template:{type:"string",value:"name",description:"a @TestUserTemplate of the backend's: its data written for each user"},machine:Wb},scope:"environment",async run(e){let t=await b(e);if(e.flags.template===void 0){if(e.flags.machine!==void 0)throw m("--machine names where --template is written: give --template too");let u=ns(e.flags.count,"count",1,100)??1,g=qb(e.flags);if(g&&(g.email!==void 0||g.phone!==void 0)&&u>1)throw m("--email and --phone are one user's: leave --count out");await v(e,t,`make ${u} test user(s)`);let y=g?{users:Array.from({length:u},()=>({...g})),with_tokens:e.flags.token===!0}:{count:u,with_tokens:e.flags.token===!0},w=await h(e,t,"POST","/admin/test-users",y,j());e.out.result(w,()=>{for(let _ of w.users)for(let P of k1(_))e.out.line(P);e.out.line("Shown this once. Their mail and texts: palbase auth:test-users:messages <id>.")});return}if(qb(e.flags)!==void 0)throw m("--template's users are made as its machine makes them: leave the kind flags out");let n=Fb(e.flags.template),s=ns(e.flags.count,"count",1,10)??1,a=await Hb(e,t);await v(e,t,`make ${s} test user(s) with ${a}'s template ${n}`);let i={users:[]},r;for(let u=0;u<s;u++)try{let g=await zb(e,t,a,()=>h(e,t,"POST",`/machines/${k(a)}/test-users`,{count:1,template:n,with_tokens:e.flags.token===!0},j()));i.users.push(...g.users);let y=(g.users[0]?.template?.error??"").split(":")[0];if(u<s-1&&P1.has(y)){r=new f(y,`${y} would be the same for the rest`);break}}catch(g){if(i.users.length===0)throw g;r=g;break}for(let u of i.users)u.template??={name:n,error:"template_failed: the platform said nothing of it"};let o=i.users.filter(u=>u.template.error),l=o.filter(u=>!u.deleted).length,d=r===void 0?"":` \u2014 stopped after ${i.users.length} of ${s}: ${r instanceof Error?r.message:String(r)}`,p=o.length===0&&r===void 0?void 0:{code:o.length>0?(o[0].template.error??"").split(":")[0]||"template_failed":r instanceof f||r instanceof C?r.code:"stopped",message:o.length>0?`${o.length} of ${i.users.length} test user(s) not made with ${a}'s template ${n}${l>0?` (${l} kept: see each)`:": none was kept"}${d}`:`${i.users.length} of ${s} test user(s) made with ${a}'s template ${n}${d}`};if(e.out.result({machine:a,...i,...p?{error:p}:{}},()=>{for(let u of i.users)u.deleted?e.out.line(`${A(u.user_id)} ${A(u.email??"")} deleted again: its template was not written`):(e.out.line(`${A(u.user_id)} ${A(u.email??"")} password ${A(u.password??"")}`),u.access_token&&e.out.line(` access token: ${A(u.access_token)}`)),e.out.line(Yb(u.template));i.users.some(u=>!u.deleted)&&e.out.line("Shown this once.")}),p){e.out.note(`error: ${p.message}`);for(let u of o)e.out.note(` ${u.user_id}: ${u.template.error}${u.deleted?" \u2014 the user was deleted again":""}`),e.out.note(` next: ${A1(u,a,n)}`);return 1}}},C1={name:"auth:test-users:messages",summary:"a test user's mail and texts of the last 7 days, the 50 newest first: to whom, the code and the link each carried",usage:"<user-id> [--channel email|sms]",details:["What a flow sent the user \u2014 a sign-in link, a verification or second factor's code, the project's own mail \u2014 is kept for it: a test reads the code or link here. --json answers each whole: its subject, text, HTML and the variables its template had.","A user who is no test user is refused (not_a_test_user): no one else's mail is kept."],args:{min:1,max:1},flags:{channel:{type:"string",value:"email|sms",description:"only this channel's"}},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=new URLSearchParams;if(e.flags.channel!==void 0){if(e.flags.channel!=="email"&&e.flags.channel!=="sms")throw m("--channel is email or sms");s.set("channel",String(e.flags.channel))}let a=s.toString(),i=await h(e,t,"GET",`/notify/test-users/${k(n)}/messages${a?`?${a}`:""}`);e.out.result(i,()=>{i.messages.length===0&&e.out.line(`Nothing was sent to ${A(n)} in the last 7 days.`);for(let r of i.messages)e.out.line(`${A(r.created_at)} ${A(r.channel)} to ${A(r.to)}${r.template?` ${A(r.template)}`:""}${r.delivered?" (sent too)":""}`),r.subject&&e.out.line(` subject: ${A(r.subject)}`),r.code&&e.out.line(` code: ${A(r.code)}`),r.link&&e.out.line(` link: ${A(r.link)}`)})}},j1={name:"auth:test-users:address",summary:"an address for an app's sign-up to be tested with: the account a sign-up with it opens is a test user, its mail kept for palbase auth:test-users:messages",usage:"",details:["The address (signup-\u2026@test.invalid) is typed into the app's sign-up form: the account it opens is a test user \u2014 its tokens say test: true, it is not counted as an active user, its verification mail is kept, never sent. Used once.","It holds a place of the environment's test-user cap until it is used, or lapses a day after it was made. A sign-up with any other address opens an ordinary user."],scope:"environment",async run(e){let t=await b(e);await v(e,t,"make a sign-up address for a test");let n=await h(e,t,"POST","/admin/test-users/addresses");e.out.result(n,()=>{e.out.line(A(n.email)),e.out.line(`Sign up with it once; unused, it lapses at ${A(n.expires_at)}. Its mail: palbase auth:test-users:messages <the new user's id>.`)})}},O1={name:"auth:test-users:template",summary:"have the backend write a @TestUserTemplate's data for a test user made with a template \u2014 the same one again answers what it wrote then",usage:"<user-id> <template> [--machine <name>]",details:["The user is one auth:test-users:create --template made on that machine: a test user is the machine's that made it. Asked again for a template it wrote, the platform answers what it wrote then, and the backend is not called: a template runs once for a user. The answer names the commit that wrote it \u2014 and when the machine serves other code now, says so: its rows are the older code's, and a new test user has the serving code's data.","Right after a push it waits, as auth:test-users:create does, until the machine serves its newest code."],args:{min:2,max:2},flags:{machine:Wb},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=Fb(e.args[1]),a=await Hb(e,t);await v(e,t,`write ${a}'s template ${s} for the test user ${n}`);let i=await zb(e,t,a,()=>h(e,t,"POST",`/machines/${k(a)}/test-users/${k(n)}/templates/${k(s)}`));if(e.out.result({user:n,machine:a,template:i},()=>e.out.line(`${A(n)}${Yb(i)}`)),i.note&&e.out.note(`note: ${i.note}`),i.error)return 1}},M1={name:"auth:test-users:delete",summary:"delete a test user \u2014 one made with a template has the backend undo it first",usage:"<user-id> [--machine <name>]",details:["A test user made with --template is deleted through the machine that made it: the backend's undo of each template runs first \u2014 a template with none, or one the serving code no longer has, leaves its data to user.deleted (what ownedByUser() owns goes with the user), and a warning says that undo did not run \u2014 and an undo that fails keeps the user, for the delete to be asked again. Any other test user is deleted at once.","A pull request preview's test users (made with --machine <name>-pr<number>) are deleted with the preview: its own database, their data, went with it.","Without --machine the environment's machines are asked which made the user; a token that does not list the machines deletes it at once, its templates left to user.deleted."],args:{min:1,max:1},flags:{machine:{type:"string",value:"name",description:"the machine that made the user with a template (default: whichever did)"}},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]);await v(e,t,`delete the test user ${n}`);let s=e.flags.machine,a=[],i=!1;if(s!==void 0)a=[s];else try{a=await Vb(e,t)}catch(d){if(!Jb(d))throw d;i=!0}let r,o=[];for(let d of a)try{o=(await h(e,t,"DELETE",`/machines/${k(d)}/test-users/${k(n)}`,void 0,{prefer:"return=representation"}))?.undo??[],r=d;break}catch(p){if(p instanceof C&&p.status===404)continue;throw p}if(r===void 0&&s!==void 0)throw new f("not_found",`${n} is no test user ${s} made with a template`,{hint:"leave --machine out: the machine that made it is found, and a test user made without a template is deleted at once"});r===void 0&&await h(e,t,"DELETE",`/admin/test-users/${k(n)}`);let l=o.filter(d=>!d.ran);for(let d of l)e.out.warn(`the undo of template ${d.template} did not run: ${d.note??"the program did not say why"}`);e.out.result({user:n,done:"auth:test-users:delete",...r!==void 0?{machine:r,undo:o}:{}},()=>{i&&e.out.note("this token does not list the machines: no template's undo was asked (user.deleted forgets what the user owned) \u2014 --machine <name> asks it"),r===void 0?e.out.line(`Deleted the test user ${n}.`):l.length===0?e.out.line(`Deleted the test user ${n}: ${r}'s backend undid its templates first.`):e.out.line(`Deleted the test user ${n} through ${r}: the undo of ${l.map(d=>d.template).join(", ")} did not run (see the warning).`)})}},L1={name:"flags:users:get",summary:"every flag as one user gets it now \u2014 their own values, the targeting, the defaults \u2014 as their client reads them",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=await h(e,t,"GET",`/flags/users/${k(n)}`);e.out.result({user:n,values:s.values},()=>{let a=Object.keys(s.values??{});a.length===0&&e.out.line("The environment has no flag yet.");for(let i of a.sort())e.out.line(`${A(i)} = ${JSON.stringify(s.values[i])}`)})}},I1={name:"flags:users:set",summary:"give a user a flag's value of their own, whatever its default and targeting say",usage:"<user-id> <key> <value>",args:{min:3,max:3},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=e.args[1],a=Ct(e.args[2]);await v(e,t,`give ${n} the value ${JSON.stringify(a)} of ${s}`),await h(e,t,"PUT",`/flags/users/${k(n)}/${k(s)}`,{value:a}),e.out.result({user:n,key:s,value:a},()=>e.out.line(`${n} gets ${s} = ${JSON.stringify(a)}.`))}},D1={name:"flags:users:unset",summary:"take a user's own value of a flag away, or every one with no key: the flag decides for them again",usage:"<user-id> [key]",args:{min:1,max:2},scope:"environment",async run(e){let t=await b(e),n=on(e.args[0]),s=e.args[1];await v(e,t,s?`take ${n}'s own value of ${s} away`:`take every value of ${n}'s own away`),await h(e,t,"DELETE",s?`/flags/users/${k(n)}/${k(s)}`:`/flags/users/${k(n)}`),e.out.result({user:n,key:s??null},()=>e.out.line(s?`${n} gets ${s} as the flag says.`:`${n} gets every flag as it says.`))}};function x1(e){let t=e===void 0?[]:Array.isArray(e)?e:[String(e)];if(t.length===0)return;let n={};for(let s of t){let a=s.indexOf("=");if(a<=0)throw m(`--var is name=value, not ${JSON.stringify(s)}`);n[s.slice(0,a)]=s.slice(a+1)}return n}var N1={name:"notify:send",summary:"send an email or a text through the environment's providers: written here, or a template with its variables",usage:"<email|sms> <to> [--template <slug> --var name=value\u2026] [--subject <s> --text <t> --html <h>] [--body <text>]",details:["A message is sent as a backend sends it (POST /notify/email, /notify/sms): its delivery is in palbase notify:messages."],args:{min:2,max:2},flags:{template:{type:"string",value:"slug",description:"a template of the environment (notify:templates:list)"},var:{type:"string",value:"name=value",multiple:!0,description:"a template's variable (again for each)"},locale:{type:"string",value:"locale",description:"the template's locale"},subject:{type:"string",value:"text",description:"an email's subject"},text:{type:"string",value:"text",description:"an email's text"},html:{type:"string",value:"html",description:"an email's HTML"},body:{type:"string",value:"text",description:"a text's words"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="email"&&t!=="sms")throw m("notify:send email <to> or notify:send sms <to>");let s=await b(e),a=e.flags,i=x1(a.var),r={to:n,...a.template?{template:a.template}:{},...i?{variables:i}:{},...a.locale?{locale:a.locale}:{}};if(t==="email"){if(!a.template&&(a.subject===void 0||a.text===void 0&&a.html===void 0))throw m("an email is a --template, or a --subject with --text or --html");Object.assign(r,a.subject!==void 0?{subject:a.subject}:{},a.text!==void 0?{text:a.text}:{},a.html!==void 0?{html:a.html}:{})}else{if(!a.template&&a.body===void 0)throw m("a text is a --template or a --body");a.body!==void 0&&(r.body=a.body)}await v(e,s,`send ${t==="email"?"an email":"a text"} to ${n}`);let o=await h(e,s,"POST",`/notify/${t}`,r,j());e.out.result(o,()=>e.out.line(o.skipped?`Not sent to ${A(n)}: the user does not take this category (${A(o.message_id??"")}).`:`Queued to ${A(n)} (${A(o.message_id??"")}): palbase notify:messages says what became of it.`))}},q1={name:"notify:messages",summary:"the messages the environment sent, newest first, and what became of each",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,100)??20,s=await h(e,t,"GET",`/notify/messages?limit=${n}`);e.out.result(s,()=>{s.messages.length===0?e.out.line("Nothing sent yet."):e.out.table(s.messages,[{header:"sent",value:a=>Tt(a.created_at)},{header:"channel",value:a=>a.channel},{header:"to",value:a=>A(a.recipient_hint??"")},{header:"template",value:a=>A(a.template??"")},{header:"status",value:a=>`${a.status}${a.error?` (${A(a.error)})`:""}`},{header:"id",value:a=>a.id}])})}},K1={name:"notify:deliverability",summary:"whether each channel can send now, through which providers, and what to do when it cannot",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/notify/deliverability");e.out.result(n,()=>e.out.table(n.channels,[{header:"channel",value:s=>s.channel},{header:"sends",value:s=>s.ok?"yes":"no"},{header:"via",value:s=>A(s.via.join(" \u2192 "))||"-"},{header:"reports",value:s=>s.reports?"yes":"no"},{header:"why / what to do",value:s=>A([s.because,s.remedy].filter(Boolean).join(" \u2014 "))}]))}},Kb=["android","ios","ios_voip","web"],G1={name:"notify:devices:list",summary:"the devices a push reaches, newest first: each user's, and the installations no one is signed in on \u2014 never a token",usage:"[--user <id>] [--platform android|ios|ios_voip|web] [--limit <n>]",flags:{user:{type:"string",value:"id",description:"one user's devices"},platform:{type:"string",value:"platform",description:"one platform's: android, ios, ios_voip or web"},limit:{type:"string",value:"n",description:"how many (default 50, at most 500)"}},scope:"environment",async run(e){let t=e.flags;if(t.platform!==void 0&&!Kb.includes(String(t.platform)))throw m(`the platforms: ${Kb.join(", ")}`);let n=ns(t.limit,"limit",1,500)??50,s=await b(e),a=new URLSearchParams({limit:String(n)});t.user!==void 0&&a.set("user_id",String(t.user)),t.platform!==void 0&&a.set("platform",String(t.platform));let i=await h(e,s,"GET",`/notify/devices?${a}`);e.out.result(i,()=>{i.devices.length===0?e.out.line("No device: an app registers its device when it asks for push."):e.out.table(i.devices,[{header:"device",value:r=>A(r.device_id)},{header:"platform",value:r=>r.platform},{header:"user",value:r=>A(r.user_id??"(no one signed in)")},{header:"app",value:r=>A(r.app_version??"")},{header:"registered",value:r=>Tt(r.created_at)},{header:"last used",value:r=>Tt(r.last_used_at)}]),i.next_cursor&&e.out.line(`More than ${n}: --limit shows more, --user or --platform fewer.`)})}},U1={name:"notify:devices:delete",summary:"forget a device \u2014 every token it registered, whoever is signed in on it, no one included: a push reaches it no more",usage:"<device_id>",details:["The device's id is the app's own name for it (notify:devices:list). The app registers it again when it next asks for push."],args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await b(e);await v(e,n,`forget the device ${t}: a push reaches it no more`);try{await h(e,n,"DELETE",`/notify/devices/${k(t)}`,void 0,j())}catch(s){throw s instanceof C&&s.status===404?new f("device_not_found",`the environment has no device ${A(t)}`,{hint:"palbase notify:devices:list shows its devices"}):s}e.out.result({deleted:t},()=>e.out.line(`Forgot the device ${A(t)}: a push reaches it no more.`))}},B1={name:"auth:audit",summary:"who did what in the environment's auth, newest first: sign-ins, changes to users and settings, admin actions",usage:"[--event <type>] [--limit <n>]",flags:{event:{type:"string",value:"type",description:"only this kind of event (user.login, admin.user.update\u2026)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"environment",async run(e){let t=await b(e),n=ns(e.flags.limit,"limit",1,1e4)??50,s=[],a;do{let i=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...typeof e.flags.event=="string"?{event_type:e.flags.event}:{}}),r=await h(e,t,"GET",`/admin/audit?${i}`);s.push(...r.events),a=r.next_cursor||void 0}while(a&&s.length<n);e.out.result({events:s},()=>{s.length===0?e.out.line("Nothing recorded yet."):e.out.table(s,[{header:"when",value:i=>Tt(i.created_at)},{header:"event",value:i=>A(i.event_type)},{header:"result",value:i=>A(i.result??"")},{header:"by",value:i=>A(i.actor?.email??i.actor?.user_id??"")},{header:"from",value:i=>A(i.actor?.ip??"")},{header:"on",value:i=>A([i.target_type,i.target_id].filter(Boolean).join(" "))}])})}},z1={name:"auth:audit:verify",summary:"whether the audit trail is whole: each event chained to the one before, none changed or taken out",scope:"environment",async run(e){let t=await b(e),n=await h(e,t,"GET","/admin/audit/verify");if(!n.valid){let s=n.broken_at?` at event ${A(n.broken_at.event_id??"?")} (number ${(n.broken_at.index??0)+1} of ${n.total}, oldest first)`:"";throw new f("audit_trail_broken",`the audit trail is broken${s}: an event there was changed or taken out`)}e.out.result(n,()=>e.out.line(`The audit trail is whole: ${n.total} events, each chained to the one before.`))}},W1={name:"auth:audit:export",summary:"the audit trail as a file: JSON lines or CSV, a span of it, one kind of event",usage:"[--format ndjson|csv] [--from <time>] [--to <time>] [--event <type>] [--out <file>]",flags:{format:{type:"string",value:"ndjson|csv",description:"JSON lines (default) or CSV"},from:{type:"string",value:"time",description:"from this time on (RFC 3339: 2026-10-01T00:00:00Z)"},to:{type:"string",value:"time",description:"up to this time"},event:{type:"string",value:"type",description:"only this kind of event"},out:{type:"string",value:"file",description:"write it here instead of to standard output"}},scope:"environment",async run(e){let t=e.flags.format??"ndjson";if(t!=="ndjson"&&t!=="csv")throw m("--format is ndjson or csv");for(let r of["from","to"]){let o=e.flags[r];if(o!==void 0&&Number.isNaN(Date.parse(String(o))))throw m(`--${r} is a time: 2026-10-01T00:00:00Z`)}let n=await b(e),s=new URLSearchParams({format:String(t)});for(let[r,o]of[["from","from"],["to","to"],["event","event_type"]])typeof e.flags[r]=="string"&&s.set(o,e.flags[r]);let a=await h(e,n,"GET",`/admin/audit/export?${s}`),i=typeof a=="string"?a:t==="ndjson"&&Array.isArray(a)?a.map(r=>JSON.stringify(r)).join(`
|
|
695
695
|
`):JSON.stringify(a);typeof e.flags.out=="string"?(await Gb(Ub(e.io.cwd,e.flags.out),i.endsWith(`
|
|
696
696
|
`)||i===""?i:`${i}
|
|
697
697
|
`,{mode:384}),e.out.result({file:e.flags.out,bytes:Buffer.byteLength(i)},()=>e.out.line(`Wrote the audit trail to ${e.flags.out} (${Buffer.byteLength(i)} bytes).`))):e.io.stdout(i.endsWith(`
|