@elixpo/lixblogs-cli 1.4.5 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/lixblogs.mjs +23 -21
  2. package/package.json +1 -1
package/dist/lixblogs.mjs CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import{parseArgs as xr}from"node:util";import{spawn as _r}from"node:child_process";var z={environment:"production",profile:"default",accountsBaseUrl:"https://accounts.elixpo.com",apiBaseUrl:"https://blogs.elixpo.com"},Ee={development:{clientId:"lixblogs-cli-dev",audience:"localhost"},staging:{clientId:"lixblogs-cli-staging",audience:"staging.blogs.elixpo.com"},production:{clientId:"lixblogs-cli-prod",audience:"blogs.elixpo.com"},test:{clientId:"lixblogs-cli-dev",audience:"localhost"}};function _({flags:t={},env:e=process.env}={}){let r=t.env??e.LIXBLOGS_ENV??z.environment,o=t.profile??e.LIXBLOGS_PROFILE??z.profile,i=Ee[r]||Ee.production,n=t.authProvider??e.LIXBLOGS_AUTH_PROVIDER??(r==="production"?"elixpo":"mock"),a=t.accountsUrl??e.LIXBLOGS_ACCOUNTS_URL??z.accountsBaseUrl,s=t.apiUrl??e.LIXBLOGS_API_URL??z.apiBaseUrl,l=t.clientId??e.LIXBLOGS_CLIENT_ID??i.clientId,u=t.audience??e.LIXBLOGS_AUDIENCE??i.audience;return{environment:r,profile:o,profileExplicit:t.profile!==void 0||e.LIXBLOGS_PROFILE!==void 0,authProvider:n,accountsBaseUrl:a,apiBaseUrl:s,clientId:l,audience:u}}var R=class{get providerId(){throw new Error("AuthProvider.providerId must be implemented by subclass")}async requestDeviceCode(e){throw new Error("AuthProvider.requestDeviceCode must be implemented by subclass")}async pollDeviceCode(e){throw new Error("AuthProvider.pollDeviceCode must be implemented by subclass")}async refresh(e){throw new Error("AuthProvider.refresh must be implemented by subclass")}async revoke(e){throw new Error("AuthProvider.revoke must be implemented by subclass")}};var ke={APPROVE_IMMEDIATELY:"mock-approve-",PENDING_THEN_APPROVE:"mock-pending-then-approve-",DENY:"mock-deny-",EXPIRE:"mock-expire-",SLOW_DOWN_THEN_APPROVE:"mock-slow-down-then-approve-"},Dt=5,Se=0;function Nt(t){return Se+=1,`${t}${Se}`}var G=class extends R{constructor(){super(),this._devicesCodes=new Map,this._revoked=new Set,this._refreshWillFail=new Set}get providerId(){return"mock"}async requestDeviceCode({scopes:e,scenario:r="APPROVE_IMMEDIATELY"}){let o=ke[r]??ke.APPROVE_IMMEDIATELY,i=Nt(o),n=i.slice(-6).toUpperCase();return this._devicesCodes.set(i,{scenario:r,pollCount:0,createdAt:Date.now(),scopes:[...e]}),{deviceCode:i,userCode:n,verificationUri:"https://mock.lixblogs.local/device",verificationUriComplete:`https://mock.lixblogs.local/device?user_code=${encodeURIComponent(n)}`,expiresInSeconds:r==="EXPIRE"?1:600,pollIntervalSeconds:1}}async pollDeviceCode({deviceCode:e}){let r=this._devicesCodes.get(e);return r?r.scenario==="EXPIRE"?{status:"expired"}:r.scenario==="DENY"?{status:"denied"}:r.scenario==="PENDING_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"pending"}:r.scenario==="SLOW_DOWN_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"slow_down",pollIntervalIncreaseSeconds:Dt}:{status:"approved",token:{accessToken:`mock-access-${e}`,refreshToken:`mock-refresh-${e}`,expiresInSeconds:3600,scopes:r.scopes}}:{status:"denied"}}async refresh({refreshToken:e,scopes:r=[]}){if(this._revoked.has(e))throw new Error("refresh token has been revoked");if(this._refreshWillFail.has(e))throw new Error("mock refresh failure (test-injected)");return{accessToken:`mock-access-refreshed-${e}`,refreshToken:e,expiresInSeconds:3600,scopes:r}}async revoke({token:e}){this._revoked.add(e)}_simulateRefreshFailureFor(e){this._refreshWillFail.add(e)}};var Ae="urn:ietf:params:oauth:grant-type:device_code",Bt=1,Mt=15e3,Ft={access_denied:"Login was denied.",authorization_pending:"Login is awaiting approval.",expired_token:"The device authorization expired. Start login again.",invalid_client:"The LixBlogs CLI client is not registered for this environment.",invalid_grant:"This session is no longer valid. Log in again.",invalid_request:"Accounts rejected the authentication request.",invalid_scope:"The requested LixBlogs permissions are not available for this client.",server_error:"Accounts could not complete authentication. Try again later.",slow_down:"Accounts requested slower polling.",temporarily_unavailable:"Accounts is temporarily unavailable. Try again later."},v=class extends Error{constructor(e,{status:r=0,requiresLogin:o=!1}={}){super(Ft[e]||"Authentication failed."),this.name="AuthProviderError",this.code=e||"authentication_failed",this.status=r,this.requiresLogin=o}},I=class extends Error{constructor(e){super(e),this.name="CompatibilityError",this.code="incompatible_accounts_contract"}};function ae(t){return String(t||"0.0.0").split(".").slice(0,3).map(e=>Number.parseInt(e,10)||0)}function zt(t,e){let r=ae(t),o=ae(e);for(let i=0;i<3;i+=1)if(r[i]!==o[i])return r[i]>o[i];return!0}function Gt(t){let e=new URL(t);if(e.pathname=e.pathname.replace(/\/$/,""),e.search="",e.hash="",e.protocol!=="https:"&&e.hostname!=="localhost"&&e.hostname!=="127.0.0.1")throw new I("Accounts must use HTTPS outside local development.");return e.toString().replace(/\/$/,"")}async function j(t){try{return await t.json()}catch{throw new v("server_error",{status:t.status})}}function V(t,e){let r=typeof t?.error=="string"?t.error:"server_error";return new v(r,{status:e.status,requiresLogin:r==="invalid_grant"||r==="access_denied"||r==="expired_token"})}function $e(t,e){if(!e.ok)throw V(t,e);if(typeof t?.access_token!="string"||typeof t?.refresh_token!="string"||!Number.isFinite(Number(t?.expires_in)))throw new v("server_error",{status:e.status});return{accessToken:t.access_token,refreshToken:t.refresh_token,expiresInSeconds:Number(t.expires_in),scopes:typeof t.scope=="string"?t.scope.split(/\s+/).filter(Boolean):[]}}var J=class extends R{constructor({accountsBaseUrl:e="https://accounts.elixpo.com",clientId:r="lixblogs-cli-prod",audience:o="blogs.elixpo.com",cliVersion:i="1.2.0",fetchImpl:n=globalThis.fetch,timeoutMs:a=Mt}={}){if(super(),typeof n!="function")throw new TypeError("A fetch implementation is required.");this.accountsBaseUrl=Gt(e),this.clientId=r,this.audience=o,this.cliVersion=i,this.fetchImpl=n,this.timeoutMs=a,this._metadata=null,this._discoveryPromise=null}get providerId(){return"elixpo"}async _fetch(e,r={}){let o=new AbortController,i=setTimeout(()=>o.abort(),this.timeoutMs);try{return await this.fetchImpl(e,{...r,signal:r.signal||o.signal,headers:{accept:"application/json",...r.headers}})}catch{throw new v("temporarily_unavailable")}finally{clearTimeout(i)}}async discover({scopes:e=[]}={}){if(!this._metadata){this._discoveryPromise||(this._discoveryPromise=this._loadDiscovery());try{this._metadata=await this._discoveryPromise}finally{this._discoveryPromise=null}}if(e.filter(o=>!this._metadata.scopes_supported.includes(o)).length)throw new v("invalid_scope");return this._metadata}async _loadDiscovery(){let e=await this._fetch(`${this.accountsBaseUrl}/.well-known/oauth-authorization-server`),r=await j(e);if(!e.ok)throw new v("temporarily_unavailable",{status:e.status});if(ae(r.elixpo_contract_version)[0]!==Bt)throw new I("Accounts uses an unsupported device-flow contract version.");if(!zt(this.cliVersion,r.elixpo_min_compatible_cli_version))throw new I(`This CLI is too old for Accounts. Upgrade to version ${r.elixpo_min_compatible_cli_version} or newer.`);if(!Array.isArray(r.grant_types_supported)||!r.grant_types_supported.includes(Ae))throw new I("Accounts does not advertise OAuth device authorization.");let i=["device_authorization_endpoint","token_endpoint","revocation_endpoint"];for(let n of i){if(typeof r[n]!="string")throw new I(`Accounts discovery is missing ${n}.`);let a=new URL(r[n]),s=new URL(this.accountsBaseUrl);if(a.origin!==s.origin)throw new I(`Accounts discovery returned an untrusted ${n}.`)}return{...r,scopes_supported:Array.isArray(r.scopes_supported)?r.scopes_supported:[]}}async requestDeviceCode({scopes:e}){let r=await this.discover({scopes:e}),o=await this._fetch(r.device_authorization_endpoint,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_id:this.clientId,scope:e.join(" "),audience:this.audience})}),i=await j(o);if(!o.ok)throw V(i,o);if(typeof i.device_code!="string"||typeof i.user_code!="string"||typeof i.verification_uri!="string")throw new v("server_error",{status:o.status});return{deviceCode:i.device_code,userCode:i.user_code,verificationUri:i.verification_uri,verificationUriComplete:i.verification_uri_complete||i.verification_uri,expiresInSeconds:Number(i.expires_in)||600,pollIntervalSeconds:Number(i.interval)||5}}async pollDeviceCode({deviceCode:e}){let r=await this.discover(),o=new URLSearchParams({grant_type:Ae,device_code:e,client_id:this.clientId}),i=await this._fetch(r.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:o}),n=await j(i);if(i.ok)return{status:"approved",token:$e(n,i)};if(n?.error==="authorization_pending")return{status:"pending"};if(n?.error==="slow_down"){let a=r.elixpo_device_flow_polling||{};return{status:"slow_down",pollIntervalIncreaseSeconds:Math.max(5,Number(a.slow_down_interval_seconds||10)-Number(a.interval_seconds||5))}}if(n?.error==="access_denied")return{status:"denied"};if(n?.error==="expired_token")return{status:"expired"};throw V(n,i)}async refresh({refreshToken:e,scopes:r}){let o=await this.discover({scopes:r||[]}),i=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:this.clientId});r?.length&&i.set("scope",r.join(" "));let n=await this._fetch(o.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:i}),a=await j(n);return $e(a,n)}async revoke({token:e}){let r=await this.discover(),o=await this._fetch(r.revocation_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.clientId})});if(!o.ok){let i=await j(o);throw V(i,o)}}};var Pe="elixpo",le=class extends Error{constructor(e){super(e),this.name="ProductionAuthGateError"}};function Re({providerId:t,environment:e}){if(e==="production"&&t!==Pe)throw new le(`Provider "${t}" is not approved for production. Only "${Pe}" may be used in production.`)}function U(t){if(t.authProvider==="mock"&&t.environment==="production")throw new Error("The mock auth provider cannot run in production.");if(t.authProvider!=="mock"&&t.authProvider!=="elixpo")throw new Error(`Unknown auth provider "${t.authProvider}".`);let e=t.authProvider==="mock"?new G:new J({accountsBaseUrl:t.accountsBaseUrl,clientId:t.clientId,audience:t.audience,cliVersion:t.cliVersion||"1.2.0",fetchImpl:t.fetchImpl});return Re({providerId:e.providerId,environment:t.environment}),e}var E=class extends Error{constructor(e){super(e),this.name="CredentialStoreUnavailableError"}},A=class{async get(e){throw new Error("CredentialStore.get must be implemented by subclass")}async set(e,r){throw new Error("CredentialStore.set must be implemented by subclass")}async delete(e){throw new Error("CredentialStore.delete must be implemented by subclass")}async listProfiles(){throw new Error("CredentialStore.listProfiles must be implemented by subclass")}},H=class extends A{constructor(){super(),this._store=new Map}async get(e){return this._store.get(e)??null}async set(e,r){this._store.set(e,r)}async delete(e){this._store.delete(e)}async listProfiles(){return[...this._store.keys()]}},K=class extends A{constructor(e){super(),this._realStore=e}async get(e){try{return await this._realStore.get(e)}catch(r){throw new E(`OS keychain is unavailable: ${r.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async set(e,r){try{await this._realStore.set(e,r)}catch(o){throw new E(`OS keychain is unavailable: ${o.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async delete(e){try{await this._realStore.delete(e)}catch(r){throw new E(`OS keychain is unavailable: ${r.message}`)}}async listProfiles(){try{return await this._realStore.listProfiles()}catch(e){throw new E(`OS keychain is unavailable: ${e.message}`)}}};import{Entry as Vt}from"@napi-rs/keyring";var Jt="lixblogs-cli",Ht="__lixblogs_availability_probe__";function W(t){return new Vt(Jt,t)}async function Oe(){let t=W(Ht);try{return t.setPassword("probe"),t.deletePassword(),{available:!0}}catch(e){return{available:!1,error:String(e.message??e).split(`
3
- `)[0].trim()}}}var ce=class extends A{async get(e){let r=W(e),o;try{o=r.getPassword()}catch(i){if(qe(i))return null;throw i}return o==null?null:JSON.parse(o)}async set(e,r){W(e).setPassword(JSON.stringify(r))}async delete(e){let r=W(e);try{r.deletePassword()}catch(o){if(qe(o))return;throw o}}async listProfiles(){throw new Error("KeychainCredentialStore.listProfiles is not supported directly \u2014 use ProfileRegistry to track known profile IDs, then look up each one via get().")}};function qe(t){let e=String(t?.message??"");return/no such|not found|nosuchkeyring|nosuchitem/i.test(e)}var X=class extends A{constructor(e){super(),this._keychain=new ce,this._registry=e}async get(e){return this._keychain.get(e)}async set(e,r){await this._keychain.set(e,r),await this._registry.add(e)}async delete(e){await this._keychain.delete(e),await this._registry.remove(e)}async listProfiles(){return this._registry.list()}};import{promises as Y}from"node:fs";import Te from"node:path";import Kt from"node:os";function Wt(){return Te.join(Kt.homedir(),".config","lixblogs","profiles.json")}var g=class{constructor(e=Wt()){this._path=e}async list(){return(await this._read()).profiles}async getActive(){let e=await this._read();return e.activeProfile&&e.profiles.includes(e.activeProfile)?e.activeProfile:e.profiles[0]||null}async setActive(e){x(e);let r=await this._read();if(!r.profiles.includes(e))throw new Error(`Profile "${e}" does not exist. Log in with it first.`);await this._write(r.profiles,e)}async _read(){try{let e=await Y.readFile(this._path,"utf8"),r=JSON.parse(e);return{profiles:Array.isArray(r.profiles)?r.profiles.filter(i=>typeof i=="string"):[],activeProfile:typeof r.activeProfile=="string"?r.activeProfile:null}}catch(e){if(e.code==="ENOENT")return{profiles:[],activeProfile:null};throw e}}async add(e){x(e);let r=await this._read(),o=new Set(r.profiles);o.add(e),await this._write([...o],r.activeProfile||e)}async remove(e){let r=await this._read(),o=r.profiles.filter(n=>n!==e),i=r.activeProfile===e?o[0]||null:r.activeProfile;await this._write(o,i)}async _write(e,r=null){await Y.mkdir(Te.dirname(this._path),{recursive:!0});let o=`${this._path}.${process.pid}.tmp`;await Y.writeFile(o,JSON.stringify({activeProfile:r,profiles:e},null,2),{encoding:"utf8",mode:384}),await Y.rename(o,this._path)}};function x(t){if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(t||""))throw new Error("Profile names must be 1-64 characters using letters, numbers, dot, dash, or underscore.");return t}async function Ce({allowInsecureFallback:t=!1,profileRegistry:e}={}){let r=await Oe();if(!r.available){if(!t)throw new E(`OS keychain is unavailable: ${r.error}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`);return process.stderr.write(`warning: OS keychain unavailable (${r.error}); using in-memory fallback because --allow-insecure-fallback was passed. Credentials will NOT persist between CLI runs.
4
- `),new H}let o=e||new g,i=new X(o);return new K(i)}var de="[REDACTED]",Xt=/token|refresh|secret|password|authorization/i,Yt=/^(mock-(access|refresh)-|Bearer\s+)\S+/i;function Le(t){return typeof t=="string"&&Yt.test(t)?de:t}function ue(t){if(Array.isArray(t))return t.map(e=>ue(e));if(t&&typeof t=="object"){let e={};for(let[r,o]of Object.entries(t))Xt.test(r)?e[r]=de:typeof o=="object"&&o!==null?e[r]=ue(o):e[r]=Le(o);return e}return Le(t)}function Z(t,e){return JSON.stringify(ue(t),null,e)}function q(t){return typeof t!="string"?t:t.replace(/(mock-(access|refresh)-\S+|Bearer\s+\S+)/gi,de)}async function je({provider:t,credentialStore:e,profileId:r,scopes:o,openBrowser:i,resolveProfileId:n,sleep:a=l=>new Promise(u=>setTimeout(u,l)),onStatus:s=()=>{}}){let l;try{l=await t.requestDeviceCode({scopes:o})}catch(c){return{ok:!1,reason:q(c.message)}}s({type:"verification_pending",verificationUri:l.verificationUri,verificationUriComplete:l.verificationUriComplete,userCode:l.userCode,expiresInSeconds:l.expiresInSeconds}),i&&await i(l.verificationUriComplete||l.verificationUri);let u=l.pollIntervalSeconds*1e3,y=Date.now()+l.expiresInSeconds*1e3;for(;Date.now()<y;){await a(u);let c;try{c=await t.pollDeviceCode({deviceCode:l.deviceCode})}catch(h){return{ok:!1,reason:q(h.message)}}if(c.status==="approved"){let h=r;if(n)try{h=await n({accessToken:c.token.accessToken,requestedProfileId:r})}catch(L){return{ok:!1,reason:q(L.message)}}return await e.set(h,{accessToken:c.token.accessToken,refreshToken:c.token.refreshToken,expiresAt:Date.now()+c.token.expiresInSeconds*1e3,scopes:c.token.scopes}),s({type:"approved"}),{ok:!0,profileId:h}}if(c.status==="denied")return s({type:"denied"}),{ok:!1,reason:"Login was denied."};if(c.status==="expired")return s({type:"expired"}),{ok:!1,reason:"Device code expired before login was approved."};if(c.status==="slow_down"){u+=c.pollIntervalIncreaseSeconds*1e3,s({type:"slow_down",newIntervalMs:u});continue}s({type:"pending"})}return{ok:!1,reason:"Device code expired before login was approved."}}async function Ue({credentialStore:t,profileId:e}){let r=e?[e]:await t.listProfiles(),o=[];for(let i of r){let n=await t.get(i);if(!n){o.push({profileId:i,loggedIn:!1});continue}o.push({profileId:i,loggedIn:!0,expired:Date.now()>=n.expiresAt,scopes:n.scopes})}return o}async function De({credentialStore:t,profileId:e}){return await t.delete(e),{ok:!0}}async function Ne({provider:t,credentialStore:e,profileId:r,confirmed:o}){if(o!==!0)return{ok:!1,reason:"Revoke was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};let i=await e.get(r);return i?(await t.revoke({token:i.refreshToken}),await e.delete(r),{ok:!0}):{ok:!1,reason:`No stored credentials for profile "${r}".`}}async function Be({credentialStore:t,profileRegistry:e}){let r=await e.getActive(),o=await t.listProfiles(),i=[];for(let n of o){let a=await t.get(n);i.push({profileId:n,active:n===r,loggedIn:!!a,expired:a?Date.now()>=a.expiresAt:void 0,scopes:a?.scopes||[]})}return{activeProfile:r,profiles:i}}async function Me({credentialStore:t,profileRegistry:e,profileId:r}){return await t.get(r)?(await e.setActive(r),{ok:!0,profileId:r}):{ok:!1,reason:`Profile "${r}" is not logged in.`}}async function Fe({accessToken:t,apiBaseUrl:e,fetchImpl:r=globalThis.fetch}){let o=new URL("/api/v1/me",e),i=await r(o,{headers:{accept:"application/json",authorization:`Bearer ${t}`}}),n;try{n=await i.json()}catch{throw new Error("LixBlogs could not resolve the signed-in username.")}if(!i.ok||typeof n?.data?.username!="string")throw new Error(n?.error?.message||"LixBlogs could not resolve the signed-in username.");return x(n.data.username)}var Zt=6e4,ze=new WeakMap;function Qt(t){let e=ze.get(t);return e||(e=new Map,ze.set(t,e)),e}var Q=class extends Error{constructor(e){super(`Profile "${e}" needs to log in again.`),this.name="LoginRequiredError",this.code="login_required"}},pe=class extends Error{constructor(e,r){super("The configured LixBlogs origin is not serving the API v1 JSON contract."),this.name="ApiContractUnavailableError",this.code="api_contract_unavailable",this.status=e,this.details={contentType:r||"unknown"},this.hint="Deploy the LixBlogs API v1 stack, or select an origin that exposes /api/v1."}},D=class{constructor({provider:e,credentialStore:r,profileId:o,apiBaseUrl:i="https://blogs.elixpo.com",fetchImpl:n=globalThis.fetch,refreshSkewMs:a=Zt}){this.provider=e,this.credentialStore=r,this.profileId=o,this.apiBaseUrl=new URL(i),this.fetchImpl=n,this.refreshSkewMs=a}async _refresh(e,{force:r=!1}={}){let o=Qt(this.credentialStore),i=o.get(this.profileId);if(i)return i;let n=(async()=>{let a=await this.credentialStore.get(this.profileId)||e;if(!r&&a.expiresAt-Date.now()>this.refreshSkewMs)return a;try{let s=await this.provider.refresh({refreshToken:a.refreshToken,scopes:a.scopes}),l={accessToken:s.accessToken,refreshToken:s.refreshToken,expiresAt:Date.now()+s.expiresInSeconds*1e3,scopes:s.scopes};return await this.credentialStore.set(this.profileId,l),l}catch(s){throw s instanceof v&&s.requiresLogin?(await this.credentialStore.delete(this.profileId),new Q(this.profileId)):s}})();o.set(this.profileId,n);try{return await n}finally{o.get(this.profileId)===n&&o.delete(this.profileId)}}async credentials({forceRefresh:e=!1}={}){let r=await this.credentialStore.get(this.profileId);if(!r)throw new Q(this.profileId);return e||r.expiresAt-Date.now()<=this.refreshSkewMs?this._refresh(r,{force:e}):r}async request(e,r={}){let o=new URL(e,this.apiBaseUrl);if(o.origin!==this.apiBaseUrl.origin||!o.pathname.startsWith("/api/v1/"))throw new Error("Authenticated CLI requests are restricted to the configured LixBlogs /api/v1 resource server.");let i=await this.credentials(),n=()=>this.fetchImpl(o.toString(),{...r,headers:{...r.headers,authorization:`Bearer ${i.accessToken}`}}),a=await n();a.status===401&&(i=await this.credentials({forceRefresh:!0}),a=await n());let s=a.headers.get("content-type")||"";if(!s.toLowerCase().includes("application/json"))throw new pe(a.status,s);return a}async requireScopes(e){let r=await this.credentials(),o=e.filter(i=>!r.scopes.includes(i));if(o.length){let i=new Error(`Login again with the required scope${o.length>1?"s":""}: ${o.join(", ")}`);throw i.name="InsufficientScopeError",i.code="insufficient_scope",i.missingScopes=o,i}}};import{randomUUID as Ge}from"node:crypto";var d=class extends Error{constructor(e,r,{status:o,requestId:i,details:n}={}){super(r),this.name="BlogApiError",this.code=e||"api_error",this.status=o||0,this.requestId=i||null,this.details=n||null}};async function er(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new d(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var N=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET"||!!o.headers["idempotency-key"];for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return er(s)}catch(s){if(!n||a>0||s instanceof d||s?.code)throw s;await this.sleep(250)}throw new d("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async whoami(){return await this.requireScopes(["lixblogs:profile:read"]),(await this.request("/api/v1/me")).payload.data}async list({status:e="all",limit:r=20,cursor:o}={}){await this.requireScopes(["lixblogs:blog:read"]);let i=new URLSearchParams({status:e,limit:String(r)});return o&&i.set("cursor",o),(await this.request(`/api/v1/blogs?${i}`)).payload}async get(e){await this.requireScopes(["lixblogs:blog:read"]);let r=await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`);return{...r.payload.data,etag:r.etag||r.payload.data.etag}}async create(e,{idempotencyKey:r=Ge()}={}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request("/api/v1/blogs",{method:"POST",headers:{"idempotency-key":r},body:JSON.stringify(e)})).payload.data}async update(e,r,{etag:o}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"if-match":o},body:JSON.stringify(r)})).payload.data}async publish(e,{etag:r,idempotencyKey:o=Ge()}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/publish`,{method:"POST",headers:{"if-match":r,"idempotency-key":o}})).payload.data}async unpublish(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/unpublish`,{method:"POST",headers:{"if-match":r}})).payload.data}async delete(e,{etag:r,permanent:o=!1}){return await this.requireScopes(["lixblogs:blog:delete",...o?["lixblogs:blog:delete:permanent"]:[]]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}${o?"?permanent=true":""}`,{method:"DELETE",headers:{"if-match":r,...o?{"x-confirm-permanent-delete":e}:{}}})).payload.data}async restore(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:delete"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/restore`,{method:"POST",headers:{"if-match":r}})).payload.data}};async function tr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new d(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var ee=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET";for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return tr(s)}catch(s){if(!n||a>0||s instanceof d||s?.code)throw s;await this.sleep(250)}throw new d("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async list(){return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request("/api/v1/orgs")).payload}async get(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}`)).payload.data}async collections(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/collections`)).payload.data}async members(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/members`)).payload.data}async targets(){await this.requireScopes(["lixblogs:organizations:read"]);let o=((await this.list())?.data||[]).filter(n=>n.canWrite),i=await Promise.all(o.map(async n=>{let a=[];try{a=await this.collections(n.id)}catch{a=[]}return{target:`org:${n.id}`,orgId:n.id,slug:n.slug,name:n.name,role:n.role,collections:a.map(s=>({id:s.id,slug:s.slug,name:s.name}))}}));return{personal:{target:"personal",name:"Personal Blog"},organizations:i}}};import{randomUUID as te}from"node:crypto";async function rr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new d(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e.data}var re=class{constructor(e){this.http=e}async request(e,r={}){let o=await this.http.request(e,{...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}});return rr(o)}async list(e){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`)}async invitations(){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request("/api/v1/collaboration/invitations")}async invite(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async role(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"PATCH",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async remove(e,{user:r,idempotencyKey:o=te()}={}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"DELETE",headers:{"idempotency-key":o},body:JSON.stringify({...r?{user:r}:{}})})}async resolveInvitation(e,{action:r,showOnProfile:o=!0,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request("/api/v1/collaboration/invitations",{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({blogId:e,action:r,showOnProfile:o})})}};async function or(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new d(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e}var oe=class{constructor(e){this.http=e}async query(e={}){let r=e.scope||"personal";await this.http.requireScopes(["lixblogs:analytics:read",...r.startsWith("org:")?["lixblogs:organizations:read"]:[]]);let o=new URLSearchParams({scope:r,range:e.range||(e.from||e.to?"custom":"30d"),dimension:e.dimension||"overview",limit:String(e.limit||20)});return e.from&&o.set("from",e.from),e.to&&o.set("to",e.to),e.cursor&&o.set("cursor",e.cursor),or(await this.http.request(`/api/v1/analytics?${o}`,{headers:{accept:"application/json"}}))}};var f=Object.freeze({OK:0,ERROR:1,USAGE:2,CONFLICT:3,AUTH:4,CONFIRMATION:5}),ir=Object.freeze({login:["auth","login"],logout:["auth","logout"],whoami:["auth","whoami"],profiles:["auth","profiles"],use:["auth","use"]});function Ve(t){let[e,...r]=t,o=ir[e];return o?[...o,...r]:t}function Je(t,e="cli_error"){if(t&&typeof t=="object"&&t.error&&!Array.isArray(t.error))return t;let r=t&&typeof t=="object"?t:{message:String(t||"Command failed.")};return{ok:!1,error:{code:r.code||e,message:r.message||"Command failed.",hint:r.hint||null,requestId:r.requestId||null,...r.details?{details:r.details}:{}}}}function w(t,e){if(t.yes)return;let r=new Error(`${e} requires --yes in non-interactive operation.`);throw r.code="confirmation_required",r.hint="Review the operation, then run it again with --yes.",r.exitCode=f.CONFIRMATION,r}var k=Object.freeze({reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",violet:"\x1B[38;5;141m",green:"\x1B[38;5;42m"});function fe(t=process.stdout,e=process.env){return!!t.isTTY&&e.NO_COLOR===void 0&&e.TERM!=="dumb"}function $(t,e,r){return r?`${e}${t}${k.reset}`:t}function He({url:t,code:e,expiresInSeconds:r,profile:o,interactive:i,color:n=!1}){let a=`${$("\u25C6",k.violet,n)} ${$("LixBlogs",k.bold,n)}`,s=i?"Press Enter to open here, or use the URL on another device.":"Open the URL in any browser and approve this device.";return["",` ${a}`,` ${$("Device login",k.dim,n)}`," \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",` URL ${t}`,` Code ${$(e,k.bold,n)}`,` Expires ${Math.ceil(r/60)} min`,o?` Profile ${o} ${$("(local credential slot)",k.dim,n)}`:` Profile ${$("your Accounts username after approval",k.dim,n)}`,"",` ${s}`," No localhost callback or exposed port is required.",""].join(`
5
- `)}function Ke(t,e=!1){return` ${$("\u2713",k.green,e)} ${t}`}function We({input:t=process.stdin,open:e,url:r}){if(!t.isTTY||typeof e!="function")return()=>{};let o=()=>{Promise.resolve(e(r)).catch(()=>{})};return t.setEncoding?.("utf8"),t.once("data",o),t.resume?.(),()=>{t.off?.("data",o),t.pause?.()}}function O(t){return[{type:"text",text:t}]}function Xe(t){let e=String(t||"").replace(/\r\n/g,`
2
+ import{parseArgs as Er}from"node:util";import{spawn as kr}from"node:child_process";var z={environment:"production",profile:"default",accountsBaseUrl:"https://accounts.elixpo.com",apiBaseUrl:"https://blogs.elixpo.com"},Se={development:{clientId:"lixblogs-cli-dev",audience:"localhost"},staging:{clientId:"lixblogs-cli-staging",audience:"staging.blogs.elixpo.com"},production:{clientId:"lixblogs-cli-prod",audience:"blogs.elixpo.com"},test:{clientId:"lixblogs-cli-dev",audience:"localhost"}};function x({flags:t={},env:e=process.env}={}){let r=t.env??e.LIXBLOGS_ENV??z.environment,o=t.profile??e.LIXBLOGS_PROFILE??z.profile,i=Se[r]||Se.production,n=t.authProvider??e.LIXBLOGS_AUTH_PROVIDER??(r==="production"?"elixpo":"mock"),a=t.accountsUrl??e.LIXBLOGS_ACCOUNTS_URL??z.accountsBaseUrl,s=t.apiUrl??e.LIXBLOGS_API_URL??z.apiBaseUrl,l=t.clientId??e.LIXBLOGS_CLIENT_ID??i.clientId,u=t.audience??e.LIXBLOGS_AUDIENCE??i.audience;return{environment:r,profile:o,profileExplicit:t.profile!==void 0||e.LIXBLOGS_PROFILE!==void 0,authProvider:n,accountsBaseUrl:a,apiBaseUrl:s,clientId:l,audience:u}}var R=class{get providerId(){throw new Error("AuthProvider.providerId must be implemented by subclass")}async requestDeviceCode(e){throw new Error("AuthProvider.requestDeviceCode must be implemented by subclass")}async pollDeviceCode(e){throw new Error("AuthProvider.pollDeviceCode must be implemented by subclass")}async refresh(e){throw new Error("AuthProvider.refresh must be implemented by subclass")}async revoke(e){throw new Error("AuthProvider.revoke must be implemented by subclass")}};var $e={APPROVE_IMMEDIATELY:"mock-approve-",PENDING_THEN_APPROVE:"mock-pending-then-approve-",DENY:"mock-deny-",EXPIRE:"mock-expire-",SLOW_DOWN_THEN_APPROVE:"mock-slow-down-then-approve-"},Mt=5,qe=0;function Ft(t){return qe+=1,`${t}${qe}`}var G=class extends R{constructor(){super(),this._devicesCodes=new Map,this._revoked=new Set,this._refreshWillFail=new Set}get providerId(){return"mock"}async requestDeviceCode({scopes:e,scenario:r="APPROVE_IMMEDIATELY"}){let o=$e[r]??$e.APPROVE_IMMEDIATELY,i=Ft(o),n=i.slice(-6).toUpperCase();return this._devicesCodes.set(i,{scenario:r,pollCount:0,createdAt:Date.now(),scopes:[...e]}),{deviceCode:i,userCode:n,verificationUri:"https://mock.lixblogs.local/device",verificationUriComplete:`https://mock.lixblogs.local/device?user_code=${encodeURIComponent(n)}`,expiresInSeconds:r==="EXPIRE"?1:600,pollIntervalSeconds:1}}async pollDeviceCode({deviceCode:e}){let r=this._devicesCodes.get(e);return r?r.scenario==="EXPIRE"?{status:"expired"}:r.scenario==="DENY"?{status:"denied"}:r.scenario==="PENDING_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"pending"}:r.scenario==="SLOW_DOWN_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"slow_down",pollIntervalIncreaseSeconds:Mt}:{status:"approved",token:{accessToken:`mock-access-${e}`,refreshToken:`mock-refresh-${e}`,expiresInSeconds:3600,scopes:r.scopes}}:{status:"denied"}}async refresh({refreshToken:e,scopes:r=[]}){if(this._revoked.has(e))throw new Error("refresh token has been revoked");if(this._refreshWillFail.has(e))throw new Error("mock refresh failure (test-injected)");return{accessToken:`mock-access-refreshed-${e}`,refreshToken:e,expiresInSeconds:3600,scopes:r}}async revoke({token:e}){this._revoked.add(e)}_simulateRefreshFailureFor(e){this._refreshWillFail.add(e)}};var Ae="urn:ietf:params:oauth:grant-type:device_code",zt=1,Gt=15e3,Vt={access_denied:"Login was denied.",authorization_pending:"Login is awaiting approval.",expired_token:"The device authorization expired. Start login again.",invalid_client:"The LixBlogs CLI client is not registered for this environment.",invalid_grant:"This session is no longer valid. Log in again.",invalid_request:"Accounts rejected the authentication request.",invalid_scope:"The requested LixBlogs permissions are not available for this client.",server_error:"Accounts could not complete authentication. Try again later.",slow_down:"Accounts requested slower polling.",temporarily_unavailable:"Accounts is temporarily unavailable. Try again later."},v=class extends Error{constructor(e,{status:r=0,requiresLogin:o=!1}={}){super(Vt[e]||"Authentication failed."),this.name="AuthProviderError",this.code=e||"authentication_failed",this.status=r,this.requiresLogin=o}},E=class extends Error{constructor(e){super(e),this.name="CompatibilityError",this.code="incompatible_accounts_contract"}};function le(t){return String(t||"0.0.0").split(".").slice(0,3).map(e=>Number.parseInt(e,10)||0)}function Jt(t,e){let r=le(t),o=le(e);for(let i=0;i<3;i+=1)if(r[i]!==o[i])return r[i]>o[i];return!0}function Ht(t){let e=new URL(t);if(e.pathname=e.pathname.replace(/\/$/,""),e.search="",e.hash="",e.protocol!=="https:"&&e.hostname!=="localhost"&&e.hostname!=="127.0.0.1")throw new E("Accounts must use HTTPS outside local development.");return e.toString().replace(/\/$/,"")}async function N(t){try{return await t.json()}catch{throw new v("server_error",{status:t.status})}}function V(t,e){let r=typeof t?.error=="string"?t.error:"server_error";return new v(r,{status:e.status,requiresLogin:r==="invalid_grant"||r==="access_denied"||r==="expired_token"})}function Re(t,e){if(!e.ok)throw V(t,e);if(typeof t?.access_token!="string"||typeof t?.refresh_token!="string"||!Number.isFinite(Number(t?.expires_in)))throw new v("server_error",{status:e.status});return{accessToken:t.access_token,refreshToken:t.refresh_token,expiresInSeconds:Number(t.expires_in),scopes:typeof t.scope=="string"?t.scope.split(/\s+/).filter(Boolean):[]}}var J=class extends R{constructor({accountsBaseUrl:e="https://accounts.elixpo.com",clientId:r="lixblogs-cli-prod",audience:o="blogs.elixpo.com",cliVersion:i="1.2.0",fetchImpl:n=globalThis.fetch,timeoutMs:a=Gt}={}){if(super(),typeof n!="function")throw new TypeError("A fetch implementation is required.");this.accountsBaseUrl=Ht(e),this.clientId=r,this.audience=o,this.cliVersion=i,this.fetchImpl=n,this.timeoutMs=a,this._metadata=null,this._discoveryPromise=null}get providerId(){return"elixpo"}async _fetch(e,r={}){let o=new AbortController,i=setTimeout(()=>o.abort(),this.timeoutMs);try{return await this.fetchImpl(e,{...r,signal:r.signal||o.signal,headers:{accept:"application/json",...r.headers}})}catch{throw new v("temporarily_unavailable")}finally{clearTimeout(i)}}async discover({scopes:e=[]}={}){if(!this._metadata){this._discoveryPromise||(this._discoveryPromise=this._loadDiscovery());try{this._metadata=await this._discoveryPromise}finally{this._discoveryPromise=null}}if(e.filter(o=>!this._metadata.scopes_supported.includes(o)).length)throw new v("invalid_scope");return this._metadata}async _loadDiscovery(){let e=await this._fetch(`${this.accountsBaseUrl}/.well-known/oauth-authorization-server`),r=await N(e);if(!e.ok)throw new v("temporarily_unavailable",{status:e.status});if(le(r.elixpo_contract_version)[0]!==zt)throw new E("Accounts uses an unsupported device-flow contract version.");if(!Jt(this.cliVersion,r.elixpo_min_compatible_cli_version))throw new E(`This CLI is too old for Accounts. Upgrade to version ${r.elixpo_min_compatible_cli_version} or newer.`);if(!Array.isArray(r.grant_types_supported)||!r.grant_types_supported.includes(Ae))throw new E("Accounts does not advertise OAuth device authorization.");let i=["device_authorization_endpoint","token_endpoint","revocation_endpoint"];for(let n of i){if(typeof r[n]!="string")throw new E(`Accounts discovery is missing ${n}.`);let a=new URL(r[n]),s=new URL(this.accountsBaseUrl);if(a.origin!==s.origin)throw new E(`Accounts discovery returned an untrusted ${n}.`)}return{...r,scopes_supported:Array.isArray(r.scopes_supported)?r.scopes_supported:[]}}async requestDeviceCode({scopes:e}){let r=await this.discover({scopes:e}),o=await this._fetch(r.device_authorization_endpoint,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_id:this.clientId,scope:e.join(" "),audience:this.audience})}),i=await N(o);if(!o.ok)throw V(i,o);if(typeof i.device_code!="string"||typeof i.user_code!="string"||typeof i.verification_uri!="string")throw new v("server_error",{status:o.status});return{deviceCode:i.device_code,userCode:i.user_code,verificationUri:i.verification_uri,verificationUriComplete:i.verification_uri_complete||i.verification_uri,expiresInSeconds:Number(i.expires_in)||600,pollIntervalSeconds:Number(i.interval)||5}}async pollDeviceCode({deviceCode:e}){let r=await this.discover(),o=new URLSearchParams({grant_type:Ae,device_code:e,client_id:this.clientId}),i=await this._fetch(r.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:o}),n=await N(i);if(i.ok)return{status:"approved",token:Re(n,i)};if(n?.error==="authorization_pending")return{status:"pending"};if(n?.error==="slow_down"){let a=r.elixpo_device_flow_polling||{};return{status:"slow_down",pollIntervalIncreaseSeconds:Math.max(5,Number(a.slow_down_interval_seconds||10)-Number(a.interval_seconds||5))}}if(n?.error==="access_denied")return{status:"denied"};if(n?.error==="expired_token")return{status:"expired"};throw V(n,i)}async refresh({refreshToken:e,scopes:r}){let o=await this.discover({scopes:r||[]}),i=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:this.clientId});r?.length&&i.set("scope",r.join(" "));let n=await this._fetch(o.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:i}),a=await N(n);return Re(a,n)}async revoke({token:e}){let r=await this.discover(),o=await this._fetch(r.revocation_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.clientId})});if(!o.ok){let i=await N(o);throw V(i,o)}}};var Pe="elixpo",ce=class extends Error{constructor(e){super(e),this.name="ProductionAuthGateError"}};function Te({providerId:t,environment:e}){if(e==="production"&&t!==Pe)throw new ce(`Provider "${t}" is not approved for production. Only "${Pe}" may be used in production.`)}function P(t){if(t.authProvider==="mock"&&t.environment==="production")throw new Error("The mock auth provider cannot run in production.");if(t.authProvider!=="mock"&&t.authProvider!=="elixpo")throw new Error(`Unknown auth provider "${t.authProvider}".`);let e=t.authProvider==="mock"?new G:new J({accountsBaseUrl:t.accountsBaseUrl,clientId:t.clientId,audience:t.audience,cliVersion:t.cliVersion||"1.2.0",fetchImpl:t.fetchImpl});return Te({providerId:e.providerId,environment:t.environment}),e}var k=class extends Error{constructor(e){super(e),this.name="CredentialStoreUnavailableError"}},q=class{async get(e){throw new Error("CredentialStore.get must be implemented by subclass")}async set(e,r){throw new Error("CredentialStore.set must be implemented by subclass")}async delete(e){throw new Error("CredentialStore.delete must be implemented by subclass")}async listProfiles(){throw new Error("CredentialStore.listProfiles must be implemented by subclass")}},H=class extends q{constructor(){super(),this._store=new Map}async get(e){return this._store.get(e)??null}async set(e,r){this._store.set(e,r)}async delete(e){this._store.delete(e)}async listProfiles(){return[...this._store.keys()]}},K=class extends q{constructor(e){super(),this._realStore=e}async get(e){try{return await this._realStore.get(e)}catch(r){throw new k(`OS keychain is unavailable: ${r.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async set(e,r){try{await this._realStore.set(e,r)}catch(o){throw new k(`OS keychain is unavailable: ${o.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async delete(e){try{await this._realStore.delete(e)}catch(r){throw new k(`OS keychain is unavailable: ${r.message}`)}}async listProfiles(){try{return await this._realStore.listProfiles()}catch(e){throw new k(`OS keychain is unavailable: ${e.message}`)}}};import{Entry as Kt}from"@napi-rs/keyring";var Wt="lixblogs-cli",Xt="__lixblogs_availability_probe__";function W(t){return new Kt(Wt,t)}async function Ce(){let t=W(Xt);try{return t.setPassword("probe"),t.deletePassword(),{available:!0}}catch(e){return{available:!1,error:String(e.message??e).split(`
3
+ `)[0].trim()}}}var ue=class extends q{async get(e){let r=W(e),o;try{o=r.getPassword()}catch(i){if(Oe(i))return null;throw i}return o==null?null:JSON.parse(o)}async set(e,r){W(e).setPassword(JSON.stringify(r))}async delete(e){let r=W(e);try{r.deletePassword()}catch(o){if(Oe(o))return;throw o}}async listProfiles(){throw new Error("KeychainCredentialStore.listProfiles is not supported directly \u2014 use ProfileRegistry to track known profile IDs, then look up each one via get().")}};function Oe(t){let e=String(t?.message??"");return/no such|not found|nosuchkeyring|nosuchitem/i.test(e)}var X=class extends q{constructor(e){super(),this._keychain=new ue,this._registry=e}async get(e){return this._keychain.get(e)}async set(e,r){await this._keychain.set(e,r),await this._registry.add(e)}async delete(e){await this._keychain.delete(e),await this._registry.remove(e)}async listProfiles(){return this._registry.list()}};import{promises as Y}from"node:fs";import Le from"node:path";import Yt from"node:os";function Zt(){return Le.join(Yt.homedir(),".config","lixblogs","profiles.json")}var y=class{constructor(e=Zt()){this._path=e}async list(){return(await this._read()).profiles}async getActive(){let e=await this._read();return e.activeProfile&&e.profiles.includes(e.activeProfile)?e.activeProfile:e.profiles[0]||null}async setActive(e){_(e);let r=await this._read();if(!r.profiles.includes(e))throw new Error(`Profile "${e}" does not exist. Log in with it first.`);await this._write(r.profiles,e)}async _read(){try{let e=await Y.readFile(this._path,"utf8"),r=JSON.parse(e);return{profiles:Array.isArray(r.profiles)?r.profiles.filter(i=>typeof i=="string"):[],activeProfile:typeof r.activeProfile=="string"?r.activeProfile:null}}catch(e){if(e.code==="ENOENT")return{profiles:[],activeProfile:null};throw e}}async add(e){_(e);let r=await this._read(),o=new Set(r.profiles);o.add(e),await this._write([...o],r.activeProfile||e)}async remove(e){let r=await this._read(),o=r.profiles.filter(n=>n!==e),i=r.activeProfile===e?o[0]||null:r.activeProfile;await this._write(o,i)}async _write(e,r=null){await Y.mkdir(Le.dirname(this._path),{recursive:!0});let o=`${this._path}.${process.pid}.tmp`;await Y.writeFile(o,JSON.stringify({activeProfile:r,profiles:e},null,2),{encoding:"utf8",mode:384}),await Y.rename(o,this._path)}};function _(t){if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(t||""))throw new Error("Profile names must be 1-64 characters using letters, numbers, dot, dash, or underscore.");return t}async function je({allowInsecureFallback:t=!1,profileRegistry:e}={}){let r=await Ce();if(!r.available){if(!t)throw new k(`OS keychain is unavailable: ${r.error}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`);return process.stderr.write(`warning: OS keychain unavailable (${r.error}); using in-memory fallback because --allow-insecure-fallback was passed. Credentials will NOT persist between CLI runs.
4
+ `),new H}let o=e||new y,i=new X(o);return new K(i)}var fe="[REDACTED]",Qt=/token|refresh|secret|password|authorization/i,er=/^(mock-(access|refresh)-|Bearer\s+)\S+/i;function Ue(t){return typeof t=="string"&&er.test(t)?fe:t}function de(t){if(Array.isArray(t))return t.map(e=>de(e));if(t&&typeof t=="object"){let e={};for(let[r,o]of Object.entries(t))Qt.test(r)?e[r]=fe:typeof o=="object"&&o!==null?e[r]=de(o):e[r]=Ue(o);return e}return Ue(t)}function Z(t,e){return JSON.stringify(de(t),null,e)}function T(t){return typeof t!="string"?t:t.replace(/(mock-(access|refresh)-\S+|Bearer\s+\S+)/gi,fe)}async function De({provider:t,credentialStore:e,profileId:r,scopes:o,openBrowser:i,resolveProfileId:n,sleep:a=l=>new Promise(u=>setTimeout(u,l)),onStatus:s=()=>{}}){let l;try{l=await t.requestDeviceCode({scopes:o})}catch(c){return{ok:!1,reason:T(c.message)}}s({type:"verification_pending",verificationUri:l.verificationUri,verificationUriComplete:l.verificationUriComplete,userCode:l.userCode,expiresInSeconds:l.expiresInSeconds}),i&&await i(l.verificationUriComplete||l.verificationUri);let u=l.pollIntervalSeconds*1e3,f=Date.now()+l.expiresInSeconds*1e3;for(;Date.now()<f;){await a(u);let c;try{c=await t.pollDeviceCode({deviceCode:l.deviceCode})}catch(g){return{ok:!1,reason:T(g.message)}}if(c.status==="approved"){let g=r;if(n)try{g=await n({accessToken:c.token.accessToken,requestedProfileId:r})}catch(D){return{ok:!1,reason:T(D.message)}}return await e.set(g,{accessToken:c.token.accessToken,refreshToken:c.token.refreshToken,expiresAt:Date.now()+c.token.expiresInSeconds*1e3,scopes:c.token.scopes}),s({type:"approved"}),{ok:!0,profileId:g}}if(c.status==="denied")return s({type:"denied"}),{ok:!1,reason:"Login was denied."};if(c.status==="expired")return s({type:"expired"}),{ok:!1,reason:"Device code expired before login was approved."};if(c.status==="slow_down"){u+=c.pollIntervalIncreaseSeconds*1e3,s({type:"slow_down",newIntervalMs:u});continue}s({type:"pending"})}return{ok:!1,reason:"Device code expired before login was approved."}}async function Ne({credentialStore:t,profileId:e}){let r=e?[e]:await t.listProfiles(),o=[];for(let i of r){let n=await t.get(i);if(!n){o.push({profileId:i,loggedIn:!1});continue}o.push({profileId:i,loggedIn:!0,expired:Date.now()>=n.expiresAt,scopes:n.scopes})}return o}async function Be({credentialStore:t,profileId:e}){return await t.delete(e),{ok:!0}}async function Me({provider:t,credentialStore:e,profileId:r,confirmed:o}){if(o!==!0)return{ok:!1,reason:"Revoke was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};let i=await e.get(r);return i?(await t.revoke({token:i.refreshToken}),await e.delete(r),{ok:!0}):{ok:!1,reason:`No stored credentials for profile "${r}".`}}async function Fe({credentialStore:t,profileRegistry:e}){let r=await e.getActive(),o=await t.listProfiles(),i=[];for(let n of o){let a=await t.get(n);i.push({profileId:n,active:n===r,loggedIn:!!a,expired:a?Date.now()>=a.expiresAt:void 0,scopes:a?.scopes||[]})}return{activeProfile:r,profiles:i}}async function ze({credentialStore:t,profileRegistry:e,profileId:r}){return await t.get(r)?(await e.setActive(r),{ok:!0,profileId:r}):{ok:!1,reason:`Profile "${r}" is not logged in.`}}async function Ge({accessToken:t,apiBaseUrl:e,fetchImpl:r=globalThis.fetch}){let o=new URL("/api/v1/me",e),i=await r(o,{headers:{accept:"application/json",authorization:`Bearer ${t}`}}),n;try{n=await i.json()}catch{throw new Error("LixBlogs could not resolve the signed-in username.")}if(!i.ok||typeof n?.data?.username!="string")throw new Error(n?.error?.message||"LixBlogs could not resolve the signed-in username.");return _(n.data.username)}var tr=6e4,Ve=new WeakMap;function rr(t){let e=Ve.get(t);return e||(e=new Map,Ve.set(t,e)),e}var Q=class extends Error{constructor(e){super(`Profile "${e}" needs to log in again.`),this.name="LoginRequiredError",this.code="login_required"}},pe=class extends Error{constructor(e,r){super("The configured LixBlogs origin is not serving the API v1 JSON contract."),this.name="ApiContractUnavailableError",this.code="api_contract_unavailable",this.status=e,this.details={contentType:r||"unknown"},this.hint="Deploy the LixBlogs API v1 stack, or select an origin that exposes /api/v1."}},O=class{constructor({provider:e,credentialStore:r,profileId:o,apiBaseUrl:i="https://blogs.elixpo.com",fetchImpl:n=globalThis.fetch,refreshSkewMs:a=tr}){this.provider=e,this.credentialStore=r,this.profileId=o,this.apiBaseUrl=new URL(i),this.fetchImpl=n,this.refreshSkewMs=a}async _refresh(e,{force:r=!1}={}){let o=rr(this.credentialStore),i=o.get(this.profileId);if(i)return i;let n=(async()=>{let a=await this.credentialStore.get(this.profileId)||e;if(!r&&a.expiresAt-Date.now()>this.refreshSkewMs)return a;try{let s=await this.provider.refresh({refreshToken:a.refreshToken,scopes:a.scopes}),l={accessToken:s.accessToken,refreshToken:s.refreshToken,expiresAt:Date.now()+s.expiresInSeconds*1e3,scopes:s.scopes};return await this.credentialStore.set(this.profileId,l),l}catch(s){throw s instanceof v&&s.requiresLogin?(await this.credentialStore.delete(this.profileId),new Q(this.profileId)):s}})();o.set(this.profileId,n);try{return await n}finally{o.get(this.profileId)===n&&o.delete(this.profileId)}}async credentials({forceRefresh:e=!1}={}){let r=await this.credentialStore.get(this.profileId);if(!r)throw new Q(this.profileId);return e||r.expiresAt-Date.now()<=this.refreshSkewMs?this._refresh(r,{force:e}):r}async request(e,r={}){let o=new URL(e,this.apiBaseUrl);if(o.origin!==this.apiBaseUrl.origin||!o.pathname.startsWith("/api/v1/"))throw new Error("Authenticated CLI requests are restricted to the configured LixBlogs /api/v1 resource server.");let i=await this.credentials(),n=()=>this.fetchImpl(o.toString(),{...r,headers:{...r.headers,authorization:`Bearer ${i.accessToken}`}}),a=await n();a.status===401&&(i=await this.credentials({forceRefresh:!0}),a=await n());let s=a.headers.get("content-type")||"";if(!s.toLowerCase().includes("application/json"))throw new pe(a.status,s);return a}async requireScopes(e){let r=await this.credentials(),o=e.filter(i=>!r.scopes.includes(i));if(o.length){let i=new Error(`Login again with the required scope${o.length>1?"s":""}: ${o.join(", ")}`);throw i.name="InsufficientScopeError",i.code="insufficient_scope",i.missingScopes=o,i}}};import{randomUUID as Je}from"node:crypto";var p=class extends Error{constructor(e,r,{status:o,requestId:i,details:n}={}){super(r),this.name="BlogApiError",this.code=e||"api_error",this.status=o||0,this.requestId=i||null,this.details=n||null}};async function or(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var B=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET"||!!o.headers["idempotency-key"];for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return or(s)}catch(s){if(!n||a>0||s instanceof p||s?.code)throw s;await this.sleep(250)}throw new p("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async whoami(){return await this.requireScopes(["lixblogs:profile:read"]),(await this.request("/api/v1/me")).payload.data}async list({status:e="all",limit:r=20,cursor:o}={}){await this.requireScopes(["lixblogs:blog:read"]);let i=new URLSearchParams({status:e,limit:String(r)});return o&&i.set("cursor",o),(await this.request(`/api/v1/blogs?${i}`)).payload}async get(e){await this.requireScopes(["lixblogs:blog:read"]);let r=await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`);return{...r.payload.data,etag:r.etag||r.payload.data.etag}}async create(e,{idempotencyKey:r=Je()}={}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request("/api/v1/blogs",{method:"POST",headers:{"idempotency-key":r},body:JSON.stringify(e)})).payload.data}async update(e,r,{etag:o}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"if-match":o},body:JSON.stringify(r)})).payload.data}async publish(e,{etag:r,idempotencyKey:o=Je()}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/publish`,{method:"POST",headers:{"if-match":r,"idempotency-key":o}})).payload.data}async unpublish(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/unpublish`,{method:"POST",headers:{"if-match":r}})).payload.data}async delete(e,{etag:r,permanent:o=!1}){return await this.requireScopes(["lixblogs:blog:delete",...o?["lixblogs:blog:delete:permanent"]:[]]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}${o?"?permanent=true":""}`,{method:"DELETE",headers:{"if-match":r,...o?{"x-confirm-permanent-delete":e}:{}}})).payload.data}async restore(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:delete"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/restore`,{method:"POST",headers:{"if-match":r}})).payload.data}};async function ir(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var ee=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET";for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return ir(s)}catch(s){if(!n||a>0||s instanceof p||s?.code)throw s;await this.sleep(250)}throw new p("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async list(){return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request("/api/v1/orgs")).payload}async get(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}`)).payload.data}async collections(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/collections`)).payload.data}async members(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/members`)).payload.data}async targets(){await this.requireScopes(["lixblogs:organizations:read"]);let o=((await this.list())?.data||[]).filter(n=>n.canWrite),i=await Promise.all(o.map(async n=>{let a=[];try{a=await this.collections(n.id)}catch{a=[]}return{target:`org:${n.id}`,orgId:n.id,slug:n.slug,name:n.name,role:n.role,collections:a.map(s=>({id:s.id,slug:s.slug,name:s.name}))}}));return{personal:{target:"personal",name:"Personal Blog"},organizations:i}}};import{randomUUID as te}from"node:crypto";async function nr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e.data}var re=class{constructor(e){this.http=e}async request(e,r={}){let o=await this.http.request(e,{...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}});return nr(o)}async list(e){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`)}async invitations(){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request("/api/v1/collaboration/invitations")}async invite(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async role(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"PATCH",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async remove(e,{user:r,idempotencyKey:o=te()}={}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"DELETE",headers:{"idempotency-key":o},body:JSON.stringify({...r?{user:r}:{}})})}async resolveInvitation(e,{action:r,showOnProfile:o=!0,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request("/api/v1/collaboration/invitations",{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({blogId:e,action:r,showOnProfile:o})})}};async function sr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e}var oe=class{constructor(e){this.http=e}async query(e={}){let r=e.scope||"personal";await this.http.requireScopes(["lixblogs:analytics:read",...r.startsWith("org:")?["lixblogs:organizations:read"]:[]]);let o=new URLSearchParams({scope:r,range:e.range||(e.from||e.to?"custom":"30d"),dimension:e.dimension||"overview",limit:String(e.limit||20)});return e.from&&o.set("from",e.from),e.to&&o.set("to",e.to),e.cursor&&o.set("cursor",e.cursor),sr(await this.http.request(`/api/v1/analytics?${o}`,{headers:{accept:"application/json"}}))}};var ie=class{constructor(e){this.http=e}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async _request(e,r={}){let o=await this.http.request(e,r),i;try{i=await o.json()}catch{i=null}if(!o.ok||i?.error){let n=new Error(i?.error?.message||`Request failed with HTTP ${o.status}`);throw n.code=i?.error?.code||`http_${o.status}`,n.status=o.status,n.requestId=i?.error?.requestId||o.headers.get("x-request-id")||null,n.details=i?.error?.details||null,n}return i.data}async cloudinaryStatus(){return await this.requireScopes(["lixblogs:integrations:cloudinary:read"]),this._request("/api/v1/integrations/cloudinary")}async cloudinaryDisconnect(){return await this.requireScopes(["lixblogs:integrations:cloudinary:disconnect"]),this._request("/api/v1/integrations/cloudinary",{method:"DELETE"})}};var h=Object.freeze({OK:0,ERROR:1,USAGE:2,CONFLICT:3,AUTH:4,CONFIRMATION:5}),ar=Object.freeze({login:["auth","login"],logout:["auth","logout"],whoami:["auth","whoami"],profiles:["auth","profiles"],use:["auth","use"]});function He(t){let[e,...r]=t,o=ar[e];return o?[...o,...r]:t}function Ke(t,e="cli_error"){if(t&&typeof t=="object"&&t.error&&!Array.isArray(t.error))return t;let r=t&&typeof t=="object"?t:{message:String(t||"Command failed.")};return{ok:!1,error:{code:r.code||e,message:r.message||"Command failed.",hint:r.hint||null,requestId:r.requestId||null,...r.details?{details:r.details}:{}}}}function w(t,e){if(t.yes)return;let r=new Error(`${e} requires --yes in non-interactive operation.`);throw r.code="confirmation_required",r.hint="Review the operation, then run it again with --yes.",r.exitCode=h.CONFIRMATION,r}var S=Object.freeze({reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",violet:"\x1B[38;5;141m",green:"\x1B[38;5;42m"});function he(t=process.stdout,e=process.env){return!!t.isTTY&&e.NO_COLOR===void 0&&e.TERM!=="dumb"}function A(t,e,r){return r?`${e}${t}${S.reset}`:t}function We({url:t,code:e,expiresInSeconds:r,profile:o,interactive:i,color:n=!1}){let a=`${A("\u25C6",S.violet,n)} ${A("LixBlogs",S.bold,n)}`,s=i?"Press Enter to open here, or use the URL on another device.":"Open the URL in any browser and approve this device.";return["",` ${a}`,` ${A("Device login",S.dim,n)}`," \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",` URL ${t}`,` Code ${A(e,S.bold,n)}`,` Expires ${Math.ceil(r/60)} min`,o?` Profile ${o} ${A("(local credential slot)",S.dim,n)}`:` Profile ${A("your Accounts username after approval",S.dim,n)}`,"",` ${s}`," No localhost callback or exposed port is required.",""].join(`
5
+ `)}function Xe(t,e=!1){return` ${A("\u2713",S.green,e)} ${t}`}function Ye({input:t=process.stdin,open:e,url:r}){if(!t.isTTY||typeof e!="function")return()=>{};let o=()=>{Promise.resolve(e(r)).catch(()=>{})};return t.setEncoding?.("utf8"),t.once("data",o),t.resume?.(),()=>{t.off?.("data",o),t.pause?.()}}function C(t){return[{type:"text",text:t}]}function Ze(t){let e=String(t||"").replace(/\r\n/g,`
6
6
  `).split(`
7
- `),r=[],o=[],i=()=>{o.length&&(r.push({type:"paragraph",content:O(o.join(" ").trim())}),o=[])};for(let n=0;n<e.length;n+=1){let s=e[n].trim();if(!s){i();continue}let l=s.match(/^```([\w+-]*)/);if(l){i();let se=[];for(n+=1;n<e.length&&!/^```/.test(e[n].trim());)se.push(e[n++]);r.push(l[1].toLowerCase()==="mermaid"?{type:"mermaidBlock",props:{diagram:se.join(`
8
- `)}}:{type:"codeBlock",props:{language:l[1].toLowerCase()},content:O(se.join(`
9
- `))});continue}let u=s.match(/^(#{1,3})\s+(.+)/);if(u){i(),r.push({type:"heading",props:{level:String(u[1].length)},content:O(u[2])});continue}let y=s.match(/^[-*]\s+(.+)/);if(y){i(),r.push({type:"bulletListItem",content:O(y[1])});continue}let c=s.match(/^\d+\.\s+(.+)/);if(c){i(),r.push({type:"numberedListItem",content:O(c[1])});continue}let h=s.match(/^>\s?(.*)/);if(h){i(),r.push({type:"quote",content:O(h[1])});continue}let L=s.match(/^!\[([^\]]*)\]\((https:\/\/[^)]+)\)$/);if(L){i(),r.push({type:"image",props:{url:L[2],caption:L[1]}});continue}if(/^([-*_])\1{2,}$/.test(s)){i(),r.push({type:"divider"});continue}o.push(s)}return i(),r}function nr(t){return(t?.content||[]).map(e=>typeof e=="string"?e:e?.text||"").join("")}function ie(t){return(t||[]).map(e=>{let r=nr(e);return e.type==="heading"?`${"#".repeat(Number(e.props?.level)||1)} ${r}`:e.type==="bulletListItem"?`- ${r}`:e.type==="numberedListItem"?`1. ${r}`:e.type==="quote"?`> ${r}`:e.type==="codeBlock"?`\`\`\`${e.props?.language||""}
7
+ `),r=[],o=[],i=()=>{o.length&&(r.push({type:"paragraph",content:C(o.join(" ").trim())}),o=[])};for(let n=0;n<e.length;n+=1){let s=e[n].trim();if(!s){i();continue}let l=s.match(/^```([\w+-]*)/);if(l){i();let ae=[];for(n+=1;n<e.length&&!/^```/.test(e[n].trim());)ae.push(e[n++]);r.push(l[1].toLowerCase()==="mermaid"?{type:"mermaidBlock",props:{diagram:ae.join(`
8
+ `)}}:{type:"codeBlock",props:{language:l[1].toLowerCase()},content:C(ae.join(`
9
+ `))});continue}let u=s.match(/^(#{1,3})\s+(.+)/);if(u){i(),r.push({type:"heading",props:{level:String(u[1].length)},content:C(u[2])});continue}let f=s.match(/^[-*]\s+(.+)/);if(f){i(),r.push({type:"bulletListItem",content:C(f[1])});continue}let c=s.match(/^\d+\.\s+(.+)/);if(c){i(),r.push({type:"numberedListItem",content:C(c[1])});continue}let g=s.match(/^>\s?(.*)/);if(g){i(),r.push({type:"quote",content:C(g[1])});continue}let D=s.match(/^!\[([^\]]*)\]\((https:\/\/[^)]+)\)$/);if(D){i(),r.push({type:"image",props:{url:D[2],caption:D[1]}});continue}if(/^([-*_])\1{2,}$/.test(s)){i(),r.push({type:"divider"});continue}o.push(s)}return i(),r}function lr(t){return(t?.content||[]).map(e=>typeof e=="string"?e:e?.text||"").join("")}function ne(t){return(t||[]).map(e=>{let r=lr(e);return e.type==="heading"?`${"#".repeat(Number(e.props?.level)||1)} ${r}`:e.type==="bulletListItem"?`- ${r}`:e.type==="numberedListItem"?`1. ${r}`:e.type==="quote"?`> ${r}`:e.type==="codeBlock"?`\`\`\`${e.props?.language||""}
10
10
  ${r}
11
11
  \`\`\``:e.type==="mermaidBlock"?`\`\`\`mermaid
12
12
  ${e.props?.diagram||""}
13
13
  \`\`\``:e.type==="image"?`![${e.props?.caption||""}](${e.props?.url||""})`:e.type==="divider"?"---":r}).join(`
14
14
 
15
- `)}import{promises as B}from"node:fs";import{tmpdir as sr}from"node:os";import he from"node:path";import{spawn as ar}from"node:child_process";async function lr(t){let e="";t.setEncoding("utf8");for await(let r of t)e+=r;return e}async function cr(t="",e=process.env.EDITOR||process.env.VISUAL){if(!e)throw new Error("$EDITOR or $VISUAL must be set when using --editor.");let r=await B.mkdtemp(he.join(sr(),"lixblogs-")),o=he.join(r,"post.md");await B.writeFile(o,t,{mode:384});try{return await new Promise((i,n)=>{let a=ar(e,[o],{stdio:"inherit",shell:!0});a.once("error",n),a.once("exit",s=>s===0?i():n(new Error(`Editor exited with code ${s}.`)))}),await B.readFile(o,"utf8")}finally{await B.rm(r,{recursive:!0,force:!0})}}async function me(t,{stdin:e=process.stdin,initial:r=""}={}){let o=[t.file!==void 0,t.stdin,t.content!==void 0,t.editor].filter(Boolean).length;if(o>1)throw new Error("Use only one of --file, --stdin, --content, or --editor.");if(!o)return null;let i;return t.file!==void 0?i=await B.readFile(he.resolve(t.file),"utf8"):t.stdin?i=await lr(e):t.content!==void 0?i=t.content:i=await cr(r),{markdown:i,blocks:Xe(i)}}function ge(t){let e={},r={title:"title",subtitle:"subtitle",slug:"slug",emoji:"emoji",publication:"publishedAs",collection:"collectionId",cover:"coverUrl"};for(let[o,i]of Object.entries(r))t[o]!==void 0&&(e[i]=t[o]);return t.tag!==void 0&&(e.tags=t.tag),t["member-only"]&&(e.memberOnly=!0),t["no-member-only"]&&(e.memberOnly=!1),t.secret&&(e.secret=!0),t["not-secret"]&&(e.secret=!1),e}function ur(t){let e=[],r=o=>{for(let i of o||[]){for(let n of i?.content||[]){let a=typeof n=="string"?n:n?.text||"";e.push(...a.trim().split(/\s+/).filter(Boolean))}i?.children&&r(i.children)}};return r(t),e.length}function ne(t,{publishing:e=!1}={}){if(t.title!==void 0&&(typeof t.title!="string"||t.title.length>300))throw new Error("Title must be 300 characters or fewer.");if(t.subtitle!==void 0&&(typeof t.subtitle!="string"||t.subtitle.length>500))throw new Error("Subtitle must be 500 characters or fewer.");if(t.tags!==void 0&&(!Array.isArray(t.tags)||t.tags.length>5))throw new Error("Use at most five tags.");if(t.coverUrl&&!/^https:\/\//i.test(t.coverUrl))throw new Error("Cover URLs must use HTTPS.");if(t.publishedAs&&t.publishedAs!=="personal"&&!/^org:[^:]+$/.test(t.publishedAs))throw new Error("Publication must be personal or org:<id>.");if(t.content!==void 0){if(!Array.isArray(t.content))throw new Error("Blog content must be a block array.");if(Buffer.byteLength(JSON.stringify(t.content),"utf8")>15e5)throw new Error("Blog content exceeds the 1.5 MB limit.")}if(e){if(!t.title?.trim())throw new Error("A title is required before publishing.");if(ur(t.content)<20)throw new Error("A post needs at least 20 words before publishing.")}return t}import{promises as we}from"node:fs";import ye from"node:path";async function Ye({client:t,options:e}){return t.list({status:e.status,limit:e.limit,cursor:e.cursor})}async function be({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");let r=await t.get(e);return{...r,markdown:ie(r.content)}}async function Ze({client:t,options:e,stdin:r}){let o=await me(e,{stdin:r}),i={...ge(e),content:o?.blocks||[]};return ne(i),e["dry-run"]?{dryRun:!0,input:i,markdown:o?.markdown||""}:t.create(i,{idempotencyKey:e["idempotency-key"]})}async function Qe({client:t,id:e,options:r,stdin:o}){if(!e)throw new Error("A blog ID is required.");let i=await t.get(e),n=await me(r,{stdin:o,initial:ie(i.content)}),a={...ge(r),...n?{content:n.blocks}:{}};if(!Object.keys(a).length)throw new Error("No blog changes were provided.");if(ne(a),r["dry-run"])return{dryRun:!0,id:e,etag:i.etag,input:a,markdown:n?.markdown};try{return await t.update(e,a,{etag:r.etag||i.etag})}catch(s){if(!(s instanceof d)||s.code!=="revision_conflict")throw s;let l=await t.get(e),u=r.conflictDirectory||ye.resolve(".lixblogs-conflicts");await we.mkdir(u,{recursive:!0});let y=e.replace(/[^A-Za-z0-9._-]/g,"_"),c=ye.join(u,`${y}-local.json`),h=ye.join(u,`${y}-server.md`);throw await Promise.all([we.writeFile(c,JSON.stringify(a,null,2),{mode:384}),we.writeFile(h,ie(l.content),{mode:384})]),s.details={...s.details,localPath:c,serverPath:h,serverEtag:l.etag},s}}async function et({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return ne(o,{publishing:!0}),r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"published"}:(w(r,"Publishing this blog"),t.publish(e,{etag:r.etag||o.etag,idempotencyKey:r["idempotency-key"]}))}async function tt({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"draft"}:(w(r,"Unpublishing this blog"),t.unpublish(e,{etag:r.etag||o.etag}))}async function ve({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");if(!r.yes)throw new Error("Deletion requires --yes. Trash is the default; add --permanent for irreversible deletion.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,permanent:r.permanent}:t.delete(e,{etag:r.etag||o.etag,permanent:r.permanent})}async function rt({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,restoreTo:o.preDeleteStatus||"draft"}:(w(r,"Restoring this blog"),t.restore(e,{etag:r.etag||o.etag}))}async function ot({client:t}){return t.list()}async function it({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.get(e)}async function nt({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.collections(e)}async function st({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.members(e)}async function at({client:t}){return t.targets()}function T(t){if(!t)throw new Error("A blog ID is required.")}async function lt({client:t,id:e}){return T(e),t.list(e)}async function ct({client:t}){return t.invitations()}async function ut({client:t,id:e,options:r}){if(T(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"invite",blogId:e,user:r.user,role:r.role}:(w(r,"Inviting this collaborator"),t.invite(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function dt({client:t,id:e,options:r}){if(T(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"role",blogId:e,user:r.user,role:r.role}:(w(r,"Changing this collaborator role"),t.role(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function pt({client:t,id:e,options:r}){return T(e),r["dry-run"]?{dryRun:!0,action:"remove",blogId:e,user:r.user||"self"}:(w(r,"Removing this collaborator or invitation"),t.remove(e,{user:r.user,idempotencyKey:r["idempotency-key"]}))}async function ft({client:t,id:e,options:r}){return T(e),r["dry-run"]?{dryRun:!0,action:"accept",blogId:e,showOnProfile:!r["hide-on-profile"]}:(w(r,"Accepting this collaboration invitation"),t.resolveInvitation(e,{action:"accept",showOnProfile:!r["hide-on-profile"],idempotencyKey:r["idempotency-key"]}))}async function ht({client:t,id:e,options:r}){return T(e),r["dry-run"]?{dryRun:!0,action:"decline",blogId:e}:(w(r,"Declining this collaboration invitation"),t.resolveInvitation(e,{action:"decline",idempotencyKey:r["idempotency-key"]}))}import{access as dr,cp as pr,readFile as fr,readdir as hr}from"node:fs/promises";import b from"node:path";import{fileURLToPath as mr}from"node:url";var xe=b.dirname(mr(import.meta.url)),wt=b.basename(xe)==="dist"?b.resolve(xe,".."):b.resolve(xe,"../../.."),mt=b.join(wt,"skills"),gt=b.resolve(wt,"../..",".agents","skills");async function C(t){try{return await dr(t),!0}catch{return!1}}async function _e(){if(await C(mt))return mt;if(await C(gt))return gt;let t=new Error("No bundled LixBlogs skills were found. Reinstall @elixpo/lixblogs-cli.");throw t.code="skills_unavailable",t}function yt(t){if(!/^lixblogs-[a-z0-9-]+$/.test(t||"")){let e=new Error("A valid lixblogs-* skill name is required.");throw e.code="invalid_skill_name",e}return t}async function bt(t,e){let r=await fr(b.join(t,e,"SKILL.md"),"utf8"),o=r.match(/^description:\s*(.+)$/m)?.[1]||r.match(/^description:\s*>-\s*\n\s*(.+)$/m)?.[1]||"",i=r.match(/`@elixpo\/lixblogs-cli`\s+([0-9.]+)/)?.[1]||null;return{name:e,description:o.trim(),minimumCliVersion:i,content:r}}async function vt(){let t=await _e(),e=await hr(t,{withFileTypes:!0});return Promise.all(e.filter(r=>r.isDirectory()&&r.name.startsWith("lixblogs-")).map(r=>bt(t,r.name))).then(r=>r.map(({content:o,...i})=>i).sort((o,i)=>o.name.localeCompare(i.name)))}async function xt({name:t}){let e=await _e(),r=yt(t);if(!await C(b.join(e,r,"SKILL.md"))){let o=new Error(`Skill "${r}" is not bundled.`);throw o.code="skill_not_found",o}return bt(e,r)}async function _t({name:t,options:e}){let r=await _e(),o=yt(t),i=b.join(r,o);if(!await C(b.join(i,"SKILL.md"))){let s=new Error(`Skill "${o}" is not bundled.`);throw s.code="skill_not_found",s}let n=b.resolve(e.target||".agents/skills"),a=b.join(n,o);if(e["dry-run"])return{dryRun:!0,name:o,target:a,replace:await C(a)};if(await C(a)){if(!e.force){let s=new Error(`Skill already exists at ${a}.`);throw s.code="skill_exists",s.hint="Inspect the existing skill or re-run with --force --yes to replace it.",s}w(e,`Replacing ${a}`)}else w(e,`Installing ${o} into ${n}`);return await pr(i,a,{recursive:!0,force:!!e.force}),{installed:!0,name:o,target:a}}import{writeFile as gr}from"node:fs/promises";var wr=new Set(["overview","timeline","posts","sources","devices","countries"]),yr=new Set(["7d","30d","90d","12m","custom"]);function kt(t={}){let e=t.dimension||"overview",r=t.range||(t.from||t.to?"custom":"30d");if(!wr.has(e))throw new Error(`Unsupported analytics dimension: ${e}.`);if(!yr.has(r))throw new Error(`Unsupported analytics range: ${r}.`);if(r==="custom"&&(!t.from||!t.to))throw new Error("Custom analytics ranges require --from and --to.");return{scope:t.scope?.[0]||t.publication||"personal",range:r,from:t.from,to:t.to,dimension:e,limit:t.limit,cursor:t.cursor}}async function St({client:t,options:e}){return t.query(kt(e))}function It(t){let e=t==null?"":typeof t=="object"?JSON.stringify(t):String(t);return/[",\n]/.test(e)?`"${e.replaceAll('"','""')}"`:e}function Et(t){let e=t?.data?.values;return Array.isArray(e)?e:e?.labels&&Array.isArray(e.labels)?e.labels.map((r,o)=>({label:r,views:e.views?.[o]||0,reads:e.reads?.[o]||0})):e?.totals?Object.entries(e.totals).map(([r,o])=>({metric:r,value:o,previous:e.previous?.[r],change:e.changes?.[r]})):[]}async function At({client:t,options:e}){if(!e.output)throw new Error("Analytics export requires --output <file>.");let r=e.format||"json";if(!["json","csv"].includes(r))throw new Error("Analytics export format must be json or csv.");let o=await t.query(kt(e)),i;if(r==="json")i=`${JSON.stringify(o,null,2)}
16
- `;else{let n=Et(o),a=[...new Set(n.flatMap(s=>Object.keys(s)))];i=`${a.map(It).join(",")}
17
- ${n.map(s=>a.map(l=>It(s[l])).join(",")).join(`
15
+ `)}import{promises as M}from"node:fs";import{tmpdir as cr}from"node:os";import me from"node:path";import{spawn as ur}from"node:child_process";async function dr(t){let e="";t.setEncoding("utf8");for await(let r of t)e+=r;return e}async function fr(t="",e=process.env.EDITOR||process.env.VISUAL){if(!e)throw new Error("$EDITOR or $VISUAL must be set when using --editor.");let r=await M.mkdtemp(me.join(cr(),"lixblogs-")),o=me.join(r,"post.md");await M.writeFile(o,t,{mode:384});try{return await new Promise((i,n)=>{let a=ur(e,[o],{stdio:"inherit",shell:!0});a.once("error",n),a.once("exit",s=>s===0?i():n(new Error(`Editor exited with code ${s}.`)))}),await M.readFile(o,"utf8")}finally{await M.rm(r,{recursive:!0,force:!0})}}async function ge(t,{stdin:e=process.stdin,initial:r=""}={}){let o=[t.file!==void 0,t.stdin,t.content!==void 0,t.editor].filter(Boolean).length;if(o>1)throw new Error("Use only one of --file, --stdin, --content, or --editor.");if(!o)return null;let i;return t.file!==void 0?i=await M.readFile(me.resolve(t.file),"utf8"):t.stdin?i=await dr(e):t.content!==void 0?i=t.content:i=await fr(r),{markdown:i,blocks:Ze(i)}}function ye(t){let e={},r={title:"title",subtitle:"subtitle",slug:"slug",emoji:"emoji",publication:"publishedAs",collection:"collectionId",cover:"coverUrl"};for(let[o,i]of Object.entries(r))t[o]!==void 0&&(e[i]=t[o]);return t.tag!==void 0&&(e.tags=t.tag),t["member-only"]&&(e.memberOnly=!0),t["no-member-only"]&&(e.memberOnly=!1),t.secret&&(e.secret=!0),t["not-secret"]&&(e.secret=!1),e}function pr(t){let e=[],r=o=>{for(let i of o||[]){for(let n of i?.content||[]){let a=typeof n=="string"?n:n?.text||"";e.push(...a.trim().split(/\s+/).filter(Boolean))}i?.children&&r(i.children)}};return r(t),e.length}function se(t,{publishing:e=!1}={}){if(t.title!==void 0&&(typeof t.title!="string"||t.title.length>300))throw new Error("Title must be 300 characters or fewer.");if(t.subtitle!==void 0&&(typeof t.subtitle!="string"||t.subtitle.length>500))throw new Error("Subtitle must be 500 characters or fewer.");if(t.tags!==void 0&&(!Array.isArray(t.tags)||t.tags.length>5))throw new Error("Use at most five tags.");if(t.coverUrl&&!/^https:\/\//i.test(t.coverUrl))throw new Error("Cover URLs must use HTTPS.");if(t.publishedAs&&t.publishedAs!=="personal"&&!/^org:[^:]+$/.test(t.publishedAs))throw new Error("Publication must be personal or org:<id>.");if(t.content!==void 0){if(!Array.isArray(t.content))throw new Error("Blog content must be a block array.");if(Buffer.byteLength(JSON.stringify(t.content),"utf8")>15e5)throw new Error("Blog content exceeds the 1.5 MB limit.")}if(e){if(!t.title?.trim())throw new Error("A title is required before publishing.");if(pr(t.content)<20)throw new Error("A post needs at least 20 words before publishing.")}return t}import{promises as we}from"node:fs";import be from"node:path";async function Qe({client:t,options:e}){return t.list({status:e.status,limit:e.limit,cursor:e.cursor})}async function ve({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");let r=await t.get(e);return{...r,markdown:ne(r.content)}}async function et({client:t,options:e,stdin:r}){let o=await ge(e,{stdin:r}),i={...ye(e),content:o?.blocks||[]};return se(i),e["dry-run"]?{dryRun:!0,input:i,markdown:o?.markdown||""}:t.create(i,{idempotencyKey:e["idempotency-key"]})}async function tt({client:t,id:e,options:r,stdin:o}){if(!e)throw new Error("A blog ID is required.");let i=await t.get(e),n=await ge(r,{stdin:o,initial:ne(i.content)}),a={...ye(r),...n?{content:n.blocks}:{}};if(!Object.keys(a).length)throw new Error("No blog changes were provided.");if(se(a),r["dry-run"])return{dryRun:!0,id:e,etag:i.etag,input:a,markdown:n?.markdown};try{return await t.update(e,a,{etag:r.etag||i.etag})}catch(s){if(!(s instanceof p)||s.code!=="revision_conflict")throw s;let l=await t.get(e),u=r.conflictDirectory||be.resolve(".lixblogs-conflicts");await we.mkdir(u,{recursive:!0});let f=e.replace(/[^A-Za-z0-9._-]/g,"_"),c=be.join(u,`${f}-local.json`),g=be.join(u,`${f}-server.md`);throw await Promise.all([we.writeFile(c,JSON.stringify(a,null,2),{mode:384}),we.writeFile(g,ne(l.content),{mode:384})]),s.details={...s.details,localPath:c,serverPath:g,serverEtag:l.etag},s}}async function rt({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return se(o,{publishing:!0}),r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"published"}:(w(r,"Publishing this blog"),t.publish(e,{etag:r.etag||o.etag,idempotencyKey:r["idempotency-key"]}))}async function ot({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"draft"}:(w(r,"Unpublishing this blog"),t.unpublish(e,{etag:r.etag||o.etag}))}async function xe({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");if(!r.yes)throw new Error("Deletion requires --yes. Trash is the default; add --permanent for irreversible deletion.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,permanent:r.permanent}:t.delete(e,{etag:r.etag||o.etag,permanent:r.permanent})}async function it({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,restoreTo:o.preDeleteStatus||"draft"}:(w(r,"Restoring this blog"),t.restore(e,{etag:r.etag||o.etag}))}async function nt({client:t}){return t.list()}async function st({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.get(e)}async function at({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.collections(e)}async function lt({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.members(e)}async function ct({client:t}){return t.targets()}function L(t){if(!t)throw new Error("A blog ID is required.")}async function ut({client:t,id:e}){return L(e),t.list(e)}async function dt({client:t}){return t.invitations()}async function ft({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"invite",blogId:e,user:r.user,role:r.role}:(w(r,"Inviting this collaborator"),t.invite(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function pt({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"role",blogId:e,user:r.user,role:r.role}:(w(r,"Changing this collaborator role"),t.role(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function ht({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"remove",blogId:e,user:r.user||"self"}:(w(r,"Removing this collaborator or invitation"),t.remove(e,{user:r.user,idempotencyKey:r["idempotency-key"]}))}async function mt({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"accept",blogId:e,showOnProfile:!r["hide-on-profile"]}:(w(r,"Accepting this collaboration invitation"),t.resolveInvitation(e,{action:"accept",showOnProfile:!r["hide-on-profile"],idempotencyKey:r["idempotency-key"]}))}async function gt({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"decline",blogId:e}:(w(r,"Declining this collaboration invitation"),t.resolveInvitation(e,{action:"decline",idempotencyKey:r["idempotency-key"]}))}import{access as hr,cp as mr,readFile as gr,readdir as yr}from"node:fs/promises";import b from"node:path";import{fileURLToPath as wr}from"node:url";var _e=b.dirname(wr(import.meta.url)),bt=b.basename(_e)==="dist"?b.resolve(_e,".."):b.resolve(_e,"../../.."),yt=b.join(bt,"skills"),wt=b.resolve(bt,"../..",".agents","skills");async function j(t){try{return await hr(t),!0}catch{return!1}}async function Ie(){if(await j(yt))return yt;if(await j(wt))return wt;let t=new Error("No bundled LixBlogs skills were found. Reinstall @elixpo/lixblogs-cli.");throw t.code="skills_unavailable",t}function vt(t){if(!/^lixblogs-[a-z0-9-]+$/.test(t||"")){let e=new Error("A valid lixblogs-* skill name is required.");throw e.code="invalid_skill_name",e}return t}async function xt(t,e){let r=await gr(b.join(t,e,"SKILL.md"),"utf8"),o=r.match(/^description:\s*(.+)$/m)?.[1]||r.match(/^description:\s*>-\s*\n\s*(.+)$/m)?.[1]||"",i=r.match(/`@elixpo\/lixblogs-cli`\s+([0-9.]+)/)?.[1]||null;return{name:e,description:o.trim(),minimumCliVersion:i,content:r}}async function _t(){let t=await Ie(),e=await yr(t,{withFileTypes:!0});return Promise.all(e.filter(r=>r.isDirectory()&&r.name.startsWith("lixblogs-")).map(r=>xt(t,r.name))).then(r=>r.map(({content:o,...i})=>i).sort((o,i)=>o.name.localeCompare(i.name)))}async function It({name:t}){let e=await Ie(),r=vt(t);if(!await j(b.join(e,r,"SKILL.md"))){let o=new Error(`Skill "${r}" is not bundled.`);throw o.code="skill_not_found",o}return xt(e,r)}async function Et({name:t,options:e}){let r=await Ie(),o=vt(t),i=b.join(r,o);if(!await j(b.join(i,"SKILL.md"))){let s=new Error(`Skill "${o}" is not bundled.`);throw s.code="skill_not_found",s}let n=b.resolve(e.target||".agents/skills"),a=b.join(n,o);if(e["dry-run"])return{dryRun:!0,name:o,target:a,replace:await j(a)};if(await j(a)){if(!e.force){let s=new Error(`Skill already exists at ${a}.`);throw s.code="skill_exists",s.hint="Inspect the existing skill or re-run with --force --yes to replace it.",s}w(e,`Replacing ${a}`)}else w(e,`Installing ${o} into ${n}`);return await mr(i,a,{recursive:!0,force:!!e.force}),{installed:!0,name:o,target:a}}import{writeFile as br}from"node:fs/promises";var vr=new Set(["overview","timeline","posts","sources","devices","countries"]),xr=new Set(["7d","30d","90d","12m","custom"]);function $t(t={}){let e=t.dimension||"overview",r=t.range||(t.from||t.to?"custom":"30d");if(!vr.has(e))throw new Error(`Unsupported analytics dimension: ${e}.`);if(!xr.has(r))throw new Error(`Unsupported analytics range: ${r}.`);if(r==="custom"&&(!t.from||!t.to))throw new Error("Custom analytics ranges require --from and --to.");return{scope:t.scope?.[0]||t.publication||"personal",range:r,from:t.from,to:t.to,dimension:e,limit:t.limit,cursor:t.cursor}}async function qt({client:t,options:e}){return t.query($t(e))}function kt(t){let e=t==null?"":typeof t=="object"?JSON.stringify(t):String(t);return/[",\n]/.test(e)?`"${e.replaceAll('"','""')}"`:e}function St(t){let e=t?.data?.values;return Array.isArray(e)?e:e?.labels&&Array.isArray(e.labels)?e.labels.map((r,o)=>({label:r,views:e.views?.[o]||0,reads:e.reads?.[o]||0})):e?.totals?Object.entries(e.totals).map(([r,o])=>({metric:r,value:o,previous:e.previous?.[r],change:e.changes?.[r]})):[]}async function At({client:t,options:e}){if(!e.output)throw new Error("Analytics export requires --output <file>.");let r=e.format||"json";if(!["json","csv"].includes(r))throw new Error("Analytics export format must be json or csv.");let o=await t.query($t(e)),i;if(r==="json")i=`${JSON.stringify(o,null,2)}
16
+ `;else{let n=St(o),a=[...new Set(n.flatMap(s=>Object.keys(s)))];i=`${a.map(kt).join(",")}
17
+ ${n.map(s=>a.map(l=>kt(s[l])).join(",")).join(`
18
18
  `)}
19
- `}return await gr(e.output,i,{encoding:"utf8",flag:"wx"}),{output:e.output,format:r,rows:Et(o).length}}import $t from"node:fs";import br from"node:path";import vr from"node:os";async function Pt({client:t,target:e}){if(e==="cloudinary"){let r=await t.http.fetch("/api/integrations/cloudinary",{method:"DELETE"});if(!r.ok)throw r.status===409?new Error("This connection still owns blog media. Delete those assets from the Media tab before removing it."):new Error("Failed to disconnect Cloudinary");return{provider:e}}else if(e==="pollinations"){let r=br.join(vr.homedir(),".pollinations","credentials.json");if($t.existsSync(r))try{$t.unlinkSync(r)}catch{throw new Error("Failed to clear local Pollinations credentials")}return{provider:e}}else throw new Error(`Unknown integration: ${e}`)}var Ir={profile:{type:"string"},env:{type:"string"},json:{type:"boolean",default:!1},quiet:{type:"boolean",default:!1},yes:{type:"boolean",short:"y",default:!1},"allow-insecure-fallback":{type:"boolean",default:!1},"auth-provider":{type:"string"},"accounts-url":{type:"string"},"api-url":{type:"string"},"client-id":{type:"string"},audience:{type:"string"},scope:{type:"string",multiple:!0},open:{type:"boolean",default:!1},status:{type:"string"},limit:{type:"string"},cursor:{type:"string"},range:{type:"string"},from:{type:"string"},to:{type:"string"},dimension:{type:"string"},format:{type:"string"},output:{type:"string"},file:{type:"string"},stdin:{type:"boolean",default:!1},content:{type:"string"},editor:{type:"boolean",default:!1},title:{type:"string"},subtitle:{type:"string"},slug:{type:"string"},tag:{type:"string",multiple:!0},emoji:{type:"string"},publication:{type:"string"},collection:{type:"string"},cover:{type:"string"},"member-only":{type:"boolean",default:!1},"no-member-only":{type:"boolean",default:!1},secret:{type:"boolean",default:!1},"not-secret":{type:"boolean",default:!1},"dry-run":{type:"boolean",default:!1},"no-input":{type:"boolean",default:!1},etag:{type:"string"},permanent:{type:"boolean",default:!1},"idempotency-key":{type:"string"},user:{type:"string"},role:{type:"string"},"hide-on-profile":{type:"boolean",default:!1},target:{type:"string"},force:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1}},Er=`lixblogs \u2014 LixBlogs CLI
19
+ `}return await br(e.output,i,{encoding:"utf8",flag:"wx"}),{output:e.output,format:r,rows:St(o).length}}import Rt from"node:fs";import _r from"node:path";import Ir from"node:os";async function Pt({client:t,target:e}){if(e==="cloudinary"){let r=await t.http.fetch("/api/integrations/cloudinary",{method:"DELETE"});if(!r.ok)throw r.status===409?new Error("This connection still owns blog media. Delete those assets from the Media tab before removing it."):new Error("Failed to disconnect Cloudinary");return{provider:e}}else if(e==="pollinations"){let r=_r.join(Ir.homedir(),".pollinations","credentials.json");if(Rt.existsSync(r))try{Rt.unlinkSync(r)}catch{throw new Error("Failed to clear local Pollinations credentials")}return{provider:e}}else throw new Error(`Unknown integration: ${e}`)}async function Tt({integrationsClient:t,confirmed:e}){if(e!==!0)return{ok:!1,reason:"Disconnect was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};try{return{ok:!0,data:await t.cloudinaryDisconnect()}}catch(r){return{ok:!1,error:r}}}async function Ot({integrationsClient:t}){try{return{ok:!0,data:await t.cloudinaryStatus()}}catch(e){return{ok:!1,error:e}}}var Sr={profile:{type:"string"},env:{type:"string"},json:{type:"boolean",default:!1},quiet:{type:"boolean",default:!1},yes:{type:"boolean",short:"y",default:!1},"allow-insecure-fallback":{type:"boolean",default:!1},"auth-provider":{type:"string"},"accounts-url":{type:"string"},"api-url":{type:"string"},"client-id":{type:"string"},audience:{type:"string"},scope:{type:"string",multiple:!0},open:{type:"boolean",default:!1},status:{type:"string"},limit:{type:"string"},cursor:{type:"string"},range:{type:"string"},from:{type:"string"},to:{type:"string"},dimension:{type:"string"},format:{type:"string"},output:{type:"string"},file:{type:"string"},stdin:{type:"boolean",default:!1},content:{type:"string"},editor:{type:"boolean",default:!1},title:{type:"string"},subtitle:{type:"string"},slug:{type:"string"},tag:{type:"string",multiple:!0},emoji:{type:"string"},publication:{type:"string"},collection:{type:"string"},cover:{type:"string"},"member-only":{type:"boolean",default:!1},"no-member-only":{type:"boolean",default:!1},secret:{type:"boolean",default:!1},"not-secret":{type:"boolean",default:!1},"dry-run":{type:"boolean",default:!1},"no-input":{type:"boolean",default:!1},etag:{type:"string"},permanent:{type:"boolean",default:!1},"idempotency-key":{type:"string"},user:{type:"string"},role:{type:"string"},"hide-on-profile":{type:"boolean",default:!1},target:{type:"string"},force:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1}},$r=`lixblogs \u2014 LixBlogs CLI
20
20
 
21
21
  Usage:
22
22
  lixblogs login [--profile <name>] [--open]
@@ -55,10 +55,12 @@ Usage:
55
55
  lixblogs collab decline <blog-id> --yes
56
56
  lixblogs analytics query [--scope personal|org:<id>] [--range 30d] [--dimension overview]
57
57
  lixblogs analytics export --output <file> [--format json|csv] [query options]
58
+ lixblogs integrations cloudinary-status [--json]
59
+ lixblogs integrations cloudinary-disconnect --yes [--json]
58
60
  lixblogs skill list [--json]
59
61
  lixblogs skill inspect <name> [--json]
60
62
  lixblogs skill install <name> [--target <directory>] [--dry-run] --yes
61
- lixblogs disconnect cloudinary
63
+ lixblogs disconnect cloudinary --yes
62
64
  lixblogs disconnect pollinations
63
65
 
64
66
  Global flags:
@@ -89,16 +91,16 @@ Global flags:
89
91
  Machine mode:
90
92
  --json --no-input produces stable JSON on stdout, diagnostics on stderr, and
91
93
  never prompts. Publishing and destructive state changes require --yes.
92
- `,kr=["openid","profile","email","lixblogs:profile:read","lixblogs:blog:read"];function P(t){return{profile:t.profile,env:t.env,authProvider:t["auth-provider"],accountsUrl:t["accounts-url"],apiUrl:t["api-url"],clientId:t["client-id"],audience:t.audience}}async function M(t,e){return t.profileExplicit?x(t.profile):await e.getActive()||x(t.profile)}async function Ie(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",r=process.platform==="win32"?["/c","start","",t]:[t],o=_r(e,r,{detached:!0,stdio:"ignore"});o.on("error",()=>{}),o.unref()}function m(t,e){t.json&&process.stdout.write(Z(e)+`
93
- `)}function p(t,e,r=f.ERROR){let o=e&&typeof e=="object"?e:{message:String(e)},i=q(o.message),n=Je({...o,message:i});t.json?process.stdout.write(Z(n)+`
94
+ `,qr=["openid","profile","email","lixblogs:profile:read","lixblogs:blog:read"];function $(t){return{profile:t.profile,env:t.env,authProvider:t["auth-provider"],accountsUrl:t["accounts-url"],apiUrl:t["api-url"],clientId:t["client-id"],audience:t.audience}}async function U(t,e){return t.profileExplicit?_(t.profile):await e.getActive()||_(t.profile)}async function ke(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",r=process.platform==="win32"?["/c","start","",t]:[t],o=kr(e,r,{detached:!0,stdio:"ignore"});o.on("error",()=>{}),o.unref()}function m(t,e){t.json&&process.stdout.write(Z(e)+`
95
+ `)}function d(t,e,r=h.ERROR){let o=e&&typeof e=="object"?e:{message:String(e)},i=T(o.message),n=Ke({...o,message:i});t.json?process.stdout.write(Z(n)+`
94
96
  `):t.quiet||(process.stderr.write(`Error: ${i}
95
97
  `),o.hint&&process.stderr.write(`Hint: ${o.hint}
96
98
  `),o.requestId&&process.stderr.write(`Request: ${o.requestId}
97
- `)),process.exitCode=o.exitCode||r}async function S(t,e){try{return await Ce({allowInsecureFallback:t["allow-insecure-fallback"],profileRegistry:e})}catch(r){return p(t,`${r.message}${t["allow-insecure-fallback"]?"":" Re-run with --allow-insecure-fallback to opt in to non-persistent storage instead."}`),null}}async function Ot(t){let e=_({flags:P(t)}),r=new g,o=x(e.profile),i=t.scope?.length?[...t.scope]:[...kr];!e.profileExplicit&&!i.includes("lixblogs:profile:read")&&i.push("lixblogs:profile:read");let n;try{n=U(e)}catch(u){return p(t,u.message)}let a=await S(t,r);if(!a)return;let s=()=>{},l;try{l=await je({provider:n,credentialStore:a,profileId:o,scopes:i,openBrowser:t.open?Ie:void 0,resolveProfileId:e.profileExplicit?void 0:({accessToken:u})=>Fe({accessToken:u,apiBaseUrl:e.apiBaseUrl}),onStatus:u=>{if(t.json){u.type!=="pending"&&m(t,{event:u.type,...u});return}if(!t.quiet)if(u.type==="verification_pending"){let y=u.verificationUriComplete||u.verificationUri,c=!!process.stdin.isTTY&&!t["no-input"];process.stdout.write(He({url:y,code:u.userCode,expiresInSeconds:u.expiresInSeconds,profile:e.profileExplicit?o:null,interactive:c,color:fe()})),c&&!t.open&&(s=We({input:process.stdin,open:Ie,url:y}))}else u.type==="approved"?console.log(Ke("Access approved by Elixpo Accounts.",fe())):u.type==="denied"?console.log(" Access denied."):u.type==="expired"&&console.log(" Device code expired.")}})}finally{s()}if(!l.ok)return p(t,l.reason);await r.add(l.profileId),await r.setActive(l.profileId),m(t,{ok:!0,profile:l.profileId}),!t.json&&!t.quiet&&(console.log(` Credentials saved to local profile "${l.profileId}".`),console.log(" Tip: add another account with `lixblogs login`, list accounts with `lixblogs profiles`,"),console.log(" and switch with `lixblogs use <username>`."))}async function Sr(t){let e=_({flags:P(t)}),r=new g,o=await M(e,r),i=await S(t,r);if(!i)return;let n=await Ue({credentialStore:i,profileId:o});if(m(t,n),!t.json)for(let a of n)a.loggedIn?console.log(`${a.profileId}: logged in${a.expired?" (expired)":""} \u2014 scopes: ${a.scopes.join(", ")}`):console.log(`${a.profileId}: not logged in`)}async function F(t){let e=_({flags:P(t)}),r=new g,o=await M(e,r),i=await S(t,r);if(!i)return null;let n;try{n=U(e)}catch(s){return p(t,s),null}let a=new D({provider:n,credentialStore:i,profileId:o,apiBaseUrl:e.apiBaseUrl});return{client:new N(a),http:a,config:e,credentialStore:i,profileId:o}}async function Ar(t){let e=await F(t);if(e)try{let[r,o]=await Promise.all([e.client.whoami(),e.credentialStore.get(e.profileId)]),i={ok:!0,profile:e.profileId,environment:e.config.environment,identity:r,scopes:o?.scopes||[],expiresAt:o?.expiresAt?new Date(o.expiresAt).toISOString():null,expired:o?Date.now()>=o.expiresAt:!0};m(t,i),!t.json&&!t.quiet&&(console.log(`${r.displayName||r.username} (@${r.username})`),console.log(`Profile: ${e.profileId} \xB7 ${i.environment}`),console.log(`Scopes: ${i.scopes.join(", ")||"none"}`),console.log(`Expires: ${i.expiresAt||"unknown"}`))}catch(r){p(t,r,r.status===401||r.status===403?f.AUTH:f.ERROR)}}async function $r(t){let e=_({flags:P(t)}),r=new URL("/register",e.accountsBaseUrl).toString();if(t["no-input"]){m(t,{ok:!0,registrationUrl:r,next:"lixblogs login"}),!t.json&&!t.quiet&&console.log(r);return}await Ie(r),t.quiet||console.log(`Create your account at ${r}, then approve the device login.`),await Ot(t)}async function Pr(t){let e=_({flags:P(t)}),r=new g,o=await M(e,r),i=await S(t,r);if(!i)return;let n=await De({credentialStore:i,profileId:o});m(t,n),!t.json&&!t.quiet&&console.log(`Logged out profile "${o}".`)}async function Rr(t){let e=_({flags:P(t)}),r=new g,o=await M(e,r);if(!t.yes)return p(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let i;try{i=U(e)}catch(s){return p(t,s.message)}let n=await S(t,r);if(!n)return;let a=await Ne({provider:i,credentialStore:n,profileId:o,confirmed:!0});if(!a.ok)return p(t,a.reason);m(t,a),!t.json&&!t.quiet&&console.log(`Revoked and logged out profile "${o}".`)}async function qr(t){let e=new g,r=await S(t,e);if(!r)return;let o=await Be({credentialStore:r,profileRegistry:e});if(m(t,o),!t.json){o.profiles.length||console.log("No profiles. Run `lixblogs auth login` first.");for(let i of o.profiles)console.log(`${i.active?"*":" "} ${i.profileId}${i.expired?" (expired)":""}`)}}async function Or(t,e){let r;try{r=x(e[0])}catch(a){return p(t,a.message)}let o=new g,i=await S(t,o);if(!i)return;let n=await Me({credentialStore:i,profileRegistry:o,profileId:r});if(!n.ok)return p(t,n.reason);m(t,n),!t.json&&!t.quiet&&console.log(`Using profile "${r}".`)}var Tt={list:Ye,get:be,preview:be,create:Ze,edit:Qe,publish:et,unpublish:tt,delete:ve,trash:ve,restore:rt},Ct={list:ot,get:it,collections:nt,members:st,targets:at},Lt={list:lt,invitations:ct,invite:ut,role:dt,remove:pt,accept:ft,decline:ht},jt={list:({options:t})=>vt(t),inspect:({id:t})=>xt({name:t}),install:({id:t,options:e})=>_t({name:t,options:e})},Ut={query:St,export:At};async function Tr(t,e,r){let o=_({flags:P(t)}),i=new g,n=await M(o,i),a=await S(t,i);if(!a)return;let s;try{s=U(o)}catch(c){return p(t,c.message)}let l=new D({provider:s,credentialStore:a,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new N(l),y={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let c=await Tt[r]({client:u,id:e[0],options:y,stdin:process.stdin});if(m(t,{ok:!0,...c}),!t.json&&!t.quiet)if(r==="list"){for(let h of c.data||[])console.log(`${h.id} ${h.status} ${h.title||"(untitled)"}`);c.meta?.nextCursor&&console.log(`Next cursor: ${c.meta.nextCursor}`)}else r==="get"?console.log(`${c.title||"(untitled)"} [${c.status}]
98
- ${c.markdown||""}`):c.dryRun?console.log(`Dry run: ${r} validated; no changes sent.`):console.log(c.url||`${r} completed for ${c.id}.`)}catch(c){if(t.json&&c instanceof d){process.stdout.write(Z({ok:!1,error:{code:c.code,message:c.message,requestId:c.requestId,details:c.details}})+`
99
- `),process.exitCode=c.status===412?3:1;return}return p(t,c,c.status===412?f.CONFLICT:f.ERROR)}}async function Cr(t,e,r){let o=await F(t);if(!o)return;let i=new ee(o.http);try{let n=await Ct[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(r==="targets"){console.log("personal Personal Blog");for(let s of n.organizations||[]){console.log(`${s.target} ${s.role} ${s.name}`);for(let l of s.collections||[])console.log(` collection:${l.id} ${l.name}`)}return}let a=r==="list"?n.data||[]:Array.isArray(n)?n:[n];for(let s of a)console.log([s.id||s.userId||s.orgId,s.role,s.slug||s.username,s.name||s.displayName].filter(Boolean).join(" "))}catch(n){p(t,n,n.status===401||n.status===403?f.AUTH:f.ERROR)}}async function Lr(t,e,r){let o=await F(t);if(!o)return;let i=new re(o.http);try{let n=await Lt[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(n.dryRun){console.log(`Dry run: ${n.action} validated; no changes sent.`);return}let a=r==="invitations"?n:r==="list"?n.collaborators||[]:[n];for(let s of a)console.log([s.blogId||s.userId,s.status,s.role,s.username||s.title,s.notificationState].filter(Boolean).join(" "))}catch(n){p(t,n,n.status===401||n.status===403?f.AUTH:f.ERROR)}}async function jr(t,e,r){try{let o=await jt[r]({id:e[0],options:t});if(m(t,{ok:!0,data:o}),t.json||t.quiet)return;if(r==="list")for(let i of o)console.log(`${i.name} CLI >= ${i.minimumCliVersion||"unknown"} ${i.description}`);else r==="inspect"?process.stdout.write(o.content):o.dryRun?console.log(`Dry run: install ${o.name} to ${o.target}${o.replace?" (replace)":""}.`):console.log(`Installed ${o.name} at ${o.target}.`)}catch(o){p(t,o)}}async function Ur(t,e,r){let o=await F(t);if(!o)return;let i=new oe(o.http),n={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let a=await Ut[r]({client:i,options:n});if(m(t,{ok:!0,data:a}),t.json||t.quiet)return;if(r==="export"){console.log(`Exported ${a.rows} rows to ${a.output}.`);return}let s=a.data;if(console.log(`${s.scope.label} \xB7 ${s.dimension} \xB7 ${s.range.key}`),s.dimension==="overview")for(let[l,u]of Object.entries(s.values.totals))console.log(`${l} ${u} ${s.values.changes[l]}%`);else if(s.dimension==="timeline")s.values.labels.forEach((l,u)=>console.log(`${l} ${s.values.views[u]} ${s.values.reads[u]}`));else{for(let l of s.values)console.log(Object.values(l).join(" "));a.meta?.nextCursor&&console.log(`Next cursor: ${a.meta.nextCursor}`)}}catch(a){p(t,a,a.status===401||a.status===403?f.AUTH:f.ERROR)}}async function Rt(t,e,r){let o=await F(t);if(!o)return;let i=o.client;try{let n=await Pt({client:i,target:r});m(t,{ok:!0,data:n}),!t.json&&!t.quiet&&console.log(`Disconnected ${n.provider}.`)}catch(n){p(t,n)}}var qt={auth:{login:Ot,status:Sr,whoami:Ar,logout:Pr,revoke:Rr,profiles:qr,use:Or},blog:Object.fromEntries(Object.keys(Tt).map(t=>[t,(e,r)=>Tr(e,r,t)])),org:Object.fromEntries(Object.keys(Ct).map(t=>[t,(e,r)=>Cr(e,r,t)])),collab:Object.fromEntries(Object.keys(Lt).map(t=>[t,(e,r)=>Lr(e,r,t)])),skill:Object.fromEntries(Object.keys(jt).map(t=>[t,(e,r)=>jr(e,r,t)])),analytics:Object.fromEntries(Object.keys(Ut).map(t=>[t,(e,r)=>Ur(e,r,t)])),disconnect:{cloudinary:(t,e)=>Rt(t,e,"cloudinary"),pollinations:(t,e)=>Rt(t,e,"pollinations")}};async function Dr(){let t,e;try{({values:t,positionals:e}=xr({args:process.argv.slice(2),options:Ir,allowPositionals:!0,strict:!0}))}catch(a){process.stderr.write(`Error: Invalid flag. ${a.message}
100
- `),process.exitCode=f.USAGE;return}if(t.help||e.length===0){process.stdout.write(Er);return}if(e[0]==="register"){await $r(t);return}e=Ve(e);let[r,o]=e,i=qt[r];if(!i){process.stderr.write(`Error: Unknown command category "${r}".
101
- `),process.stderr.write(`Available categories: ${Object.keys(qt).join(", ")}
102
- `),process.exitCode=f.USAGE;return}let n=i[o];if(!n){process.stderr.write(`Error: Unknown ${r} command "${o}".
99
+ `)),process.exitCode=o.exitCode||r}async function I(t,e){try{return await je({allowInsecureFallback:t["allow-insecure-fallback"],profileRegistry:e})}catch(r){return d(t,`${r.message}${t["allow-insecure-fallback"]?"":" Re-run with --allow-insecure-fallback to opt in to non-persistent storage instead."}`),null}}async function Lt(t){let e=x({flags:$(t)}),r=new y,o=_(e.profile),i=t.scope?.length?[...t.scope]:[...qr];!e.profileExplicit&&!i.includes("lixblogs:profile:read")&&i.push("lixblogs:profile:read");let n;try{n=P(e)}catch(u){return d(t,u.message)}let a=await I(t,r);if(!a)return;let s=()=>{},l;try{l=await De({provider:n,credentialStore:a,profileId:o,scopes:i,openBrowser:t.open?ke:void 0,resolveProfileId:e.profileExplicit?void 0:({accessToken:u})=>Ge({accessToken:u,apiBaseUrl:e.apiBaseUrl}),onStatus:u=>{if(t.json){u.type!=="pending"&&m(t,{event:u.type,...u});return}if(!t.quiet)if(u.type==="verification_pending"){let f=u.verificationUriComplete||u.verificationUri,c=!!process.stdin.isTTY&&!t["no-input"];process.stdout.write(We({url:f,code:u.userCode,expiresInSeconds:u.expiresInSeconds,profile:e.profileExplicit?o:null,interactive:c,color:he()})),c&&!t.open&&(s=Ye({input:process.stdin,open:ke,url:f}))}else u.type==="approved"?console.log(Xe("Access approved by Elixpo Accounts.",he())):u.type==="denied"?console.log(" Access denied."):u.type==="expired"&&console.log(" Device code expired.")}})}finally{s()}if(!l.ok)return d(t,l.reason);await r.add(l.profileId),await r.setActive(l.profileId),m(t,{ok:!0,profile:l.profileId}),!t.json&&!t.quiet&&(console.log(` Credentials saved to local profile "${l.profileId}".`),console.log(" Tip: add another account with `lixblogs login`, list accounts with `lixblogs profiles`,"),console.log(" and switch with `lixblogs use <username>`."))}async function Ar(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return;let n=await Ne({credentialStore:i,profileId:o});if(m(t,n),!t.json)for(let a of n)a.loggedIn?console.log(`${a.profileId}: logged in${a.expired?" (expired)":""} \u2014 scopes: ${a.scopes.join(", ")}`):console.log(`${a.profileId}: not logged in`)}async function F(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return null;let n;try{n=P(e)}catch(s){return d(t,s),null}let a=new O({provider:n,credentialStore:i,profileId:o,apiBaseUrl:e.apiBaseUrl});return{client:new B(a),http:a,config:e,credentialStore:i,profileId:o}}async function Rr(t){let e=await F(t);if(e)try{let[r,o]=await Promise.all([e.client.whoami(),e.credentialStore.get(e.profileId)]),i={ok:!0,profile:e.profileId,environment:e.config.environment,identity:r,scopes:o?.scopes||[],expiresAt:o?.expiresAt?new Date(o.expiresAt).toISOString():null,expired:o?Date.now()>=o.expiresAt:!0};m(t,i),!t.json&&!t.quiet&&(console.log(`${r.displayName||r.username} (@${r.username})`),console.log(`Profile: ${e.profileId} \xB7 ${i.environment}`),console.log(`Scopes: ${i.scopes.join(", ")||"none"}`),console.log(`Expires: ${i.expiresAt||"unknown"}`))}catch(r){d(t,r,r.status===401||r.status===403?h.AUTH:h.ERROR)}}async function Pr(t){let e=x({flags:$(t)}),r=new URL("/register",e.accountsBaseUrl).toString();if(t["no-input"]){m(t,{ok:!0,registrationUrl:r,next:"lixblogs login"}),!t.json&&!t.quiet&&console.log(r);return}await ke(r),t.quiet||console.log(`Create your account at ${r}, then approve the device login.`),await Lt(t)}async function Tr(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return;let n=await Be({credentialStore:i,profileId:o});m(t,n),!t.json&&!t.quiet&&console.log(`Logged out profile "${o}".`)}async function Or(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r);if(!t.yes)return d(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let i;try{i=P(e)}catch(s){return d(t,s.message)}let n=await I(t,r);if(!n)return;let a=await Me({provider:i,credentialStore:n,profileId:o,confirmed:!0});if(!a.ok)return d(t,a.reason);m(t,a),!t.json&&!t.quiet&&console.log(`Revoked and logged out profile "${o}".`)}async function Ee(t,e,r){let o=x({flags:$(t)}),i=new y,n=await U(o,i);if(r==="cloudinary-disconnect"&&!t.yes)return d(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let a;try{a=P(o)}catch(c){return d(t,c.message)}let s=await I(t,i);if(!s)return;let l=new O({provider:a,credentialStore:s,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new ie(l),f=r==="cloudinary-status"?await Ot({integrationsClient:u}):await Tt({integrationsClient:u,confirmed:!0});if(!f.ok)return d(t,f.error||f.reason);m(t,f),!t.json&&!t.quiet&&console.log(r==="cloudinary-status"?`Cloudinary: ${f.data.connected?`connected (${f.data.cloudName})`:"not connected"}`:"Cloudinary connection disconnected.")}async function Cr(t){let e=new y,r=await I(t,e);if(!r)return;let o=await Fe({credentialStore:r,profileRegistry:e});if(m(t,o),!t.json){o.profiles.length||console.log("No profiles. Run `lixblogs auth login` first.");for(let i of o.profiles)console.log(`${i.active?"*":" "} ${i.profileId}${i.expired?" (expired)":""}`)}}async function Lr(t,e){let r;try{r=_(e[0])}catch(a){return d(t,a.message)}let o=new y,i=await I(t,o);if(!i)return;let n=await ze({credentialStore:i,profileRegistry:o,profileId:r});if(!n.ok)return d(t,n.reason);m(t,n),!t.json&&!t.quiet&&console.log(`Using profile "${r}".`)}var jt={list:Qe,get:ve,preview:ve,create:et,edit:tt,publish:rt,unpublish:ot,delete:xe,trash:xe,restore:it},Ut={list:nt,get:st,collections:at,members:lt,targets:ct},Dt={list:ut,invitations:dt,invite:ft,role:pt,remove:ht,accept:mt,decline:gt},Nt={list:({options:t})=>_t(t),inspect:({id:t})=>It({name:t}),install:({id:t,options:e})=>Et({name:t,options:e})},Bt={query:qt,export:At};async function jr(t,e,r){let o=x({flags:$(t)}),i=new y,n=await U(o,i),a=await I(t,i);if(!a)return;let s;try{s=P(o)}catch(c){return d(t,c.message)}let l=new O({provider:s,credentialStore:a,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new B(l),f={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let c=await jt[r]({client:u,id:e[0],options:f,stdin:process.stdin});if(m(t,{ok:!0,...c}),!t.json&&!t.quiet)if(r==="list"){for(let g of c.data||[])console.log(`${g.id} ${g.status} ${g.title||"(untitled)"}`);c.meta?.nextCursor&&console.log(`Next cursor: ${c.meta.nextCursor}`)}else r==="get"?console.log(`${c.title||"(untitled)"} [${c.status}]
100
+ ${c.markdown||""}`):c.dryRun?console.log(`Dry run: ${r} validated; no changes sent.`):console.log(c.url||`${r} completed for ${c.id}.`)}catch(c){if(t.json&&c instanceof p){process.stdout.write(Z({ok:!1,error:{code:c.code,message:c.message,requestId:c.requestId,details:c.details}})+`
101
+ `),process.exitCode=c.status===412?3:1;return}return d(t,c,c.status===412?h.CONFLICT:h.ERROR)}}async function Ur(t,e,r){let o=await F(t);if(!o)return;let i=new ee(o.http);try{let n=await Ut[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(r==="targets"){console.log("personal Personal Blog");for(let s of n.organizations||[]){console.log(`${s.target} ${s.role} ${s.name}`);for(let l of s.collections||[])console.log(` collection:${l.id} ${l.name}`)}return}let a=r==="list"?n.data||[]:Array.isArray(n)?n:[n];for(let s of a)console.log([s.id||s.userId||s.orgId,s.role,s.slug||s.username,s.name||s.displayName].filter(Boolean).join(" "))}catch(n){d(t,n,n.status===401||n.status===403?h.AUTH:h.ERROR)}}async function Dr(t,e,r){let o=await F(t);if(!o)return;let i=new re(o.http);try{let n=await Dt[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(n.dryRun){console.log(`Dry run: ${n.action} validated; no changes sent.`);return}let a=r==="invitations"?n:r==="list"?n.collaborators||[]:[n];for(let s of a)console.log([s.blogId||s.userId,s.status,s.role,s.username||s.title,s.notificationState].filter(Boolean).join(" "))}catch(n){d(t,n,n.status===401||n.status===403?h.AUTH:h.ERROR)}}async function Nr(t,e,r){try{let o=await Nt[r]({id:e[0],options:t});if(m(t,{ok:!0,data:o}),t.json||t.quiet)return;if(r==="list")for(let i of o)console.log(`${i.name} CLI >= ${i.minimumCliVersion||"unknown"} ${i.description}`);else r==="inspect"?process.stdout.write(o.content):o.dryRun?console.log(`Dry run: install ${o.name} to ${o.target}${o.replace?" (replace)":""}.`):console.log(`Installed ${o.name} at ${o.target}.`)}catch(o){d(t,o)}}async function Br(t,e,r){let o=await F(t);if(!o)return;let i=new oe(o.http),n={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let a=await Bt[r]({client:i,options:n});if(m(t,{ok:!0,data:a}),t.json||t.quiet)return;if(r==="export"){console.log(`Exported ${a.rows} rows to ${a.output}.`);return}let s=a.data;if(console.log(`${s.scope.label} \xB7 ${s.dimension} \xB7 ${s.range.key}`),s.dimension==="overview")for(let[l,u]of Object.entries(s.values.totals))console.log(`${l} ${u} ${s.values.changes[l]}%`);else if(s.dimension==="timeline")s.values.labels.forEach((l,u)=>console.log(`${l} ${s.values.views[u]} ${s.values.reads[u]}`));else{for(let l of s.values)console.log(Object.values(l).join(" "));a.meta?.nextCursor&&console.log(`Next cursor: ${a.meta.nextCursor}`)}}catch(a){d(t,a,a.status===401||a.status===403?h.AUTH:h.ERROR)}}async function Mr(t,e,r){let o=await F(t);if(!o)return;let i=o.client;try{let n=await Pt({client:i,target:r});m(t,{ok:!0,data:n}),!t.json&&!t.quiet&&console.log(`Disconnected ${n.provider}.`)}catch(n){d(t,n)}}var Ct={auth:{login:Lt,status:Ar,whoami:Rr,logout:Tr,revoke:Or,profiles:Cr,use:Lr},blog:Object.fromEntries(Object.keys(jt).map(t=>[t,(e,r)=>jr(e,r,t)])),org:Object.fromEntries(Object.keys(Ut).map(t=>[t,(e,r)=>Ur(e,r,t)])),collab:Object.fromEntries(Object.keys(Dt).map(t=>[t,(e,r)=>Dr(e,r,t)])),skill:Object.fromEntries(Object.keys(Nt).map(t=>[t,(e,r)=>Nr(e,r,t)])),analytics:Object.fromEntries(Object.keys(Bt).map(t=>[t,(e,r)=>Br(e,r,t)])),integrations:{"cloudinary-status":(t,e)=>Ee(t,e,"cloudinary-status"),"cloudinary-disconnect":(t,e)=>Ee(t,e,"cloudinary-disconnect")},disconnect:{cloudinary:(t,e)=>Ee(t,e,"cloudinary-disconnect"),pollinations:(t,e)=>Mr(t,e,"pollinations")}};async function Fr(){let t,e;try{({values:t,positionals:e}=Er({args:process.argv.slice(2),options:Sr,allowPositionals:!0,strict:!0}))}catch(a){process.stderr.write(`Error: Invalid flag. ${a.message}
102
+ `),process.exitCode=h.USAGE;return}if(t.help||e.length===0){process.stdout.write($r);return}if(e[0]==="register"){await Pr(t);return}e=He(e);let[r,o]=e,i=Ct[r];if(!i){process.stderr.write(`Error: Unknown command category "${r}".
103
+ `),process.stderr.write(`Available categories: ${Object.keys(Ct).join(", ")}
104
+ `),process.exitCode=h.USAGE;return}let n=i[o];if(!n){process.stderr.write(`Error: Unknown ${r} command "${o}".
103
105
  `),process.stderr.write(`Available commands: ${Object.keys(i).map(a=>`${r} ${a}`).join(", ")}
104
- `),process.exitCode=f.USAGE;return}await n(t,e.slice(2))}Dr();
106
+ `),process.exitCode=h.USAGE;return}await n(t,e.slice(2))}Fr();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixpo/lixblogs-cli",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Official CLI for LixBlogs — publish, manage, and inspect blogs through the supported API. Built for creators and agent automation.",
5
5
  "author": {
6
6
  "name": "Elixpo",