@elixpo/lixblogs-cli 1.4.2 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lixblogs.mjs +17 -15
- package/package.json +1 -1
package/dist/lixblogs.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseArgs as gr}from"node:util";import{spawn as wr}from"node:child_process";var F={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??F.environment,o=t.profile??e.LIXBLOGS_PROFILE??F.profile,i=Ee[r]||Ee.production,n=t.authProvider??e.LIXBLOGS_AUTH_PROVIDER??(r==="production"?"elixpo":"mock"),a=t.accountsUrl??e.LIXBLOGS_ACCOUNTS_URL??F.accountsBaseUrl,s=t.apiUrl??e.LIXBLOGS_API_URL??F.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 P=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-"},Lt=5,Se=0;function jt(t){return Se+=1,`${t}${Se}`}var z=class extends P{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=jt(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:Lt}:{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",Ut=1,Nt=15e3,Dt={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(Dt[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 Bt(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 Mt(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 G(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 G(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 V=class extends P{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=Nt}={}){if(super(),typeof n!="function")throw new TypeError("A fetch implementation is required.");this.accountsBaseUrl=Mt(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]!==Ut)throw new I("Accounts uses an unsupported device-flow contract version.");if(!Bt(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 G(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 G(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 G(i,o)}}};var Re="elixpo",le=class extends Error{constructor(e){super(e),this.name="ProductionAuthGateError"}};function Pe({providerId:t,environment:e}){if(e==="production"&&t!==Re)throw new le(`Provider "${t}" is not approved for production. Only "${Re}" 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 z:new V({accountsBaseUrl:t.accountsBaseUrl,clientId:t.clientId,audience:t.audience,cliVersion:t.cliVersion||"1.2.0",fetchImpl:t.fetchImpl});return Pe({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")}},J=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()]}},H=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 Ft}from"@napi-rs/keyring";var zt="lixblogs-cli",Gt="__lixblogs_availability_probe__";function K(t){return new Ft(zt,t)}async function Oe(){let t=K(Gt);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=
|
|
4
|
-
`),new J}let o=e||new g,i=new W(o);return new H(i)}var de="[REDACTED]",Ht=/token|refresh|secret|password|authorization/i,Kt=/^(mock-(access|refresh)-|Bearer\s+)\S+/i;function Le(t){return typeof t=="string"&&Kt.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))Ht.test(r)?e[r]=de:typeof o=="object"&&o!==null?e[r]=ue(o):e[r]=Le(o);return e}return Le(t)}function Y(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 Ne({credentialStore:t,profileId:e}){return await t.delete(e),{ok:!0}}async function De({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 Wt=6e4,ze=new WeakMap;function Xt(t){let e=ze.get(t);return e||(e=new Map,ze.set(t,e)),e}var Z=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."}},N=class{constructor({provider:e,credentialStore:r,profileId:o,apiBaseUrl:i="https://blogs.elixpo.com",fetchImpl:n=globalThis.fetch,refreshSkewMs:a=Wt}){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=Xt(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 Z(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 Z(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 Yt(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 D=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 Yt(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 Zt(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 Q=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 Zt(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 ee}from"node:crypto";async function Qt(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 te=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 Qt(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=ee()}){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=ee()}){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=ee()}={}){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=ee()}){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 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 e}var re=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),er(await this.http.request(`/api/v1/analytics?${o}`,{headers:{accept:"application/json"}}))}};var p=Object.freeze({OK:0,ERROR:1,USAGE:2,CONFLICT:3,AUTH:4,CONFIRMATION:5}),tr=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=tr[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=p.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(`
|
|
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
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,`
|
|
6
6
|
`).split(`
|
|
7
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
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
|
|
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||""}
|
|
10
10
|
${r}
|
|
11
11
|
\`\`\``:e.type==="mermaidBlock"?`\`\`\`mermaid
|
|
12
12
|
${e.props?.diagram||""}
|
|
13
13
|
\`\`\``:e.type==="image"?``:e.type==="divider"?"---":r}).join(`
|
|
14
14
|
|
|
15
|
-
`)}import{promises as B}from"node:fs";import{tmpdir as or}from"node:os";import he from"node:path";import{spawn as ir}from"node:child_process";async function nr(t){let e="";t.setEncoding("utf8");for await(let r of t)e+=r;return e}async function sr(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(or(),"lixblogs-")),o=he.join(r,"post.md");await B.writeFile(o,t,{mode:384});try{return await new Promise((i,n)=>{let a=ir(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 nr(e):t.content!==void 0?i=t.content:i=await sr(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 ar(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 ie(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(ar(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:oe(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 ie(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:oe(i.content)}),a={...ge(r),...n?{content:n.blocks}:{}};if(!Object.keys(a).length)throw new Error("No blog changes were provided.");if(ie(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,oe(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 ie(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 lr,cp as cr,readFile as ur,readdir as dr}from"node:fs/promises";import b from"node:path";import{fileURLToPath as pr}from"node:url";var xe=b.dirname(pr(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 lr(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 ur(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 dr(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 cr(i,a,{recursive:!0,force:!!e.force}),{installed:!0,name:o,target:a}}import{writeFile as fr}from"node:fs/promises";var hr=new Set(["overview","timeline","posts","sources","devices","countries"]),mr=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(!hr.has(e))throw new Error(`Unsupported analytics dimension: ${e}.`);if(!mr.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)}
|
|
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
16
|
`;else{let n=Et(o),a=[...new Set(n.flatMap(s=>Object.keys(s)))];i=`${a.map(It).join(",")}
|
|
17
17
|
${n.map(s=>a.map(l=>It(s[l])).join(",")).join(`
|
|
18
18
|
`)}
|
|
19
|
-
`}return await
|
|
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
|
|
20
20
|
|
|
21
21
|
Usage:
|
|
22
22
|
lixblogs login [--profile <name>] [--open]
|
|
@@ -58,6 +58,8 @@ Usage:
|
|
|
58
58
|
lixblogs skill list [--json]
|
|
59
59
|
lixblogs skill inspect <name> [--json]
|
|
60
60
|
lixblogs skill install <name> [--target <directory>] [--dry-run] --yes
|
|
61
|
+
lixblogs disconnect cloudinary
|
|
62
|
+
lixblogs disconnect pollinations
|
|
61
63
|
|
|
62
64
|
Global flags:
|
|
63
65
|
--profile <name> local account alias (defaults to the signed-in username)
|
|
@@ -87,16 +89,16 @@ Global flags:
|
|
|
87
89
|
Machine mode:
|
|
88
90
|
--json --no-input produces stable JSON on stdout, diagnostics on stderr, and
|
|
89
91
|
never prompts. Publishing and destructive state changes require --yes.
|
|
90
|
-
`,
|
|
91
|
-
`)}function
|
|
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)+`
|
|
92
94
|
`):t.quiet||(process.stderr.write(`Error: ${i}
|
|
93
95
|
`),o.hint&&process.stderr.write(`Hint: ${o.hint}
|
|
94
96
|
`),o.requestId&&process.stderr.write(`Request: ${o.requestId}
|
|
95
|
-
`)),process.exitCode=o.exitCode||r}async function S(t,e){try{return await Ce({allowInsecureFallback:t["allow-insecure-fallback"],profileRegistry:e})}catch(r){return
|
|
96
|
-
${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(
|
|
97
|
-
`),process.exitCode=c.status===412?3:1;return}return
|
|
98
|
-
`),process.exitCode=
|
|
99
|
-
`),process.stderr.write(`Available categories: ${Object.keys(
|
|
100
|
-
`),process.exitCode=
|
|
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}".
|
|
101
103
|
`),process.stderr.write(`Available commands: ${Object.keys(i).map(a=>`${r} ${a}`).join(", ")}
|
|
102
|
-
`),process.exitCode=
|
|
104
|
+
`),process.exitCode=f.USAGE;return}await n(t,e.slice(2))}Dr();
|
package/package.json
CHANGED