@elixpo/lixblogs-cli 1.4.6 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,75 +1,31 @@
1
1
  # @elixpo/lixblogs-cli
2
2
 
3
- The official CLI for LixBlogs publish, manage, and inspect blogs through
4
- the supported API. Built for creators and agent/automation use.
5
-
6
- This package implements production device-flow authentication and the core
7
- blog lifecycle over the stable LixBlogs API v1 contract. Its output stays
8
- compact and predictable for both terminals and automation.
9
-
10
- ## Install (local development)
11
-
12
- ```bash
13
- cd packages/lixblogs-cli
14
- npm install
15
- ```
3
+ Publish, manage, and inspect LixBlogs through its stable API v1. The CLI uses
4
+ device authorization and predictable terminal or JSON output.
16
5
 
17
6
  ```bash
18
7
  npm install -g @elixpo/lixblogs-cli
19
- ```
20
-
21
- ## Usage
22
-
23
- ```bash
24
- node bin/lixblogs.mjs --help
8
+ lixblogs --help
25
9
  ```
26
10
 
27
11
  ### Authentication
28
12
 
29
13
  ```bash
30
- # Log in via device authorization
31
14
  node bin/lixblogs.mjs login
32
- # Credentials are saved under the authenticated username and made active.
33
-
34
- # Save credentials under an explicit local profile name
35
15
  node bin/lixblogs.mjs login --profile personal
36
-
37
- # Check login status
38
16
  node bin/lixblogs.mjs whoami
39
-
40
- # List profiles and choose the active one
41
17
  node bin/lixblogs.mjs profiles
42
18
  node bin/lixblogs.mjs use work
43
-
44
- # Log out (clears local credentials only)
45
19
  node bin/lixblogs.mjs logout
46
-
47
- # Revoke the token server-side and clear local credentials (destructive)
48
20
  node bin/lixblogs.mjs auth revoke --yes
49
21
  ```
50
22
 
51
- Interactive login shows one compact device card and stays quiet while Accounts
52
- approval is pending. Press Enter to open the verification URL locally, use
53
- `--open` to open it immediately, or copy the URL to any browser when connected
54
- to a VPS. Device authorization does not require a localhost callback or an
55
- exposed port. The authenticated username becomes the local profile alias unless
56
- `--profile` explicitly overrides it. Run `lixblogs login` again to add another
57
- account, `lixblogs profiles` to list saved accounts, and
58
- `lixblogs use <username>` to switch the active one.
23
+ Press Enter to open the verification URL or copy it to another device. The
24
+ username becomes the profile alias unless `--profile` overrides it. Use
25
+ `profiles` and `use <username>` to switch accounts.
59
26
 
60
27
  ### Blog lifecycle
61
28
 
62
- Request the permissions needed for the operations you intend to use:
63
-
64
- ```bash
65
- node bin/lixblogs.mjs auth login \
66
- --scope openid --scope profile --scope lixblogs:blog:read \
67
- --scope lixblogs:blog:write --scope lixblogs:blog:publish \
68
- --scope lixblogs:blog:delete
69
- ```
70
-
71
- Then work with Markdown without any database or Cloudflare credentials:
72
-
73
29
  ```bash
74
30
  lixblogs blog list --status draft
75
31
  lixblogs blog create --file post.md --title "A new post" --tag engineering
@@ -80,8 +36,12 @@ lixblogs blog unpublish <id> --yes
80
36
  lixblogs blog delete <id> --yes
81
37
  lixblogs blog list --status trashed
82
38
  lixblogs blog restore <id> --yes
39
+ lixblogs blog history <id>
40
+ lixblogs blog restore-version <id> --version <version-id> --yes
83
41
  ```
84
42
 
43
+ Titles, subtitles, slugs, tags, icon emoji, cover URL/position/zoom, publication target, collection, comment policy, membership, secret state, and published/unlisted visibility are supported by `blog create`, `blog edit`, and `blog publish`.
44
+
85
45
  Inspect valid publication targets before assigning organization metadata:
86
46
 
87
47
  ```bash
@@ -92,9 +52,6 @@ lixblogs org members ORG_ID
92
52
  lixblogs org targets --json
93
53
  ```
94
54
 
95
- Organization lookup is membership-bound. A slug alone never grants access;
96
- the API resolves the authenticated user's role before returning tenant data.
97
-
98
55
  Editorial collaboration stays separate from publishing:
99
56
 
100
57
  ```bash
@@ -106,10 +63,6 @@ lixblogs collab accept BLOG_ID --yes
106
63
  lixblogs collab decline BLOG_ID --yes
107
64
  ```
108
65
 
109
- Viewer, editor, and admin roles grant different editorial authority. None of
110
- these commands publishes a post; public-state changes still use `blog publish`
111
- with the publish scope and a separate confirmation.
112
-
113
66
  ### Creator analytics
114
67
 
115
68
  Analytics is read-only and uses bounded date ranges and dimensions:
@@ -122,9 +75,27 @@ lixblogs analytics query --scope org:ORG_ID --range custom \
122
75
  lixblogs analytics export --dimension timeline --format csv --output analytics.csv
123
76
  ```
124
77
 
125
- Organization analytics also requires `lixblogs:organizations:read`. Results contain
126
- aggregates only; the API does not expose visitor identifiers or credentials.
127
- Exports refuse to overwrite an existing file.
78
+ Organization analytics also requires `lixblogs:organizations:read`. Results are
79
+ aggregate-only, and exports refuse to overwrite an existing file.
80
+
81
+ ### Comments and media
82
+
83
+ ```bash
84
+ lixblogs comment list BLOG_ID
85
+ lixblogs comment add BLOG_ID --content "Clear explanation"
86
+ lixblogs comment reply BLOG_ID --parent COMMENT_ID --content "Following up"
87
+ lixblogs comment delete BLOG_ID --comment COMMENT_ID --yes
88
+
89
+ lixblogs media upload --file diagram.webp --blog BLOG_ID --type inline --attach
90
+ lixblogs integrations pollinations-status --json
91
+ lixblogs media generate --prompt "Editorial illustration" --model flux \
92
+ --blog BLOG_ID --type cover --attach --output cover.jpg
93
+ lixblogs media delete MEDIA_ID --yes
94
+ ```
95
+
96
+ Pollinations generation uses the creator's BYOP connection in Settings. The
97
+ CLI never stores its key or retries a billable generation automatically. Keep
98
+ the local output for a manual `media upload` retry.
128
99
 
129
100
  ### Agent skills
130
101
 
@@ -137,116 +108,32 @@ lixblogs skill install lixblogs-author --target .agents/skills --dry-run
137
108
  lixblogs skill install lixblogs-author --target .agents/skills --yes
138
109
  ```
139
110
 
140
- Install only the skill needed by the current agent. Existing files are not
141
- replaced unless `--force --yes` is explicit. The bundled skill declares its
142
- minimum compatible CLI version and scopes.
111
+ Install only the needed skill. Existing files require explicit `--force --yes`.
112
+ Each skill declares its minimum CLI version and scopes.
143
113
 
144
114
  `create`, `edit`, `publish`, `unpublish`, `delete`, and `restore` accept
145
115
  `--dry-run`. Content input is mutually exclusive: `--file`, `--stdin`,
146
- `--content`, or `--editor`. Permanent deletion additionally requires
116
+ `--content`, or `--editor`. Permanent deletion requires
147
117
  `--permanent --yes` and the `lixblogs:blog:delete:permanent` scope.
148
118
 
149
- Edits use the server ETag automatically. If another editor wins the race, the
150
- command exits with code 3 and retains both versions under
151
- `.lixblogs-conflicts/`; it never overwrites the newer server revision.
152
-
153
- Global flags:
154
- - `--profile <name>` — override the username-based local account alias
155
- - `--env <environment>` — override environment (`development` | `staging` | `production`)
156
- - `--scope <scope>` — request an additional/alternate OAuth scope; repeatable
157
- - `--open` — open the verification URL with the device code pre-filled
158
- - `--accounts-url <url>` — override the Accounts issuer for local/staging tests
159
- - `--api-url <url>` — override the LixBlogs API origin; production defaults to
160
- `https://blogs.elixpo.com`
161
- - `--json` — machine-readable JSON output
162
- - `--quiet` — suppress non-essential output
163
- - `--yes`, `-y` — confirm publishing and destructive state changes
164
- - `--allow-insecure-fallback` — explicit opt-in: if the OS keychain is
165
- unavailable, use a non-persistent in-memory store instead of failing
119
+ Edits use the server ETag. Conflicts exit with code 3 and retain both versions
120
+ under `.lixblogs-conflicts/` without overwriting the server revision.
166
121
 
167
122
  ### Service boundary
168
123
 
169
124
  - `https://accounts.elixpo.com` issues, refreshes, and revokes OAuth tokens.
170
125
  - `https://blogs.elixpo.com/api/v1` is the only production resource API.
171
- - The CLI discovers Accounts endpoints before login and rejects incompatible
172
- contract versions or endpoints on an unexpected origin.
173
- - The mock provider is available only with an explicit non-production
174
- environment, for example `--env development --auth-provider mock`.
175
- - The resource contract, scopes, pagination, errors, and mutation guarantees
176
- are documented in the [API reference](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/API.md).
177
- - Release compatibility, provenance, smoke gates, and rollback are documented
178
- in the [release policy](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/RELEASE.md).
179
- Contract changes are summarized in the
180
- [changelog](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/CHANGELOG.md).
181
-
182
- The production client is public and has no client secret. Never add one to
183
- CLI configuration, package files, or GitHub secrets.
184
-
185
- ### Configuration precedence
186
-
187
- Configuration resolves in this order: command flags, `LIXBLOGS_*` environment
188
- variables, the selected named profile, then production-safe defaults. Use
189
- `lixblogs whoami --json --no-input` to verify the active profile, environment,
190
- granted scopes, and expiry before automation. Flags are best for one command;
191
- environment values are best for a contained CI job. Credentials remain in the
192
- OS keychain and are never read from environment variables.
126
+ - Incompatible discovery metadata and unexpected origins are rejected.
127
+ - See the [API contract](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/API.md), [release policy](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/RELEASE.md), and [changelog](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/CHANGELOG.md).
128
+
129
+ The public production client has no client secret. Never add one.
193
130
 
194
131
  ### Troubleshooting
195
132
 
196
- - `invalid_scope`: Accounts has not registered the requested permission for
197
- this client; do not substitute a broader token.
133
+ - `invalid_scope`: Accounts has not registered the requested permission.
198
134
  - `insufficient_scope`: log in again with only the reported missing scope.
199
- - `account_not_provisioned`: sign in to LixBlogs once with the same Accounts
200
- identity before retrying the CLI.
135
+ - `account_not_provisioned`: sign in to LixBlogs once, then retry.
201
136
  - `precondition_failed`: fetch the current post, reconcile the retained
202
137
  conflict copy, and retry with the new revision.
203
138
  - `rate_limit_exceeded`: honor `Retry-After`; do not fan out retries.
204
- - Include the returned request ID in a report, never a token or credential.
205
-
206
- ## Development
207
-
208
- ```bash
209
- npm test # runs the full CLI test suite
210
- ```
211
-
212
- Tests exercise both a mocked auth provider and, where relevant, the real
213
- OS keychain backend on whatever machine runs them — see
214
- the [threat model](https://github.com/elixpo/blogs.elixpo/blob/main/packages/lixblogs-cli/THREAT_MODEL.md)
215
- and inline comments in `src/config/KeychainCredentialStore.js`
216
- for known platform-specific behavior (e.g. a documented WSL/keyring-rs quirk).
217
-
218
- ## Architecture
219
-
220
- ```
221
- bin/lixblogs.mjs Source CLI entry point (Node's native util.parseArgs)
222
- dist/lixblogs.mjs Minified executable shipped in the npm package
223
- src/auth/ Accounts provider, development mock, refresh-safe
224
- authenticated client, and production safety gate
225
- src/commands/auth/ Command logic (login, status, logout, revoke) —
226
- framework-agnostic, testable independently of the CLI shell
227
- src/commands/blog/ Blog lifecycle commands and Markdown/editor input
228
- src/api/ Versioned LixBlogs resource client and stable errors
229
- src/content/ Dependency-free Markdown/block conversion
230
- src/config/ Credential storage (real keychain + gated fallback),
231
- profile registry, config resolution, token redaction
232
- tests/ Full test suite
233
- THREAT_MODEL.md Security threat model (repository documentation)
234
- ```
235
-
236
- Command logic under `src/commands/` is deliberately decoupled from the CLI
237
- parsing layer in `bin/`, so the parser (or any other interface built on top
238
- of these commands later) can change without touching command logic or its
239
- tests.
240
-
241
- ## Roadmap
242
-
243
- See [#135](https://github.com/elixpo/blogs.elixpo/issues/135) for the full
244
- scope. Rough remaining order:
245
-
246
- 1. Media commands
247
- 2. Interactive terminal UI and branding in a separate issue
248
-
249
- ## Contributing
250
-
251
- This package is part of the [blogs.elixpo](https://github.com/elixpo/blogs.elixpo)
252
- monorepo. See the root repository's contribution guidelines.
139
+ - Report the request ID, never a token or credential.
package/dist/lixblogs.mjs CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import{parseArgs as Er}from"node:util";import{spawn as kr}from"node:child_process";var z={environment:"production",profile:"default",accountsBaseUrl:"https://accounts.elixpo.com",apiBaseUrl:"https://blogs.elixpo.com"},Se={development:{clientId:"lixblogs-cli-dev",audience:"localhost"},staging:{clientId:"lixblogs-cli-staging",audience:"staging.blogs.elixpo.com"},production:{clientId:"lixblogs-cli-prod",audience:"blogs.elixpo.com"},test:{clientId:"lixblogs-cli-dev",audience:"localhost"}};function x({flags:t={},env:e=process.env}={}){let r=t.env??e.LIXBLOGS_ENV??z.environment,o=t.profile??e.LIXBLOGS_PROFILE??z.profile,i=Se[r]||Se.production,n=t.authProvider??e.LIXBLOGS_AUTH_PROVIDER??(r==="production"?"elixpo":"mock"),a=t.accountsUrl??e.LIXBLOGS_ACCOUNTS_URL??z.accountsBaseUrl,s=t.apiUrl??e.LIXBLOGS_API_URL??z.apiBaseUrl,l=t.clientId??e.LIXBLOGS_CLIENT_ID??i.clientId,u=t.audience??e.LIXBLOGS_AUDIENCE??i.audience;return{environment:r,profile:o,profileExplicit:t.profile!==void 0||e.LIXBLOGS_PROFILE!==void 0,authProvider:n,accountsBaseUrl:a,apiBaseUrl:s,clientId:l,audience:u}}var R=class{get providerId(){throw new Error("AuthProvider.providerId must be implemented by subclass")}async requestDeviceCode(e){throw new Error("AuthProvider.requestDeviceCode must be implemented by subclass")}async pollDeviceCode(e){throw new Error("AuthProvider.pollDeviceCode must be implemented by subclass")}async refresh(e){throw new Error("AuthProvider.refresh must be implemented by subclass")}async revoke(e){throw new Error("AuthProvider.revoke must be implemented by subclass")}};var $e={APPROVE_IMMEDIATELY:"mock-approve-",PENDING_THEN_APPROVE:"mock-pending-then-approve-",DENY:"mock-deny-",EXPIRE:"mock-expire-",SLOW_DOWN_THEN_APPROVE:"mock-slow-down-then-approve-"},Mt=5,qe=0;function Ft(t){return qe+=1,`${t}${qe}`}var G=class extends R{constructor(){super(),this._devicesCodes=new Map,this._revoked=new Set,this._refreshWillFail=new Set}get providerId(){return"mock"}async requestDeviceCode({scopes:e,scenario:r="APPROVE_IMMEDIATELY"}){let o=$e[r]??$e.APPROVE_IMMEDIATELY,i=Ft(o),n=i.slice(-6).toUpperCase();return this._devicesCodes.set(i,{scenario:r,pollCount:0,createdAt:Date.now(),scopes:[...e]}),{deviceCode:i,userCode:n,verificationUri:"https://mock.lixblogs.local/device",verificationUriComplete:`https://mock.lixblogs.local/device?user_code=${encodeURIComponent(n)}`,expiresInSeconds:r==="EXPIRE"?1:600,pollIntervalSeconds:1}}async pollDeviceCode({deviceCode:e}){let r=this._devicesCodes.get(e);return r?r.scenario==="EXPIRE"?{status:"expired"}:r.scenario==="DENY"?{status:"denied"}:r.scenario==="PENDING_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"pending"}:r.scenario==="SLOW_DOWN_THEN_APPROVE"&&(r.pollCount+=1,r.pollCount<2)?{status:"slow_down",pollIntervalIncreaseSeconds:Mt}:{status:"approved",token:{accessToken:`mock-access-${e}`,refreshToken:`mock-refresh-${e}`,expiresInSeconds:3600,scopes:r.scopes}}:{status:"denied"}}async refresh({refreshToken:e,scopes:r=[]}){if(this._revoked.has(e))throw new Error("refresh token has been revoked");if(this._refreshWillFail.has(e))throw new Error("mock refresh failure (test-injected)");return{accessToken:`mock-access-refreshed-${e}`,refreshToken:e,expiresInSeconds:3600,scopes:r}}async revoke({token:e}){this._revoked.add(e)}_simulateRefreshFailureFor(e){this._refreshWillFail.add(e)}};var Ae="urn:ietf:params:oauth:grant-type:device_code",zt=1,Gt=15e3,Vt={access_denied:"Login was denied.",authorization_pending:"Login is awaiting approval.",expired_token:"The device authorization expired. Start login again.",invalid_client:"The LixBlogs CLI client is not registered for this environment.",invalid_grant:"This session is no longer valid. Log in again.",invalid_request:"Accounts rejected the authentication request.",invalid_scope:"The requested LixBlogs permissions are not available for this client.",server_error:"Accounts could not complete authentication. Try again later.",slow_down:"Accounts requested slower polling.",temporarily_unavailable:"Accounts is temporarily unavailable. Try again later."},v=class extends Error{constructor(e,{status:r=0,requiresLogin:o=!1}={}){super(Vt[e]||"Authentication failed."),this.name="AuthProviderError",this.code=e||"authentication_failed",this.status=r,this.requiresLogin=o}},E=class extends Error{constructor(e){super(e),this.name="CompatibilityError",this.code="incompatible_accounts_contract"}};function le(t){return String(t||"0.0.0").split(".").slice(0,3).map(e=>Number.parseInt(e,10)||0)}function Jt(t,e){let r=le(t),o=le(e);for(let i=0;i<3;i+=1)if(r[i]!==o[i])return r[i]>o[i];return!0}function Ht(t){let e=new URL(t);if(e.pathname=e.pathname.replace(/\/$/,""),e.search="",e.hash="",e.protocol!=="https:"&&e.hostname!=="localhost"&&e.hostname!=="127.0.0.1")throw new E("Accounts must use HTTPS outside local development.");return e.toString().replace(/\/$/,"")}async function N(t){try{return await t.json()}catch{throw new v("server_error",{status:t.status})}}function V(t,e){let r=typeof t?.error=="string"?t.error:"server_error";return new v(r,{status:e.status,requiresLogin:r==="invalid_grant"||r==="access_denied"||r==="expired_token"})}function Re(t,e){if(!e.ok)throw V(t,e);if(typeof t?.access_token!="string"||typeof t?.refresh_token!="string"||!Number.isFinite(Number(t?.expires_in)))throw new v("server_error",{status:e.status});return{accessToken:t.access_token,refreshToken:t.refresh_token,expiresInSeconds:Number(t.expires_in),scopes:typeof t.scope=="string"?t.scope.split(/\s+/).filter(Boolean):[]}}var J=class extends R{constructor({accountsBaseUrl:e="https://accounts.elixpo.com",clientId:r="lixblogs-cli-prod",audience:o="blogs.elixpo.com",cliVersion:i="1.2.0",fetchImpl:n=globalThis.fetch,timeoutMs:a=Gt}={}){if(super(),typeof n!="function")throw new TypeError("A fetch implementation is required.");this.accountsBaseUrl=Ht(e),this.clientId=r,this.audience=o,this.cliVersion=i,this.fetchImpl=n,this.timeoutMs=a,this._metadata=null,this._discoveryPromise=null}get providerId(){return"elixpo"}async _fetch(e,r={}){let o=new AbortController,i=setTimeout(()=>o.abort(),this.timeoutMs);try{return await this.fetchImpl(e,{...r,signal:r.signal||o.signal,headers:{accept:"application/json",...r.headers}})}catch{throw new v("temporarily_unavailable")}finally{clearTimeout(i)}}async discover({scopes:e=[]}={}){if(!this._metadata){this._discoveryPromise||(this._discoveryPromise=this._loadDiscovery());try{this._metadata=await this._discoveryPromise}finally{this._discoveryPromise=null}}if(e.filter(o=>!this._metadata.scopes_supported.includes(o)).length)throw new v("invalid_scope");return this._metadata}async _loadDiscovery(){let e=await this._fetch(`${this.accountsBaseUrl}/.well-known/oauth-authorization-server`),r=await N(e);if(!e.ok)throw new v("temporarily_unavailable",{status:e.status});if(le(r.elixpo_contract_version)[0]!==zt)throw new E("Accounts uses an unsupported device-flow contract version.");if(!Jt(this.cliVersion,r.elixpo_min_compatible_cli_version))throw new E(`This CLI is too old for Accounts. Upgrade to version ${r.elixpo_min_compatible_cli_version} or newer.`);if(!Array.isArray(r.grant_types_supported)||!r.grant_types_supported.includes(Ae))throw new E("Accounts does not advertise OAuth device authorization.");let i=["device_authorization_endpoint","token_endpoint","revocation_endpoint"];for(let n of i){if(typeof r[n]!="string")throw new E(`Accounts discovery is missing ${n}.`);let a=new URL(r[n]),s=new URL(this.accountsBaseUrl);if(a.origin!==s.origin)throw new E(`Accounts discovery returned an untrusted ${n}.`)}return{...r,scopes_supported:Array.isArray(r.scopes_supported)?r.scopes_supported:[]}}async requestDeviceCode({scopes:e}){let r=await this.discover({scopes:e}),o=await this._fetch(r.device_authorization_endpoint,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_id:this.clientId,scope:e.join(" "),audience:this.audience})}),i=await N(o);if(!o.ok)throw V(i,o);if(typeof i.device_code!="string"||typeof i.user_code!="string"||typeof i.verification_uri!="string")throw new v("server_error",{status:o.status});return{deviceCode:i.device_code,userCode:i.user_code,verificationUri:i.verification_uri,verificationUriComplete:i.verification_uri_complete||i.verification_uri,expiresInSeconds:Number(i.expires_in)||600,pollIntervalSeconds:Number(i.interval)||5}}async pollDeviceCode({deviceCode:e}){let r=await this.discover(),o=new URLSearchParams({grant_type:Ae,device_code:e,client_id:this.clientId}),i=await this._fetch(r.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:o}),n=await N(i);if(i.ok)return{status:"approved",token:Re(n,i)};if(n?.error==="authorization_pending")return{status:"pending"};if(n?.error==="slow_down"){let a=r.elixpo_device_flow_polling||{};return{status:"slow_down",pollIntervalIncreaseSeconds:Math.max(5,Number(a.slow_down_interval_seconds||10)-Number(a.interval_seconds||5))}}if(n?.error==="access_denied")return{status:"denied"};if(n?.error==="expired_token")return{status:"expired"};throw V(n,i)}async refresh({refreshToken:e,scopes:r}){let o=await this.discover({scopes:r||[]}),i=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:this.clientId});r?.length&&i.set("scope",r.join(" "));let n=await this._fetch(o.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:i}),a=await N(n);return Re(a,n)}async revoke({token:e}){let r=await this.discover(),o=await this._fetch(r.revocation_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.clientId})});if(!o.ok){let i=await N(o);throw V(i,o)}}};var Pe="elixpo",ce=class extends Error{constructor(e){super(e),this.name="ProductionAuthGateError"}};function Te({providerId:t,environment:e}){if(e==="production"&&t!==Pe)throw new ce(`Provider "${t}" is not approved for production. Only "${Pe}" may be used in production.`)}function P(t){if(t.authProvider==="mock"&&t.environment==="production")throw new Error("The mock auth provider cannot run in production.");if(t.authProvider!=="mock"&&t.authProvider!=="elixpo")throw new Error(`Unknown auth provider "${t.authProvider}".`);let e=t.authProvider==="mock"?new G:new J({accountsBaseUrl:t.accountsBaseUrl,clientId:t.clientId,audience:t.audience,cliVersion:t.cliVersion||"1.2.0",fetchImpl:t.fetchImpl});return Te({providerId:e.providerId,environment:t.environment}),e}var k=class extends Error{constructor(e){super(e),this.name="CredentialStoreUnavailableError"}},q=class{async get(e){throw new Error("CredentialStore.get must be implemented by subclass")}async set(e,r){throw new Error("CredentialStore.set must be implemented by subclass")}async delete(e){throw new Error("CredentialStore.delete must be implemented by subclass")}async listProfiles(){throw new Error("CredentialStore.listProfiles must be implemented by subclass")}},H=class extends q{constructor(){super(),this._store=new Map}async get(e){return this._store.get(e)??null}async set(e,r){this._store.set(e,r)}async delete(e){this._store.delete(e)}async listProfiles(){return[...this._store.keys()]}},K=class extends q{constructor(e){super(),this._realStore=e}async get(e){try{return await this._realStore.get(e)}catch(r){throw new k(`OS keychain is unavailable: ${r.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async set(e,r){try{await this._realStore.set(e,r)}catch(o){throw new k(`OS keychain is unavailable: ${o.message}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`)}}async delete(e){try{await this._realStore.delete(e)}catch(r){throw new k(`OS keychain is unavailable: ${r.message}`)}}async listProfiles(){try{return await this._realStore.listProfiles()}catch(e){throw new k(`OS keychain is unavailable: ${e.message}`)}}};import{Entry as Kt}from"@napi-rs/keyring";var Wt="lixblogs-cli",Xt="__lixblogs_availability_probe__";function W(t){return new Kt(Wt,t)}async function Ce(){let t=W(Xt);try{return t.setPassword("probe"),t.deletePassword(),{available:!0}}catch(e){return{available:!1,error:String(e.message??e).split(`
3
- `)[0].trim()}}}var ue=class extends q{async get(e){let r=W(e),o;try{o=r.getPassword()}catch(i){if(Oe(i))return null;throw i}return o==null?null:JSON.parse(o)}async set(e,r){W(e).setPassword(JSON.stringify(r))}async delete(e){let r=W(e);try{r.deletePassword()}catch(o){if(Oe(o))return;throw o}}async listProfiles(){throw new Error("KeychainCredentialStore.listProfiles is not supported directly \u2014 use ProfileRegistry to track known profile IDs, then look up each one via get().")}};function Oe(t){let e=String(t?.message??"");return/no such|not found|nosuchkeyring|nosuchitem/i.test(e)}var X=class extends q{constructor(e){super(),this._keychain=new ue,this._registry=e}async get(e){return this._keychain.get(e)}async set(e,r){await this._keychain.set(e,r),await this._registry.add(e)}async delete(e){await this._keychain.delete(e),await this._registry.remove(e)}async listProfiles(){return this._registry.list()}};import{promises as Y}from"node:fs";import Le from"node:path";import Yt from"node:os";function Zt(){return Le.join(Yt.homedir(),".config","lixblogs","profiles.json")}var y=class{constructor(e=Zt()){this._path=e}async list(){return(await this._read()).profiles}async getActive(){let e=await this._read();return e.activeProfile&&e.profiles.includes(e.activeProfile)?e.activeProfile:e.profiles[0]||null}async setActive(e){_(e);let r=await this._read();if(!r.profiles.includes(e))throw new Error(`Profile "${e}" does not exist. Log in with it first.`);await this._write(r.profiles,e)}async _read(){try{let e=await Y.readFile(this._path,"utf8"),r=JSON.parse(e);return{profiles:Array.isArray(r.profiles)?r.profiles.filter(i=>typeof i=="string"):[],activeProfile:typeof r.activeProfile=="string"?r.activeProfile:null}}catch(e){if(e.code==="ENOENT")return{profiles:[],activeProfile:null};throw e}}async add(e){_(e);let r=await this._read(),o=new Set(r.profiles);o.add(e),await this._write([...o],r.activeProfile||e)}async remove(e){let r=await this._read(),o=r.profiles.filter(n=>n!==e),i=r.activeProfile===e?o[0]||null:r.activeProfile;await this._write(o,i)}async _write(e,r=null){await Y.mkdir(Le.dirname(this._path),{recursive:!0});let o=`${this._path}.${process.pid}.tmp`;await Y.writeFile(o,JSON.stringify({activeProfile:r,profiles:e},null,2),{encoding:"utf8",mode:384}),await Y.rename(o,this._path)}};function _(t){if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(t||""))throw new Error("Profile names must be 1-64 characters using letters, numbers, dot, dash, or underscore.");return t}async function je({allowInsecureFallback:t=!1,profileRegistry:e}={}){let r=await Ce();if(!r.available){if(!t)throw new k(`OS keychain is unavailable: ${r.error}. Re-run with an explicit fallback flag if you want to opt in to a less secure storage method.`);return process.stderr.write(`warning: OS keychain unavailable (${r.error}); using in-memory fallback because --allow-insecure-fallback was passed. Credentials will NOT persist between CLI runs.
4
- `),new H}let o=e||new y,i=new X(o);return new K(i)}var fe="[REDACTED]",Qt=/token|refresh|secret|password|authorization/i,er=/^(mock-(access|refresh)-|Bearer\s+)\S+/i;function Ue(t){return typeof t=="string"&&er.test(t)?fe:t}function de(t){if(Array.isArray(t))return t.map(e=>de(e));if(t&&typeof t=="object"){let e={};for(let[r,o]of Object.entries(t))Qt.test(r)?e[r]=fe:typeof o=="object"&&o!==null?e[r]=de(o):e[r]=Ue(o);return e}return Ue(t)}function Z(t,e){return JSON.stringify(de(t),null,e)}function T(t){return typeof t!="string"?t:t.replace(/(mock-(access|refresh)-\S+|Bearer\s+\S+)/gi,fe)}async function De({provider:t,credentialStore:e,profileId:r,scopes:o,openBrowser:i,resolveProfileId:n,sleep:a=l=>new Promise(u=>setTimeout(u,l)),onStatus:s=()=>{}}){let l;try{l=await t.requestDeviceCode({scopes:o})}catch(c){return{ok:!1,reason:T(c.message)}}s({type:"verification_pending",verificationUri:l.verificationUri,verificationUriComplete:l.verificationUriComplete,userCode:l.userCode,expiresInSeconds:l.expiresInSeconds}),i&&await i(l.verificationUriComplete||l.verificationUri);let u=l.pollIntervalSeconds*1e3,f=Date.now()+l.expiresInSeconds*1e3;for(;Date.now()<f;){await a(u);let c;try{c=await t.pollDeviceCode({deviceCode:l.deviceCode})}catch(g){return{ok:!1,reason:T(g.message)}}if(c.status==="approved"){let g=r;if(n)try{g=await n({accessToken:c.token.accessToken,requestedProfileId:r})}catch(D){return{ok:!1,reason:T(D.message)}}return await e.set(g,{accessToken:c.token.accessToken,refreshToken:c.token.refreshToken,expiresAt:Date.now()+c.token.expiresInSeconds*1e3,scopes:c.token.scopes}),s({type:"approved"}),{ok:!0,profileId:g}}if(c.status==="denied")return s({type:"denied"}),{ok:!1,reason:"Login was denied."};if(c.status==="expired")return s({type:"expired"}),{ok:!1,reason:"Device code expired before login was approved."};if(c.status==="slow_down"){u+=c.pollIntervalIncreaseSeconds*1e3,s({type:"slow_down",newIntervalMs:u});continue}s({type:"pending"})}return{ok:!1,reason:"Device code expired before login was approved."}}async function Ne({credentialStore:t,profileId:e}){let r=e?[e]:await t.listProfiles(),o=[];for(let i of r){let n=await t.get(i);if(!n){o.push({profileId:i,loggedIn:!1});continue}o.push({profileId:i,loggedIn:!0,expired:Date.now()>=n.expiresAt,scopes:n.scopes})}return o}async function Be({credentialStore:t,profileId:e}){return await t.delete(e),{ok:!0}}async function Me({provider:t,credentialStore:e,profileId:r,confirmed:o}){if(o!==!0)return{ok:!1,reason:"Revoke was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};let i=await e.get(r);return i?(await t.revoke({token:i.refreshToken}),await e.delete(r),{ok:!0}):{ok:!1,reason:`No stored credentials for profile "${r}".`}}async function Fe({credentialStore:t,profileRegistry:e}){let r=await e.getActive(),o=await t.listProfiles(),i=[];for(let n of o){let a=await t.get(n);i.push({profileId:n,active:n===r,loggedIn:!!a,expired:a?Date.now()>=a.expiresAt:void 0,scopes:a?.scopes||[]})}return{activeProfile:r,profiles:i}}async function ze({credentialStore:t,profileRegistry:e,profileId:r}){return await t.get(r)?(await e.setActive(r),{ok:!0,profileId:r}):{ok:!1,reason:`Profile "${r}" is not logged in.`}}async function Ge({accessToken:t,apiBaseUrl:e,fetchImpl:r=globalThis.fetch}){let o=new URL("/api/v1/me",e),i=await r(o,{headers:{accept:"application/json",authorization:`Bearer ${t}`}}),n;try{n=await i.json()}catch{throw new Error("LixBlogs could not resolve the signed-in username.")}if(!i.ok||typeof n?.data?.username!="string")throw new Error(n?.error?.message||"LixBlogs could not resolve the signed-in username.");return _(n.data.username)}var tr=6e4,Ve=new WeakMap;function rr(t){let e=Ve.get(t);return e||(e=new Map,Ve.set(t,e)),e}var Q=class extends Error{constructor(e){super(`Profile "${e}" needs to log in again.`),this.name="LoginRequiredError",this.code="login_required"}},pe=class extends Error{constructor(e,r){super("The configured LixBlogs origin is not serving the API v1 JSON contract."),this.name="ApiContractUnavailableError",this.code="api_contract_unavailable",this.status=e,this.details={contentType:r||"unknown"},this.hint="Deploy the LixBlogs API v1 stack, or select an origin that exposes /api/v1."}},O=class{constructor({provider:e,credentialStore:r,profileId:o,apiBaseUrl:i="https://blogs.elixpo.com",fetchImpl:n=globalThis.fetch,refreshSkewMs:a=tr}){this.provider=e,this.credentialStore=r,this.profileId=o,this.apiBaseUrl=new URL(i),this.fetchImpl=n,this.refreshSkewMs=a}async _refresh(e,{force:r=!1}={}){let o=rr(this.credentialStore),i=o.get(this.profileId);if(i)return i;let n=(async()=>{let a=await this.credentialStore.get(this.profileId)||e;if(!r&&a.expiresAt-Date.now()>this.refreshSkewMs)return a;try{let s=await this.provider.refresh({refreshToken:a.refreshToken,scopes:a.scopes}),l={accessToken:s.accessToken,refreshToken:s.refreshToken,expiresAt:Date.now()+s.expiresInSeconds*1e3,scopes:s.scopes};return await this.credentialStore.set(this.profileId,l),l}catch(s){throw s instanceof v&&s.requiresLogin?(await this.credentialStore.delete(this.profileId),new Q(this.profileId)):s}})();o.set(this.profileId,n);try{return await n}finally{o.get(this.profileId)===n&&o.delete(this.profileId)}}async credentials({forceRefresh:e=!1}={}){let r=await this.credentialStore.get(this.profileId);if(!r)throw new Q(this.profileId);return e||r.expiresAt-Date.now()<=this.refreshSkewMs?this._refresh(r,{force:e}):r}async request(e,r={}){let o=new URL(e,this.apiBaseUrl);if(o.origin!==this.apiBaseUrl.origin||!o.pathname.startsWith("/api/v1/"))throw new Error("Authenticated CLI requests are restricted to the configured LixBlogs /api/v1 resource server.");let i=await this.credentials(),n=()=>this.fetchImpl(o.toString(),{...r,headers:{...r.headers,authorization:`Bearer ${i.accessToken}`}}),a=await n();a.status===401&&(i=await this.credentials({forceRefresh:!0}),a=await n());let s=a.headers.get("content-type")||"";if(!s.toLowerCase().includes("application/json"))throw new pe(a.status,s);return a}async requireScopes(e){let r=await this.credentials(),o=e.filter(i=>!r.scopes.includes(i));if(o.length){let i=new Error(`Login again with the required scope${o.length>1?"s":""}: ${o.join(", ")}`);throw i.name="InsufficientScopeError",i.code="insufficient_scope",i.missingScopes=o,i}}};import{randomUUID as Je}from"node:crypto";var p=class extends Error{constructor(e,r,{status:o,requestId:i,details:n}={}){super(r),this.name="BlogApiError",this.code=e||"api_error",this.status=o||0,this.requestId=i||null,this.details=n||null}};async function or(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var B=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET"||!!o.headers["idempotency-key"];for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return or(s)}catch(s){if(!n||a>0||s instanceof p||s?.code)throw s;await this.sleep(250)}throw new p("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async whoami(){return await this.requireScopes(["lixblogs:profile:read"]),(await this.request("/api/v1/me")).payload.data}async list({status:e="all",limit:r=20,cursor:o}={}){await this.requireScopes(["lixblogs:blog:read"]);let i=new URLSearchParams({status:e,limit:String(r)});return o&&i.set("cursor",o),(await this.request(`/api/v1/blogs?${i}`)).payload}async get(e){await this.requireScopes(["lixblogs:blog:read"]);let r=await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`);return{...r.payload.data,etag:r.etag||r.payload.data.etag}}async create(e,{idempotencyKey:r=Je()}={}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request("/api/v1/blogs",{method:"POST",headers:{"idempotency-key":r},body:JSON.stringify(e)})).payload.data}async update(e,r,{etag:o}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"if-match":o},body:JSON.stringify(r)})).payload.data}async publish(e,{etag:r,idempotencyKey:o=Je()}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/publish`,{method:"POST",headers:{"if-match":r,"idempotency-key":o}})).payload.data}async unpublish(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/unpublish`,{method:"POST",headers:{"if-match":r}})).payload.data}async delete(e,{etag:r,permanent:o=!1}){return await this.requireScopes(["lixblogs:blog:delete",...o?["lixblogs:blog:delete:permanent"]:[]]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}${o?"?permanent=true":""}`,{method:"DELETE",headers:{"if-match":r,...o?{"x-confirm-permanent-delete":e}:{}}})).payload.data}async restore(e,{etag:r}){return await this.requireScopes(["lixblogs:blog:delete"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/restore`,{method:"POST",headers:{"if-match":r}})).payload.data}};async function ir(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return{payload:e,etag:t.headers.get("etag")}}var ee=class{constructor(e,{sleep:r=o=>new Promise(i=>setTimeout(i,o))}={}){this.http=e,this.sleep=r}async request(e,r={}){let o={...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}},n=(o.method||"GET")==="GET";for(let a=0;a<2;a+=1)try{let s=await this.http.request(e,o);if(n&&a===0&&(s.status===429||s.status>=500)){let l=Math.min(2,Number.parseInt(s.headers.get("retry-after")||"1",10)||1);await this.sleep(l*1e3);continue}return ir(s)}catch(s){if(!n||a>0||s instanceof p||s?.code)throw s;await this.sleep(250)}throw new p("request_failed","The LixBlogs request failed after retrying.")}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async list(){return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request("/api/v1/orgs")).payload}async get(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}`)).payload.data}async collections(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/collections`)).payload.data}async members(e){if(!e)throw new Error("An organization ID or handle is required.");return await this.requireScopes(["lixblogs:organizations:read"]),(await this.request(`/api/v1/orgs/${encodeURIComponent(e)}/members`)).payload.data}async targets(){await this.requireScopes(["lixblogs:organizations:read"]);let o=((await this.list())?.data||[]).filter(n=>n.canWrite),i=await Promise.all(o.map(async n=>{let a=[];try{a=await this.collections(n.id)}catch{a=[]}return{target:`org:${n.id}`,orgId:n.id,slug:n.slug,name:n.name,role:n.role,collections:a.map(s=>({id:s.id,slug:s.slug,name:s.name}))}}));return{personal:{target:"personal",name:"Personal Blog"},organizations:i}}};import{randomUUID as te}from"node:crypto";async function nr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e.data}var re=class{constructor(e){this.http=e}async request(e,r={}){let o=await this.http.request(e,{...r,headers:{accept:"application/json",...r.body?{"content-type":"application/json"}:{},...r.headers}});return nr(o)}async list(e){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`)}async invitations(){return await this.http.requireScopes(["lixblogs:collaboration:read"]),this.request("/api/v1/collaboration/invitations")}async invite(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async role(e,{user:r,role:o,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"PATCH",headers:{"idempotency-key":i},body:JSON.stringify({user:r,role:o})})}async remove(e,{user:r,idempotencyKey:o=te()}={}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request(`/api/v1/blogs/${encodeURIComponent(e)}/collaborators`,{method:"DELETE",headers:{"idempotency-key":o},body:JSON.stringify({...r?{user:r}:{}})})}async resolveInvitation(e,{action:r,showOnProfile:o=!0,idempotencyKey:i=te()}){return await this.http.requireScopes(["lixblogs:collaboration:write"]),this.request("/api/v1/collaboration/invitations",{method:"POST",headers:{"idempotency-key":i},body:JSON.stringify({blogId:e,action:r,showOnProfile:o})})}};async function sr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new p(e?.error?.code||`http_${t.status}`,e?.error?.message||`LixBlogs returned HTTP ${t.status}.`,{status:t.status,requestId:e?.error?.requestId||t.headers.get("x-request-id"),details:e?.error?.details});return e}var oe=class{constructor(e){this.http=e}async query(e={}){let r=e.scope||"personal";await this.http.requireScopes(["lixblogs:analytics:read",...r.startsWith("org:")?["lixblogs:organizations:read"]:[]]);let o=new URLSearchParams({scope:r,range:e.range||(e.from||e.to?"custom":"30d"),dimension:e.dimension||"overview",limit:String(e.limit||20)});return e.from&&o.set("from",e.from),e.to&&o.set("to",e.to),e.cursor&&o.set("cursor",e.cursor),sr(await this.http.request(`/api/v1/analytics?${o}`,{headers:{accept:"application/json"}}))}};var ie=class{constructor(e){this.http=e}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async _request(e,r={}){let o=await this.http.request(e,r),i;try{i=await o.json()}catch{i=null}if(!o.ok||i?.error){let n=new Error(i?.error?.message||`Request failed with HTTP ${o.status}`);throw n.code=i?.error?.code||`http_${o.status}`,n.status=o.status,n.requestId=i?.error?.requestId||o.headers.get("x-request-id")||null,n.details=i?.error?.details||null,n}return i.data}async cloudinaryStatus(){return await this.requireScopes(["lixblogs:integrations:cloudinary:read"]),this._request("/api/v1/integrations/cloudinary")}async cloudinaryDisconnect(){return await this.requireScopes(["lixblogs:integrations:cloudinary:disconnect"]),this._request("/api/v1/integrations/cloudinary",{method:"DELETE"})}};var h=Object.freeze({OK:0,ERROR:1,USAGE:2,CONFLICT:3,AUTH:4,CONFIRMATION:5}),ar=Object.freeze({login:["auth","login"],logout:["auth","logout"],whoami:["auth","whoami"],profiles:["auth","profiles"],use:["auth","use"]});function He(t){let[e,...r]=t,o=ar[e];return o?[...o,...r]:t}function Ke(t,e="cli_error"){if(t&&typeof t=="object"&&t.error&&!Array.isArray(t.error))return t;let r=t&&typeof t=="object"?t:{message:String(t||"Command failed.")};return{ok:!1,error:{code:r.code||e,message:r.message||"Command failed.",hint:r.hint||null,requestId:r.requestId||null,...r.details?{details:r.details}:{}}}}function w(t,e){if(t.yes)return;let r=new Error(`${e} requires --yes in non-interactive operation.`);throw r.code="confirmation_required",r.hint="Review the operation, then run it again with --yes.",r.exitCode=h.CONFIRMATION,r}var S=Object.freeze({reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",violet:"\x1B[38;5;141m",green:"\x1B[38;5;42m"});function he(t=process.stdout,e=process.env){return!!t.isTTY&&e.NO_COLOR===void 0&&e.TERM!=="dumb"}function A(t,e,r){return r?`${e}${t}${S.reset}`:t}function We({url:t,code:e,expiresInSeconds:r,profile:o,interactive:i,color:n=!1}){let a=`${A("\u25C6",S.violet,n)} ${A("LixBlogs",S.bold,n)}`,s=i?"Press Enter to open here, or use the URL on another device.":"Open the URL in any browser and approve this device.";return["",` ${a}`,` ${A("Device login",S.dim,n)}`," \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",` URL ${t}`,` Code ${A(e,S.bold,n)}`,` Expires ${Math.ceil(r/60)} min`,o?` Profile ${o} ${A("(local credential slot)",S.dim,n)}`:` Profile ${A("your Accounts username after approval",S.dim,n)}`,"",` ${s}`," No localhost callback or exposed port is required.",""].join(`
5
- `)}function Xe(t,e=!1){return` ${A("\u2713",S.green,e)} ${t}`}function Ye({input:t=process.stdin,open:e,url:r}){if(!t.isTTY||typeof e!="function")return()=>{};let o=()=>{Promise.resolve(e(r)).catch(()=>{})};return t.setEncoding?.("utf8"),t.once("data",o),t.resume?.(),()=>{t.off?.("data",o),t.pause?.()}}function C(t){return[{type:"text",text:t}]}function Ze(t){let e=String(t||"").replace(/\r\n/g,`
2
+ import{parseArgs as Mr}from"node:util";import{spawn as Fr}from"node:child_process";var V={environment:"production",profile:"default",accountsBaseUrl:"https://accounts.elixpo.com",apiBaseUrl:"https://blogs.elixpo.com"},Re={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 I({flags:t={},env:e=process.env}={}){let r=t.env??e.LIXBLOGS_ENV??V.environment,o=t.profile??e.LIXBLOGS_PROFILE??V.profile,i=Re[r]||Re.production,n=t.authProvider??e.LIXBLOGS_AUTH_PROVIDER??(r==="production"?"elixpo":"mock"),a=t.accountsUrl??e.LIXBLOGS_ACCOUNTS_URL??V.accountsBaseUrl,s=t.apiUrl??e.LIXBLOGS_API_URL??V.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 Pe={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-"},ir=5,Te=0;function nr(t){return Te+=1,`${t}${Te}`}var J=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=Pe[r]??Pe.APPROVE_IMMEDIATELY,i=nr(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:ir}:{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 Oe="urn:ietf:params:oauth:grant-type:device_code",sr=1,ar=15e3,lr={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."},x=class extends Error{constructor(e,{status:r=0,requiresLogin:o=!1}={}){super(lr[e]||"Authentication failed."),this.name="AuthProviderError",this.code=e||"authentication_failed",this.status=r,this.requiresLogin=o}},k=class extends Error{constructor(e){super(e),this.name="CompatibilityError",this.code="incompatible_accounts_contract"}};function de(t){return String(t||"0.0.0").split(".").slice(0,3).map(e=>Number.parseInt(e,10)||0)}function cr(t,e){let r=de(t),o=de(e);for(let i=0;i<3;i+=1)if(r[i]!==o[i])return r[i]>o[i];return!0}function ur(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 k("Accounts must use HTTPS outside local development.");return e.toString().replace(/\/$/,"")}async function F(t){try{return await t.json()}catch{throw new x("server_error",{status:t.status})}}function H(t,e){let r=typeof t?.error=="string"?t.error:"server_error";return new x(r,{status:e.status,requiresLogin:r==="invalid_grant"||r==="access_denied"||r==="expired_token"})}function je(t,e){if(!e.ok)throw H(t,e);if(typeof t?.access_token!="string"||typeof t?.refresh_token!="string"||!Number.isFinite(Number(t?.expires_in)))throw new x("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 W=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=ar}={}){if(super(),typeof n!="function")throw new TypeError("A fetch implementation is required.");this.accountsBaseUrl=ur(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 x("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 x("invalid_scope");return this._metadata}async _loadDiscovery(){let e=await this._fetch(`${this.accountsBaseUrl}/.well-known/oauth-authorization-server`),r=await F(e);if(!e.ok)throw new x("temporarily_unavailable",{status:e.status});if(de(r.elixpo_contract_version)[0]!==sr)throw new k("Accounts uses an unsupported device-flow contract version.");if(!cr(this.cliVersion,r.elixpo_min_compatible_cli_version))throw new k(`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(Oe))throw new k("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 k(`Accounts discovery is missing ${n}.`);let a=new URL(r[n]),s=new URL(this.accountsBaseUrl);if(a.origin!==s.origin)throw new k(`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 F(o);if(!o.ok)throw H(i,o);if(typeof i.device_code!="string"||typeof i.user_code!="string"||typeof i.verification_uri!="string")throw new x("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:Oe,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 F(i);if(i.ok)return{status:"approved",token:je(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 H(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 F(n);return je(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 F(o);throw H(i,o)}}};var Ce="elixpo",pe=class extends Error{constructor(e){super(e),this.name="ProductionAuthGateError"}};function Le({providerId:t,environment:e}){if(e==="production"&&t!==Ce)throw new pe(`Provider "${t}" is not approved for production. Only "${Ce}" may be used in production.`)}function T(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 J:new W({accountsBaseUrl:t.accountsBaseUrl,clientId:t.clientId,audience:t.audience,cliVersion:t.cliVersion||"1.2.0",fetchImpl:t.fetchImpl});return Le({providerId:e.providerId,environment:t.environment}),e}var S=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")}},X=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 S(`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 S(`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 S(`OS keychain is unavailable: ${r.message}`)}}async listProfiles(){try{return await this._realStore.listProfiles()}catch(e){throw new S(`OS keychain is unavailable: ${e.message}`)}}};import{Entry as dr}from"@napi-rs/keyring";var pr="lixblogs-cli",fr="__lixblogs_availability_probe__";function Y(t){return new dr(pr,t)}async function De(){let t=Y(fr);try{return t.setPassword("probe"),t.deletePassword(),{available:!0}}catch(e){return{available:!1,error:String(e.message??e).split(`
3
+ `)[0].trim()}}}var fe=class extends A{async get(e){let r=Y(e),o;try{o=r.getPassword()}catch(i){if(Ue(i))return null;throw i}return o==null?null:JSON.parse(o)}async set(e,r){Y(e).setPassword(JSON.stringify(r))}async delete(e){let r=Y(e);try{r.deletePassword()}catch(o){if(Ue(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 Ue(t){let e=String(t?.message??"");return/no such|not found|nosuchkeyring|nosuchitem/i.test(e)}var Z=class extends A{constructor(e){super(),this._keychain=new fe,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 Q}from"node:fs";import Ne from"node:path";import mr from"node:os";function gr(){return Ne.join(mr.homedir(),".config","lixblogs","profiles.json")}var w=class{constructor(e=gr()){this._path=e}async list(){return(await this._read()).profiles}async getActive(){let e=await this._read();return e.activeProfile&&e.profiles.includes(e.activeProfile)?e.activeProfile:e.profiles[0]||null}async setActive(e){E(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 Q.readFile(this._path,"utf8"),r=JSON.parse(e);return{profiles:Array.isArray(r.profiles)?r.profiles.filter(i=>typeof i=="string"):[],activeProfile:typeof r.activeProfile=="string"?r.activeProfile:null}}catch(e){if(e.code==="ENOENT")return{profiles:[],activeProfile:null};throw e}}async add(e){E(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 Q.mkdir(Ne.dirname(this._path),{recursive:!0});let o=`${this._path}.${process.pid}.tmp`;await Q.writeFile(o,JSON.stringify({activeProfile:r,profiles:e},null,2),{encoding:"utf8",mode:384}),await Q.rename(o,this._path)}};function E(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 Be({allowInsecureFallback:t=!1,profileRegistry:e}={}){let r=await De();if(!r.available){if(!t)throw new S(`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 X}let o=e||new w,i=new Z(o);return new K(i)}var ge="[REDACTED]",hr=/token|refresh|secret|password|authorization/i,yr=/^(mock-(access|refresh)-|Bearer\s+)\S+/i;function Me(t){return typeof t=="string"&&yr.test(t)?ge:t}function me(t){if(Array.isArray(t))return t.map(e=>me(e));if(t&&typeof t=="object"){let e={};for(let[r,o]of Object.entries(t))hr.test(r)?e[r]=ge:typeof o=="object"&&o!==null?e[r]=me(o):e[r]=Me(o);return e}return Me(t)}function ee(t,e){return JSON.stringify(me(t),null,e)}function O(t){return typeof t!="string"?t:t.replace(/(mock-(access|refresh)-\S+|Bearer\s+\S+)/gi,ge)}async function Fe({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:O(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,d=Date.now()+l.expiresInSeconds*1e3;for(;Date.now()<d;){await a(u);let c;try{c=await t.pollDeviceCode({deviceCode:l.deviceCode})}catch(p){return{ok:!1,reason:O(p.message)}}if(c.status==="approved"){let p=r;if(n)try{p=await n({accessToken:c.token.accessToken,requestedProfileId:r})}catch(v){return{ok:!1,reason:O(v.message)}}return await e.set(p,{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:p}}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 ze({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 Ge({credentialStore:t,profileId:e}){return await t.delete(e),{ok:!0}}async function Ve({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 Je({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 He({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 We({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 E(n.data.username)}var wr=6e4,Xe=new WeakMap;function br(t){let e=Xe.get(t);return e||(e=new Map,Xe.set(t,e)),e}var te=class extends Error{constructor(e){super(`Profile "${e}" needs to log in again.`),this.name="LoginRequiredError",this.code="login_required"}},he=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."}},j=class{constructor({provider:e,credentialStore:r,profileId:o,apiBaseUrl:i="https://blogs.elixpo.com",fetchImpl:n=globalThis.fetch,refreshSkewMs:a=wr}){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=br(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 x&&s.requiresLogin?(await this.credentialStore.delete(this.profileId),new te(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 te(this.profileId);return e||r.expiresAt-Date.now()<=this.refreshSkewMs?this._refresh(r,{force:e}):r}async request(e,r={}){let o=await this.requestRaw(e,r),i=o.headers.get("content-type")||"";if(!i.toLowerCase().includes("application/json"))throw new he(o.status,i);return o}async requestRaw(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();return a.status===401&&(i=await this.credentials({forceRefresh:!0}),a=await n()),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 Ke}from"node:crypto";var h=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 vr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new h(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 z=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 vr(s)}catch(s){if(!n||a>0||s instanceof h||s?.code)throw s;await this.sleep(250)}throw new h("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=Ke()}={}){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,status:o="published",idempotencyKey:i=Ke()}){return await this.requireScopes(["lixblogs:blog:publish"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/publish`,{method:"POST",headers:{"if-match":r,"idempotency-key":i},body:JSON.stringify({status: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 versions(e){return await this.requireScopes(["lixblogs:blog:read"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/versions`)).payload.data}async restoreVersion(e,r,{etag:o}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/versions`,{method:"POST",headers:{"if-match":o},body:JSON.stringify({versionId:r})})).payload.data}async comments(e){return await this.requireScopes(["lixblogs:blog:read"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/comments`)).payload.data}async comment(e,r,{parentId:o}={}){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/comments`,{method:"POST",body:JSON.stringify({content:r,parentId:o})})).payload.data}async deleteComment(e,r){return await this.requireScopes(["lixblogs:blog:write"]),(await this.request(`/api/v1/blogs/${encodeURIComponent(e)}/comments/${encodeURIComponent(r)}`,{method:"DELETE"})).payload.data}};async function xr(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new h(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 re=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 xr(s)}catch(s){if(!n||a>0||s instanceof h||s?.code)throw s;await this.sleep(250)}throw new h("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 oe}from"node:crypto";async function Ir(t){let e;try{e=await t.json()}catch{e=null}if(!t.ok||e?.error)throw new h(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 ie=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 Ir(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=oe()}){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=oe()}){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=oe()}={}){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=oe()}){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 h(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 ne=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 se=class{constructor(e){this.http=e}async requireScopes(e){typeof this.http.requireScopes=="function"&&await this.http.requireScopes(e)}async _request(e,r={}){let o=await this.http.request(e,r),i;try{i=await o.json()}catch{i=null}if(!o.ok||i?.error){let n=new Error(i?.error?.message||`Request failed with HTTP ${o.status}`);throw n.code=i?.error?.code||`http_${o.status}`,n.status=o.status,n.requestId=i?.error?.requestId||o.headers.get("x-request-id")||null,n.details=i?.error?.details||null,n}return i.data}async cloudinaryStatus(){return await this.requireScopes(["lixblogs:integrations:cloudinary:read"]),this._request("/api/v1/integrations/cloudinary")}async cloudinaryDisconnect(){return await this.requireScopes(["lixblogs:integrations:cloudinary:disconnect"]),this._request("/api/v1/integrations/cloudinary",{method:"DELETE"})}async pollinationsStatus({refresh:e=!1}={}){return await this.requireScopes(["lixblogs:media:read"]),this._request(`/api/v1/integrations/pollinations${e?"?refresh=1":""}`)}async pollinationsDisconnect(){return await this.requireScopes(["lixblogs:media:write"]),this._request("/api/v1/integrations/pollinations",{method:"DELETE"})}};import{randomUUID as Ye}from"node:crypto";var _r=Object.freeze({"image/avif":"avif","image/bmp":"bmp","image/jpeg":"jpg","image/png":"png","image/svg+xml":"svg","image/webp":"webp"});async function ye(t){let e=await t.json().catch(()=>({})),r=new Error(e.error?.message||e.error||`Media request failed with HTTP ${t.status}`);return r.code=e.error?.code||e.code||`http_${t.status}`,r.status=t.status,r}var ae=class{constructor(e){this.http=e}async generate({prompt:e,model:r="flux",seed:o,width:i,height:n,destination:a="inline",generationId:s=Ye(),reference:l}){await this.http.requireScopes(["lixblogs:media:write"]);let u,d;if(l){u=new FormData;for(let[p,v]of Object.entries({prompt:e,model:r,seed:o,width:i,height:n,destination:a,generationId:s}))v!==void 0&&u.append(p,String(v));u.append("referenceImage",new Blob([l.bytes],{type:l.mimeType}),l.name||"reference-image"),d={accept:"image/*, application/json"}}else u=JSON.stringify({prompt:e,model:r,seed:o,width:i,height:n,destination:a,generationId:s}),d={"content-type":"application/json",accept:"image/*, application/json"};let c=await this.http.requestRaw("/api/v1/media/generate",{method:"POST",headers:d,body:u});if(!c.ok)throw await ye(c);return{bytes:new Uint8Array(await c.arrayBuffer()),mimeType:c.headers.get("content-type")||"image/jpeg",generationId:s}}async upload({bytes:e,mimeType:r,blogId:o,mediaType:i="inline",uploadId:n=Ye()}){await this.http.requireScopes(["lixblogs:media:write"]);let a=new FormData,s=_r[r];if(!s)throw new Error(`Unsupported image MIME type: ${r}`);a.append("file",new Blob([e],{type:r}),`lixblogs-${n}.${s}`),a.append("type",i),a.append("uploadId",n),o&&a.append("blogId",o);let l=await this.http.requestRaw("/api/v1/media/upload",{method:"POST",body:a,headers:{accept:"application/json"}});if(!l.ok)throw await ye(l);return l.json()}async delete(e){if(!e)throw new Error("A media ID is required.");await this.http.requireScopes(["lixblogs:media:write"]);let r=await this.http.request(`/api/v1/media/${encodeURIComponent(e)}`,{method:"DELETE"});if(!r.ok)throw await ye(r);let o=await r.json();return o.data||o}};var m=Object.freeze({OK:0,ERROR:1,USAGE:2,CONFLICT:3,AUTH:4,CONFIRMATION:5}),kr=Object.freeze({login:["auth","login"],logout:["auth","logout"],whoami:["auth","whoami"],profiles:["auth","profiles"],use:["auth","use"]});function Ze(t){let[e,...r]=t,o=kr[e];return o?[...o,...r]:t}function Qe(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 g(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=m.CONFIRMATION,r}var q=Object.freeze({reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",violet:"\x1B[38;5;141m",green:"\x1B[38;5;42m"});function we(t=process.stdout,e=process.env){return!!t.isTTY&&e.NO_COLOR===void 0&&e.TERM!=="dumb"}function R(t,e,r){return r?`${e}${t}${q.reset}`:t}function et({url:t,code:e,expiresInSeconds:r,profile:o,interactive:i,color:n=!1}){let a=`${R("\u25C6",q.violet,n)} ${R("LixBlogs",q.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}`,` ${R("Device login",q.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 ${R(e,q.bold,n)}`,` Expires ${Math.ceil(r/60)} min`,o?` Profile ${o} ${R("(local credential slot)",q.dim,n)}`:` Profile ${R("your Accounts username after approval",q.dim,n)}`,"",` ${s}`," No localhost callback or exposed port is required.",""].join(`
5
+ `)}function tt(t,e=!1){return` ${R("\u2713",q.green,e)} ${t}`}function rt({input:t=process.stdin,open:e,url:r}){if(!t.isTTY||typeof e!="function")return()=>{};let o=()=>{Promise.resolve(e(r)).catch(()=>{})};return t.setEncoding?.("utf8"),t.once("data",o),t.resume?.(),()=>{t.off?.("data",o),t.pause?.()}}function C(t){return[{type:"text",text:t}]}function ot(t){let e=String(t||"").replace(/\r\n/g,`
6
6
  `).split(`
7
- `),r=[],o=[],i=()=>{o.length&&(r.push({type:"paragraph",content:C(o.join(" ").trim())}),o=[])};for(let n=0;n<e.length;n+=1){let s=e[n].trim();if(!s){i();continue}let l=s.match(/^```([\w+-]*)/);if(l){i();let ae=[];for(n+=1;n<e.length&&!/^```/.test(e[n].trim());)ae.push(e[n++]);r.push(l[1].toLowerCase()==="mermaid"?{type:"mermaidBlock",props:{diagram:ae.join(`
8
- `)}}:{type:"codeBlock",props:{language:l[1].toLowerCase()},content:C(ae.join(`
9
- `))});continue}let u=s.match(/^(#{1,3})\s+(.+)/);if(u){i(),r.push({type:"heading",props:{level:String(u[1].length)},content:C(u[2])});continue}let f=s.match(/^[-*]\s+(.+)/);if(f){i(),r.push({type:"bulletListItem",content:C(f[1])});continue}let c=s.match(/^\d+\.\s+(.+)/);if(c){i(),r.push({type:"numberedListItem",content:C(c[1])});continue}let g=s.match(/^>\s?(.*)/);if(g){i(),r.push({type:"quote",content:C(g[1])});continue}let D=s.match(/^!\[([^\]]*)\]\((https:\/\/[^)]+)\)$/);if(D){i(),r.push({type:"image",props:{url:D[2],caption:D[1]}});continue}if(/^([-*_])\1{2,}$/.test(s)){i(),r.push({type:"divider"});continue}o.push(s)}return i(),r}function lr(t){return(t?.content||[]).map(e=>typeof e=="string"?e:e?.text||"").join("")}function ne(t){return(t||[]).map(e=>{let r=lr(e);return e.type==="heading"?`${"#".repeat(Number(e.props?.level)||1)} ${r}`:e.type==="bulletListItem"?`- ${r}`:e.type==="numberedListItem"?`1. ${r}`:e.type==="quote"?`> ${r}`:e.type==="codeBlock"?`\`\`\`${e.props?.language||""}
7
+ `),r=[],o=[],i=()=>{o.length&&(r.push({type:"paragraph",content:C(o.join(" ").trim())}),o=[])};for(let n=0;n<e.length;n+=1){let s=e[n].trim();if(!s){i();continue}let l=s.match(/^```([\w+-]*)/);if(l){i();let ue=[];for(n+=1;n<e.length&&!/^```/.test(e[n].trim());)ue.push(e[n++]);r.push(l[1].toLowerCase()==="mermaid"?{type:"mermaidBlock",props:{diagram:ue.join(`
8
+ `)}}:{type:"codeBlock",props:{language:l[1].toLowerCase()},content:C(ue.join(`
9
+ `))});continue}let u=s.match(/^(#{1,3})\s+(.+)/);if(u){i(),r.push({type:"heading",props:{level:String(u[1].length)},content:C(u[2])});continue}let d=s.match(/^[-*]\s+(.+)/);if(d){i(),r.push({type:"bulletListItem",content:C(d[1])});continue}let c=s.match(/^\d+\.\s+(.+)/);if(c){i(),r.push({type:"numberedListItem",content:C(c[1])});continue}let p=s.match(/^>\s?(.*)/);if(p){i(),r.push({type:"quote",content:C(p[1])});continue}let v=s.match(/^!\[([^\]]*)\]\((https:\/\/[^)]+)\)$/);if(v){i(),r.push({type:"image",props:{url:v[2],caption:v[1]}});continue}if(/^([-*_])\1{2,}$/.test(s)){i(),r.push({type:"divider"});continue}o.push(s)}return i(),r}function Sr(t){return(t?.content||[]).map(e=>typeof e=="string"?e:e?.text||"").join("")}function le(t){return(t||[]).map(e=>{let r=Sr(e);return e.type==="heading"?`${"#".repeat(Number(e.props?.level)||1)} ${r}`:e.type==="bulletListItem"?`- ${r}`:e.type==="numberedListItem"?`1. ${r}`:e.type==="quote"?`> ${r}`:e.type==="codeBlock"?`\`\`\`${e.props?.language||""}
10
10
  ${r}
11
11
  \`\`\``:e.type==="mermaidBlock"?`\`\`\`mermaid
12
12
  ${e.props?.diagram||""}
13
13
  \`\`\``:e.type==="image"?`![${e.props?.caption||""}](${e.props?.url||""})`:e.type==="divider"?"---":r}).join(`
14
14
 
15
- `)}import{promises as M}from"node:fs";import{tmpdir as cr}from"node:os";import me from"node:path";import{spawn as ur}from"node:child_process";async function dr(t){let e="";t.setEncoding("utf8");for await(let r of t)e+=r;return e}async function fr(t="",e=process.env.EDITOR||process.env.VISUAL){if(!e)throw new Error("$EDITOR or $VISUAL must be set when using --editor.");let r=await M.mkdtemp(me.join(cr(),"lixblogs-")),o=me.join(r,"post.md");await M.writeFile(o,t,{mode:384});try{return await new Promise((i,n)=>{let a=ur(e,[o],{stdio:"inherit",shell:!0});a.once("error",n),a.once("exit",s=>s===0?i():n(new Error(`Editor exited with code ${s}.`)))}),await M.readFile(o,"utf8")}finally{await M.rm(r,{recursive:!0,force:!0})}}async function ge(t,{stdin:e=process.stdin,initial:r=""}={}){let o=[t.file!==void 0,t.stdin,t.content!==void 0,t.editor].filter(Boolean).length;if(o>1)throw new Error("Use only one of --file, --stdin, --content, or --editor.");if(!o)return null;let i;return t.file!==void 0?i=await M.readFile(me.resolve(t.file),"utf8"):t.stdin?i=await dr(e):t.content!==void 0?i=t.content:i=await fr(r),{markdown:i,blocks:Ze(i)}}function ye(t){let e={},r={title:"title",subtitle:"subtitle",slug:"slug",emoji:"emoji",publication:"publishedAs",collection:"collectionId",cover:"coverUrl"};for(let[o,i]of Object.entries(r))t[o]!==void 0&&(e[i]=t[o]);return t.tag!==void 0&&(e.tags=t.tag),t["member-only"]&&(e.memberOnly=!0),t["no-member-only"]&&(e.memberOnly=!1),t.secret&&(e.secret=!0),t["not-secret"]&&(e.secret=!1),e}function pr(t){let e=[],r=o=>{for(let i of o||[]){for(let n of i?.content||[]){let a=typeof n=="string"?n:n?.text||"";e.push(...a.trim().split(/\s+/).filter(Boolean))}i?.children&&r(i.children)}};return r(t),e.length}function se(t,{publishing:e=!1}={}){if(t.title!==void 0&&(typeof t.title!="string"||t.title.length>300))throw new Error("Title must be 300 characters or fewer.");if(t.subtitle!==void 0&&(typeof t.subtitle!="string"||t.subtitle.length>500))throw new Error("Subtitle must be 500 characters or fewer.");if(t.tags!==void 0&&(!Array.isArray(t.tags)||t.tags.length>5))throw new Error("Use at most five tags.");if(t.coverUrl&&!/^https:\/\//i.test(t.coverUrl))throw new Error("Cover URLs must use HTTPS.");if(t.publishedAs&&t.publishedAs!=="personal"&&!/^org:[^:]+$/.test(t.publishedAs))throw new Error("Publication must be personal or org:<id>.");if(t.content!==void 0){if(!Array.isArray(t.content))throw new Error("Blog content must be a block array.");if(Buffer.byteLength(JSON.stringify(t.content),"utf8")>15e5)throw new Error("Blog content exceeds the 1.5 MB limit.")}if(e){if(!t.title?.trim())throw new Error("A title is required before publishing.");if(pr(t.content)<20)throw new Error("A post needs at least 20 words before publishing.")}return t}import{promises as we}from"node:fs";import be from"node:path";async function Qe({client:t,options:e}){return t.list({status:e.status,limit:e.limit,cursor:e.cursor})}async function ve({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");let r=await t.get(e);return{...r,markdown:ne(r.content)}}async function et({client:t,options:e,stdin:r}){let o=await ge(e,{stdin:r}),i={...ye(e),content:o?.blocks||[]};return se(i),e["dry-run"]?{dryRun:!0,input:i,markdown:o?.markdown||""}:t.create(i,{idempotencyKey:e["idempotency-key"]})}async function tt({client:t,id:e,options:r,stdin:o}){if(!e)throw new Error("A blog ID is required.");let i=await t.get(e),n=await ge(r,{stdin:o,initial:ne(i.content)}),a={...ye(r),...n?{content:n.blocks}:{}};if(!Object.keys(a).length)throw new Error("No blog changes were provided.");if(se(a),r["dry-run"])return{dryRun:!0,id:e,etag:i.etag,input:a,markdown:n?.markdown};try{return await t.update(e,a,{etag:r.etag||i.etag})}catch(s){if(!(s instanceof p)||s.code!=="revision_conflict")throw s;let l=await t.get(e),u=r.conflictDirectory||be.resolve(".lixblogs-conflicts");await we.mkdir(u,{recursive:!0});let f=e.replace(/[^A-Za-z0-9._-]/g,"_"),c=be.join(u,`${f}-local.json`),g=be.join(u,`${f}-server.md`);throw await Promise.all([we.writeFile(c,JSON.stringify(a,null,2),{mode:384}),we.writeFile(g,ne(l.content),{mode:384})]),s.details={...s.details,localPath:c,serverPath:g,serverEtag:l.etag},s}}async function rt({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return se(o,{publishing:!0}),r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"published"}:(w(r,"Publishing this blog"),t.publish(e,{etag:r.etag||o.etag,idempotencyKey:r["idempotency-key"]}))}async function ot({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:"draft"}:(w(r,"Unpublishing this blog"),t.unpublish(e,{etag:r.etag||o.etag}))}async function xe({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");if(!r.yes)throw new Error("Deletion requires --yes. Trash is the default; add --permanent for irreversible deletion.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,permanent:r.permanent}:t.delete(e,{etag:r.etag||o.etag,permanent:r.permanent})}async function it({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);return r["dry-run"]?{dryRun:!0,id:e,restoreTo:o.preDeleteStatus||"draft"}:(w(r,"Restoring this blog"),t.restore(e,{etag:r.etag||o.etag}))}async function nt({client:t}){return t.list()}async function st({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.get(e)}async function at({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.collections(e)}async function lt({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.members(e)}async function ct({client:t}){return t.targets()}function L(t){if(!t)throw new Error("A blog ID is required.")}async function ut({client:t,id:e}){return L(e),t.list(e)}async function dt({client:t}){return t.invitations()}async function ft({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"invite",blogId:e,user:r.user,role:r.role}:(w(r,"Inviting this collaborator"),t.invite(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function pt({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"role",blogId:e,user:r.user,role:r.role}:(w(r,"Changing this collaborator role"),t.role(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function ht({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"remove",blogId:e,user:r.user||"self"}:(w(r,"Removing this collaborator or invitation"),t.remove(e,{user:r.user,idempotencyKey:r["idempotency-key"]}))}async function mt({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"accept",blogId:e,showOnProfile:!r["hide-on-profile"]}:(w(r,"Accepting this collaboration invitation"),t.resolveInvitation(e,{action:"accept",showOnProfile:!r["hide-on-profile"],idempotencyKey:r["idempotency-key"]}))}async function gt({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"decline",blogId:e}:(w(r,"Declining this collaboration invitation"),t.resolveInvitation(e,{action:"decline",idempotencyKey:r["idempotency-key"]}))}import{access as hr,cp as mr,readFile as gr,readdir as yr}from"node:fs/promises";import b from"node:path";import{fileURLToPath as wr}from"node:url";var _e=b.dirname(wr(import.meta.url)),bt=b.basename(_e)==="dist"?b.resolve(_e,".."):b.resolve(_e,"../../.."),yt=b.join(bt,"skills"),wt=b.resolve(bt,"../..",".agents","skills");async function j(t){try{return await hr(t),!0}catch{return!1}}async function Ie(){if(await j(yt))return yt;if(await j(wt))return wt;let t=new Error("No bundled LixBlogs skills were found. Reinstall @elixpo/lixblogs-cli.");throw t.code="skills_unavailable",t}function vt(t){if(!/^lixblogs-[a-z0-9-]+$/.test(t||"")){let e=new Error("A valid lixblogs-* skill name is required.");throw e.code="invalid_skill_name",e}return t}async function xt(t,e){let r=await gr(b.join(t,e,"SKILL.md"),"utf8"),o=r.match(/^description:\s*(.+)$/m)?.[1]||r.match(/^description:\s*>-\s*\n\s*(.+)$/m)?.[1]||"",i=r.match(/`@elixpo\/lixblogs-cli`\s+([0-9.]+)/)?.[1]||null;return{name:e,description:o.trim(),minimumCliVersion:i,content:r}}async function _t(){let t=await Ie(),e=await yr(t,{withFileTypes:!0});return Promise.all(e.filter(r=>r.isDirectory()&&r.name.startsWith("lixblogs-")).map(r=>xt(t,r.name))).then(r=>r.map(({content:o,...i})=>i).sort((o,i)=>o.name.localeCompare(i.name)))}async function It({name:t}){let e=await Ie(),r=vt(t);if(!await j(b.join(e,r,"SKILL.md"))){let o=new Error(`Skill "${r}" is not bundled.`);throw o.code="skill_not_found",o}return xt(e,r)}async function Et({name:t,options:e}){let r=await Ie(),o=vt(t),i=b.join(r,o);if(!await j(b.join(i,"SKILL.md"))){let s=new Error(`Skill "${o}" is not bundled.`);throw s.code="skill_not_found",s}let n=b.resolve(e.target||".agents/skills"),a=b.join(n,o);if(e["dry-run"])return{dryRun:!0,name:o,target:a,replace:await j(a)};if(await j(a)){if(!e.force){let s=new Error(`Skill already exists at ${a}.`);throw s.code="skill_exists",s.hint="Inspect the existing skill or re-run with --force --yes to replace it.",s}w(e,`Replacing ${a}`)}else w(e,`Installing ${o} into ${n}`);return await mr(i,a,{recursive:!0,force:!!e.force}),{installed:!0,name:o,target:a}}import{writeFile as br}from"node:fs/promises";var vr=new Set(["overview","timeline","posts","sources","devices","countries"]),xr=new Set(["7d","30d","90d","12m","custom"]);function $t(t={}){let e=t.dimension||"overview",r=t.range||(t.from||t.to?"custom":"30d");if(!vr.has(e))throw new Error(`Unsupported analytics dimension: ${e}.`);if(!xr.has(r))throw new Error(`Unsupported analytics range: ${r}.`);if(r==="custom"&&(!t.from||!t.to))throw new Error("Custom analytics ranges require --from and --to.");return{scope:t.scope?.[0]||t.publication||"personal",range:r,from:t.from,to:t.to,dimension:e,limit:t.limit,cursor:t.cursor}}async function qt({client:t,options:e}){return t.query($t(e))}function kt(t){let e=t==null?"":typeof t=="object"?JSON.stringify(t):String(t);return/[",\n]/.test(e)?`"${e.replaceAll('"','""')}"`:e}function St(t){let e=t?.data?.values;return Array.isArray(e)?e:e?.labels&&Array.isArray(e.labels)?e.labels.map((r,o)=>({label:r,views:e.views?.[o]||0,reads:e.reads?.[o]||0})):e?.totals?Object.entries(e.totals).map(([r,o])=>({metric:r,value:o,previous:e.previous?.[r],change:e.changes?.[r]})):[]}async function At({client:t,options:e}){if(!e.output)throw new Error("Analytics export requires --output <file>.");let r=e.format||"json";if(!["json","csv"].includes(r))throw new Error("Analytics export format must be json or csv.");let o=await t.query($t(e)),i;if(r==="json")i=`${JSON.stringify(o,null,2)}
16
- `;else{let n=St(o),a=[...new Set(n.flatMap(s=>Object.keys(s)))];i=`${a.map(kt).join(",")}
17
- ${n.map(s=>a.map(l=>kt(s[l])).join(",")).join(`
15
+ `)}import{promises as G}from"node:fs";import{tmpdir as qr}from"node:os";import be from"node:path";import{spawn as $r}from"node:child_process";async function Ar(t){let e="";t.setEncoding("utf8");for await(let r of t)e+=r;return e}async function Rr(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 G.mkdtemp(be.join(qr(),"lixblogs-")),o=be.join(r,"post.md");await G.writeFile(o,t,{mode:384});try{return await new Promise((i,n)=>{let a=$r(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 G.readFile(o,"utf8")}finally{await G.rm(r,{recursive:!0,force:!0})}}async function ve(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 G.readFile(be.resolve(t.file),"utf8"):t.stdin?i=await Ar(e):t.content!==void 0?i=t.content:i=await Rr(r),{markdown:i,blocks:ot(i)}}function xe(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),t["allow-comments"]&&(e.allowComments=!0),t["no-comments"]&&(e.allowComments=!1),(t["cover-x"]!==void 0||t["cover-y"]!==void 0)&&(e.coverPosition={x:Number(t["cover-x"]??50),y:Number(t["cover-y"]??50)}),t["cover-zoom"]!==void 0&&(e.coverZoom=Number(t["cover-zoom"])),e}function Pr(t){let e=[],r=o=>{for(let i of o||[]){for(let n of i?.content||[]){let a=typeof n=="string"?n:n?.text||"";e.push(...a.trim().split(/\s+/).filter(Boolean))}i?.children&&r(i.children)}};return r(t),e.length}function ce(t,{publishing:e=!1}={}){if(t.title!==void 0&&(typeof t.title!="string"||t.title.length>300))throw new Error("Title must be 300 characters or fewer.");if(t.subtitle!==void 0&&(typeof t.subtitle!="string"||t.subtitle.length>500))throw new Error("Subtitle must be 500 characters or fewer.");if(t.tags!==void 0&&(!Array.isArray(t.tags)||t.tags.length>5))throw new Error("Use at most five tags.");if(t.coverUrl&&!/^https:\/\//i.test(t.coverUrl))throw new Error("Cover URLs must use HTTPS.");if(t.publishedAs&&t.publishedAs!=="personal"&&!/^org:[^:]+$/.test(t.publishedAs))throw new Error("Publication must be personal or org:<id>.");if(t.content!==void 0){if(!Array.isArray(t.content))throw new Error("Blog content must be a block array.");if(Buffer.byteLength(JSON.stringify(t.content),"utf8")>15e5)throw new Error("Blog content exceeds the 1.5 MB limit.")}if(e){if(!t.title?.trim())throw new Error("A title is required before publishing.");if(Pr(t.content)<20)throw new Error("A post needs at least 20 words before publishing.")}return t}import{promises as Ie}from"node:fs";import Ee from"node:path";async function it({client:t,options:e}){return t.list({status:e.status,limit:e.limit,cursor:e.cursor})}async function _e({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");let r=await t.get(e);return{...r,markdown:le(r.content)}}async function nt({client:t,options:e,stdin:r}){let o=await ve(e,{stdin:r}),i={...xe(e),content:o?.blocks||[]};return ce(i),e["dry-run"]?{dryRun:!0,input:i,markdown:o?.markdown||""}:t.create(i,{idempotencyKey:e["idempotency-key"]})}async function st({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 ve(r,{stdin:o,initial:le(i.content)}),a={...xe(r),...n?{content:n.blocks}:{}};if(!Object.keys(a).length)throw new Error("No blog changes were provided.");if(ce(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 h)||s.code!=="revision_conflict")throw s;let l=await t.get(e),u=r.conflictDirectory||Ee.resolve(".lixblogs-conflicts");await Ie.mkdir(u,{recursive:!0});let d=e.replace(/[^A-Za-z0-9._-]/g,"_"),c=Ee.join(u,`${d}-local.json`),p=Ee.join(u,`${d}-server.md`);throw await Promise.all([Ie.writeFile(c,JSON.stringify(a,null,2),{mode:384}),Ie.writeFile(p,le(l.content),{mode:384})]),s.details={...s.details,localPath:c,serverPath:p,serverEtag:l.etag},s}}async function at({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");let o=await t.get(e);ce(o,{publishing:!0});let i=r.status||"published";if(!["published","unlisted"].includes(i))throw new Error("--status must be published or unlisted.");return r["dry-run"]?{dryRun:!0,id:e,from:o.status,to:i}:(g(r,"Publishing this blog"),t.publish(e,{etag:r.etag||o.etag,status:i,idempotencyKey:r["idempotency-key"]}))}async function lt({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");return{data:await t.versions(e)}}async function ct({client:t,id:e,options:r}){if(!e||!r.version)throw new Error("A blog ID and --version are required.");g(r,"Restoring this historical version");let o=await t.get(e);return t.restoreVersion(e,r.version,{etag:r.etag||o.etag})}async function ut({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"}:(g(r,"Unpublishing this blog"),t.unpublish(e,{etag:r.etag||o.etag}))}async function ke({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 dt({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"}:(g(r,"Restoring this blog"),t.restore(e,{etag:r.etag||o.etag}))}async function pt({client:t}){return t.list()}async function ft({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.get(e)}async function mt({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.collections(e)}async function gt({client:t,id:e}){if(!e)throw new Error("An organization ID or handle is required.");return t.members(e)}async function ht({client:t}){return t.targets()}function L(t){if(!t)throw new Error("A blog ID is required.")}async function yt({client:t,id:e}){return L(e),t.list(e)}async function wt({client:t}){return t.invitations()}async function bt({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"invite",blogId:e,user:r.user,role:r.role}:(g(r,"Inviting this collaborator"),t.invite(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function vt({client:t,id:e,options:r}){if(L(e),!r.user)throw new Error("--user is required.");if(!["viewer","editor","admin"].includes(r.role))throw new Error("--role must be viewer, editor, or admin.");return r["dry-run"]?{dryRun:!0,action:"role",blogId:e,user:r.user,role:r.role}:(g(r,"Changing this collaborator role"),t.role(e,{user:r.user,role:r.role,idempotencyKey:r["idempotency-key"]}))}async function xt({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"remove",blogId:e,user:r.user||"self"}:(g(r,"Removing this collaborator or invitation"),t.remove(e,{user:r.user,idempotencyKey:r["idempotency-key"]}))}async function It({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"accept",blogId:e,showOnProfile:!r["hide-on-profile"]}:(g(r,"Accepting this collaboration invitation"),t.resolveInvitation(e,{action:"accept",showOnProfile:!r["hide-on-profile"],idempotencyKey:r["idempotency-key"]}))}async function Et({client:t,id:e,options:r}){return L(e),r["dry-run"]?{dryRun:!0,action:"decline",blogId:e}:(g(r,"Declining this collaboration invitation"),t.resolveInvitation(e,{action:"decline",idempotencyKey:r["idempotency-key"]}))}import{access as Tr,cp as Or,readFile as jr,readdir as Cr}from"node:fs/promises";import b from"node:path";import{fileURLToPath as Lr}from"node:url";var Se=b.dirname(Lr(import.meta.url)),St=b.basename(Se)==="dist"?b.resolve(Se,".."):b.resolve(Se,"../../.."),_t=b.join(St,"skills"),kt=b.resolve(St,"../..",".agents","skills");async function U(t){try{return await Tr(t),!0}catch{return!1}}async function qe(){if(await U(_t))return _t;if(await U(kt))return kt;let t=new Error("No bundled LixBlogs skills were found. Reinstall @elixpo/lixblogs-cli.");throw t.code="skills_unavailable",t}function qt(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 $t(t,e){let r=await jr(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 At(){let t=await qe(),e=await Cr(t,{withFileTypes:!0});return Promise.all(e.filter(r=>r.isDirectory()&&r.name.startsWith("lixblogs-")).map(r=>$t(t,r.name))).then(r=>r.map(({content:o,...i})=>i).sort((o,i)=>o.name.localeCompare(i.name)))}async function Rt({name:t}){let e=await qe(),r=qt(t);if(!await U(b.join(e,r,"SKILL.md"))){let o=new Error(`Skill "${r}" is not bundled.`);throw o.code="skill_not_found",o}return $t(e,r)}async function Pt({name:t,options:e}){let r=await qe(),o=qt(t),i=b.join(r,o);if(!await U(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 U(a)};if(await U(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}g(e,`Replacing ${a}`)}else g(e,`Installing ${o} into ${n}`);return await Or(i,a,{recursive:!0,force:!!e.force}),{installed:!0,name:o,target:a}}import{writeFile as Ur}from"node:fs/promises";var Dr=new Set(["overview","timeline","posts","sources","devices","countries"]),Nr=new Set(["7d","30d","90d","12m","custom"]);function jt(t={}){let e=t.dimension||"overview",r=t.range||(t.from||t.to?"custom":"30d");if(!Dr.has(e))throw new Error(`Unsupported analytics dimension: ${e}.`);if(!Nr.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 Ct({client:t,options:e}){return t.query(jt(e))}function Tt(t){let e=t==null?"":typeof t=="object"?JSON.stringify(t):String(t);return/[",\n]/.test(e)?`"${e.replaceAll('"','""')}"`:e}function Ot(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 Lt({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(jt(e)),i;if(r==="json")i=`${JSON.stringify(o,null,2)}
16
+ `;else{let n=Ot(o),a=[...new Set(n.flatMap(s=>Object.keys(s)))];i=`${a.map(Tt).join(",")}
17
+ ${n.map(s=>a.map(l=>Tt(s[l])).join(",")).join(`
18
18
  `)}
19
- `}return await br(e.output,i,{encoding:"utf8",flag:"wx"}),{output:e.output,format:r,rows:St(o).length}}import Rt from"node:fs";import _r from"node:path";import Ir from"node:os";async function Pt({client:t,target:e}){if(e==="cloudinary"){let r=await t.http.fetch("/api/integrations/cloudinary",{method:"DELETE"});if(!r.ok)throw r.status===409?new Error("This connection still owns blog media. Delete those assets from the Media tab before removing it."):new Error("Failed to disconnect Cloudinary");return{provider:e}}else if(e==="pollinations"){let r=_r.join(Ir.homedir(),".pollinations","credentials.json");if(Rt.existsSync(r))try{Rt.unlinkSync(r)}catch{throw new Error("Failed to clear local Pollinations credentials")}return{provider:e}}else throw new Error(`Unknown integration: ${e}`)}async function Tt({integrationsClient:t,confirmed:e}){if(e!==!0)return{ok:!1,reason:"Disconnect was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};try{return{ok:!0,data:await t.cloudinaryDisconnect()}}catch(r){return{ok:!1,error:r}}}async function Ot({integrationsClient:t}){try{return{ok:!0,data:await t.cloudinaryStatus()}}catch(e){return{ok:!1,error:e}}}var Sr={profile:{type:"string"},env:{type:"string"},json:{type:"boolean",default:!1},quiet:{type:"boolean",default:!1},yes:{type:"boolean",short:"y",default:!1},"allow-insecure-fallback":{type:"boolean",default:!1},"auth-provider":{type:"string"},"accounts-url":{type:"string"},"api-url":{type:"string"},"client-id":{type:"string"},audience:{type:"string"},scope:{type:"string",multiple:!0},open:{type:"boolean",default:!1},status:{type:"string"},limit:{type:"string"},cursor:{type:"string"},range:{type:"string"},from:{type:"string"},to:{type:"string"},dimension:{type:"string"},format:{type:"string"},output:{type:"string"},file:{type:"string"},stdin:{type:"boolean",default:!1},content:{type:"string"},editor:{type:"boolean",default:!1},title:{type:"string"},subtitle:{type:"string"},slug:{type:"string"},tag:{type:"string",multiple:!0},emoji:{type:"string"},publication:{type:"string"},collection:{type:"string"},cover:{type:"string"},"member-only":{type:"boolean",default:!1},"no-member-only":{type:"boolean",default:!1},secret:{type:"boolean",default:!1},"not-secret":{type:"boolean",default:!1},"dry-run":{type:"boolean",default:!1},"no-input":{type:"boolean",default:!1},etag:{type:"string"},permanent:{type:"boolean",default:!1},"idempotency-key":{type:"string"},user:{type:"string"},role:{type:"string"},"hide-on-profile":{type:"boolean",default:!1},target:{type:"string"},force:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1}},$r=`lixblogs \u2014 LixBlogs CLI
19
+ `}return await Ur(e.output,i,{encoding:"utf8",flag:"wx"}),{output:e.output,format:r,rows:Ot(o).length}}async function Ut({integrationsClient:t,confirmed:e}){if(e!==!0)return{ok:!1,reason:"Disconnect was not confirmed. This is a destructive action and requires explicit confirmation (interactive prompt, or --yes in a non-interactive session)."};try{return{ok:!0,data:await t.cloudinaryDisconnect()}}catch(r){return{ok:!1,error:r}}}async function Dt({integrationsClient:t}){try{return{ok:!0,data:await t.cloudinaryStatus()}}catch(e){return{ok:!1,error:e}}}import{randomUUID as Nt}from"node:crypto";import{promises as $e}from"node:fs";import D from"node:path";function Br(t){let e=r=>t[r]===void 0?void 0:Number.parseInt(t[r],10);return{width:e("width"),height:e("height"),seed:e("seed")}}var Bt=Object.freeze({".avif":"image/avif",".bmp":"image/bmp",".jpeg":"image/jpeg",".jpg":"image/jpeg",".png":"image/png",".svg":"image/svg+xml",".webp":"image/webp"});async function Mt({blogClient:t,blogId:e,media:r,type:o,caption:i}){if(!e)return null;let n=await t.get(e);if(o==="cover")return t.update(e,{coverUrl:r.url},{etag:n.etag});let a=[...n.content||[],{id:Nt(),type:"image",props:{url:r.url,caption:i||"",_mediaId:r.id||""},content:[],children:[]}];return t.update(e,{content:a},{etag:n.etag})}async function Ft({mediaClient:t,blogClient:e,options:r}){let o=r.prompt?.trim();if(!o)throw new Error("--prompt is required.");let i=r.type||"inline";if(!["inline","cover"].includes(i))throw new Error("--type must be inline or cover.");let n;if(r.reference){let c=D.resolve(r.reference),p=D.extname(c).toLowerCase(),v=Bt[p];if(!v)throw new Error("Unsupported reference image type. Use AVIF, BMP, JPEG, PNG, SVG, or WebP.");n={bytes:await $e.readFile(c),mimeType:v,name:D.basename(c)}}let a=await t.generate({prompt:o,model:r.model||"flux",destination:i,reference:n,...Br(r)}),s=a.mimeType.includes("png")?"png":a.mimeType.includes("webp")?"webp":"jpg",l=D.resolve(r.output||`lixblogs-${a.generationId}.${s}`);await $e.writeFile(l,a.bytes,{mode:384});let u=null,d=null;return r.blog&&(u=await t.upload({bytes:a.bytes,mimeType:a.mimeType,blogId:r.blog,mediaType:i,uploadId:a.generationId}),r.attach&&(d=await Mt({blogClient:e,blogId:r.blog,media:u,type:i,caption:r.caption}))),{generationId:a.generationId,output:l,mimeType:a.mimeType,media:u,blog:d}}async function zt({mediaClient:t,blogClient:e,options:r}){if(!r.file)throw new Error("--file is required.");if(!r.blog)throw new Error("--blog is required.");let o=r.type||"inline",i=D.resolve(r.file),n=await $e.readFile(i),a=D.extname(i).toLowerCase(),s=Bt[a];if(!s)throw new Error("Unsupported image type. Use AVIF, BMP, JPEG, PNG, SVG, or WebP.");let l=await t.upload({bytes:n,mimeType:s,blogId:r.blog,mediaType:o,uploadId:r["upload-id"]||Nt()}),u=r.attach?await Mt({blogClient:e,blogId:r.blog,media:l,type:o,caption:r.caption}):null;return{media:l,blog:u}}async function Gt({mediaClient:t,id:e,options:r}){if(!e)throw new Error("A media ID is required.");g(r,"Deleting this media asset from its storage provider");let o=await t.delete(e);return o?.data||o}async function Vt({client:t,id:e}){if(!e)throw new Error("A blog ID is required.");return t.comments(e)}async function Jt({client:t,id:e,options:r}){if(!e)throw new Error("A blog ID is required.");if(!r.content?.trim())throw new Error("--content is required.");return t.comment(e,r.content.trim())}async function Ht({client:t,id:e,options:r}){if(!e||!r.parent)throw new Error("A blog ID and --parent comment ID are required.");if(!r.content?.trim())throw new Error("--content is required.");return t.comment(e,r.content.trim(),{parentId:r.parent})}async function Wt({client:t,id:e,options:r}){if(!e||!r.comment)throw new Error("A blog ID and --comment ID are required.");return g(r,"Deleting this comment"),t.deleteComment(e,r.comment)}var zr={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},prompt:{type:"string"},reference:{type:"string"},model:{type:"string"},seed:{type:"string"},width:{type:"string"},height:{type:"string"},blog:{type:"string"},type:{type:"string"},attach:{type:"boolean",default:!1},caption:{type:"string"},"upload-id":{type:"string"},version:{type:"string"},parent:{type:"string"},comment:{type:"string"},"allow-comments":{type:"boolean",default:!1},"no-comments":{type:"boolean",default:!1},"cover-x":{type:"string"},"cover-y":{type:"string"},"cover-zoom":{type:"string"},help:{type:"boolean",short:"h",default:!1}},Gr=`lixblogs \u2014 LixBlogs CLI
20
20
 
21
21
  Usage:
22
22
  lixblogs login [--profile <name>] [--open]
@@ -41,6 +41,12 @@ Usage:
41
41
  lixblogs blog delete <id> --yes [--permanent] [--dry-run] [--json]
42
42
  lixblogs blog trash <id> --yes [--dry-run] [--json]
43
43
  lixblogs blog restore <id> --yes [--dry-run] [--json]
44
+ lixblogs blog history <id> [--json]
45
+ lixblogs blog restore-version <id> --version <version-id> --yes [--json]
46
+ lixblogs comment list <blog-id> [--json]
47
+ lixblogs comment add <blog-id> --content <text> [--json]
48
+ lixblogs comment reply <blog-id> --parent <comment-id> --content <text> [--json]
49
+ lixblogs comment delete <blog-id> --comment <comment-id> --yes [--json]
44
50
  lixblogs org list [--json]
45
51
  lixblogs org get <id> [--json]
46
52
  lixblogs org collections <id> [--json]
@@ -57,6 +63,11 @@ Usage:
57
63
  lixblogs analytics export --output <file> [--format json|csv] [query options]
58
64
  lixblogs integrations cloudinary-status [--json]
59
65
  lixblogs integrations cloudinary-disconnect --yes [--json]
66
+ lixblogs integrations pollinations-status [--json]
67
+ lixblogs integrations pollinations-disconnect --yes [--json]
68
+ lixblogs media generate --prompt <text> [--model flux] [--reference <image>] [--blog <id> --type inline|cover --attach] [--output <file>]
69
+ lixblogs media upload --file <image> --blog <id> [--type inline|cover] [--attach]
70
+ lixblogs media delete <media-id> --yes [--json]
60
71
  lixblogs skill list [--json]
61
72
  lixblogs skill inspect <name> [--json]
62
73
  lixblogs skill install <name> [--target <directory>] [--dry-run] --yes
@@ -91,16 +102,16 @@ Global flags:
91
102
  Machine mode:
92
103
  --json --no-input produces stable JSON on stdout, diagnostics on stderr, and
93
104
  never prompts. Publishing and destructive state changes require --yes.
94
- `,qr=["openid","profile","email","lixblogs:profile:read","lixblogs:blog:read"];function $(t){return{profile:t.profile,env:t.env,authProvider:t["auth-provider"],accountsUrl:t["accounts-url"],apiUrl:t["api-url"],clientId:t["client-id"],audience:t.audience}}async function U(t,e){return t.profileExplicit?_(t.profile):await e.getActive()||_(t.profile)}async function ke(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",r=process.platform==="win32"?["/c","start","",t]:[t],o=kr(e,r,{detached:!0,stdio:"ignore"});o.on("error",()=>{}),o.unref()}function m(t,e){t.json&&process.stdout.write(Z(e)+`
95
- `)}function d(t,e,r=h.ERROR){let o=e&&typeof e=="object"?e:{message:String(e)},i=T(o.message),n=Ke({...o,message:i});t.json?process.stdout.write(Z(n)+`
105
+ `,Vr=["openid","profile","email","lixblogs:profile:read","lixblogs:profile:write","lixblogs:blog:read","lixblogs:blog:write","lixblogs:blog:publish","lixblogs:blog:delete","lixblogs:media:read","lixblogs:media:write","lixblogs:organizations:read","lixblogs:organizations:write","lixblogs:collaboration:read","lixblogs:collaboration:write","lixblogs:analytics:read","lixblogs:notifications:read"];function $(t){return{profile:t.profile,env:t.env,authProvider:t["auth-provider"],accountsUrl:t["accounts-url"],apiUrl:t["api-url"],clientId:t["client-id"],audience:t.audience}}async function B(t,e){return t.profileExplicit?E(t.profile):await e.getActive()||E(t.profile)}async function Ae(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",r=process.platform==="win32"?["/c","start","",t]:[t],o=Fr(e,r,{detached:!0,stdio:"ignore"});o.on("error",()=>{}),o.unref()}function y(t,e){t.json&&process.stdout.write(ee(e)+`
106
+ `)}function f(t,e,r=m.ERROR){let o=e&&typeof e=="object"?e:{message:String(e)},i=O(o.message),n=Qe({...o,message:i});t.json?process.stdout.write(ee(n)+`
96
107
  `):t.quiet||(process.stderr.write(`Error: ${i}
97
108
  `),o.hint&&process.stderr.write(`Hint: ${o.hint}
98
109
  `),o.requestId&&process.stderr.write(`Request: ${o.requestId}
99
- `)),process.exitCode=o.exitCode||r}async function I(t,e){try{return await je({allowInsecureFallback:t["allow-insecure-fallback"],profileRegistry:e})}catch(r){return d(t,`${r.message}${t["allow-insecure-fallback"]?"":" Re-run with --allow-insecure-fallback to opt in to non-persistent storage instead."}`),null}}async function Lt(t){let e=x({flags:$(t)}),r=new y,o=_(e.profile),i=t.scope?.length?[...t.scope]:[...qr];!e.profileExplicit&&!i.includes("lixblogs:profile:read")&&i.push("lixblogs:profile:read");let n;try{n=P(e)}catch(u){return d(t,u.message)}let a=await I(t,r);if(!a)return;let s=()=>{},l;try{l=await De({provider:n,credentialStore:a,profileId:o,scopes:i,openBrowser:t.open?ke:void 0,resolveProfileId:e.profileExplicit?void 0:({accessToken:u})=>Ge({accessToken:u,apiBaseUrl:e.apiBaseUrl}),onStatus:u=>{if(t.json){u.type!=="pending"&&m(t,{event:u.type,...u});return}if(!t.quiet)if(u.type==="verification_pending"){let f=u.verificationUriComplete||u.verificationUri,c=!!process.stdin.isTTY&&!t["no-input"];process.stdout.write(We({url:f,code:u.userCode,expiresInSeconds:u.expiresInSeconds,profile:e.profileExplicit?o:null,interactive:c,color:he()})),c&&!t.open&&(s=Ye({input:process.stdin,open:ke,url:f}))}else u.type==="approved"?console.log(Xe("Access approved by Elixpo Accounts.",he())):u.type==="denied"?console.log(" Access denied."):u.type==="expired"&&console.log(" Device code expired.")}})}finally{s()}if(!l.ok)return d(t,l.reason);await r.add(l.profileId),await r.setActive(l.profileId),m(t,{ok:!0,profile:l.profileId}),!t.json&&!t.quiet&&(console.log(` Credentials saved to local profile "${l.profileId}".`),console.log(" Tip: add another account with `lixblogs login`, list accounts with `lixblogs profiles`,"),console.log(" and switch with `lixblogs use <username>`."))}async function Ar(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return;let n=await Ne({credentialStore:i,profileId:o});if(m(t,n),!t.json)for(let a of n)a.loggedIn?console.log(`${a.profileId}: logged in${a.expired?" (expired)":""} \u2014 scopes: ${a.scopes.join(", ")}`):console.log(`${a.profileId}: not logged in`)}async function F(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return null;let n;try{n=P(e)}catch(s){return d(t,s),null}let a=new O({provider:n,credentialStore:i,profileId:o,apiBaseUrl:e.apiBaseUrl});return{client:new B(a),http:a,config:e,credentialStore:i,profileId:o}}async function Rr(t){let e=await F(t);if(e)try{let[r,o]=await Promise.all([e.client.whoami(),e.credentialStore.get(e.profileId)]),i={ok:!0,profile:e.profileId,environment:e.config.environment,identity:r,scopes:o?.scopes||[],expiresAt:o?.expiresAt?new Date(o.expiresAt).toISOString():null,expired:o?Date.now()>=o.expiresAt:!0};m(t,i),!t.json&&!t.quiet&&(console.log(`${r.displayName||r.username} (@${r.username})`),console.log(`Profile: ${e.profileId} \xB7 ${i.environment}`),console.log(`Scopes: ${i.scopes.join(", ")||"none"}`),console.log(`Expires: ${i.expiresAt||"unknown"}`))}catch(r){d(t,r,r.status===401||r.status===403?h.AUTH:h.ERROR)}}async function Pr(t){let e=x({flags:$(t)}),r=new URL("/register",e.accountsBaseUrl).toString();if(t["no-input"]){m(t,{ok:!0,registrationUrl:r,next:"lixblogs login"}),!t.json&&!t.quiet&&console.log(r);return}await ke(r),t.quiet||console.log(`Create your account at ${r}, then approve the device login.`),await Lt(t)}async function Tr(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r),i=await I(t,r);if(!i)return;let n=await Be({credentialStore:i,profileId:o});m(t,n),!t.json&&!t.quiet&&console.log(`Logged out profile "${o}".`)}async function Or(t){let e=x({flags:$(t)}),r=new y,o=await U(e,r);if(!t.yes)return d(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let i;try{i=P(e)}catch(s){return d(t,s.message)}let n=await I(t,r);if(!n)return;let a=await Me({provider:i,credentialStore:n,profileId:o,confirmed:!0});if(!a.ok)return d(t,a.reason);m(t,a),!t.json&&!t.quiet&&console.log(`Revoked and logged out profile "${o}".`)}async function Ee(t,e,r){let o=x({flags:$(t)}),i=new y,n=await U(o,i);if(r==="cloudinary-disconnect"&&!t.yes)return d(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let a;try{a=P(o)}catch(c){return d(t,c.message)}let s=await I(t,i);if(!s)return;let l=new O({provider:a,credentialStore:s,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new ie(l),f=r==="cloudinary-status"?await Ot({integrationsClient:u}):await Tt({integrationsClient:u,confirmed:!0});if(!f.ok)return d(t,f.error||f.reason);m(t,f),!t.json&&!t.quiet&&console.log(r==="cloudinary-status"?`Cloudinary: ${f.data.connected?`connected (${f.data.cloudName})`:"not connected"}`:"Cloudinary connection disconnected.")}async function Cr(t){let e=new y,r=await I(t,e);if(!r)return;let o=await Fe({credentialStore:r,profileRegistry:e});if(m(t,o),!t.json){o.profiles.length||console.log("No profiles. Run `lixblogs auth login` first.");for(let i of o.profiles)console.log(`${i.active?"*":" "} ${i.profileId}${i.expired?" (expired)":""}`)}}async function Lr(t,e){let r;try{r=_(e[0])}catch(a){return d(t,a.message)}let o=new y,i=await I(t,o);if(!i)return;let n=await ze({credentialStore:i,profileRegistry:o,profileId:r});if(!n.ok)return d(t,n.reason);m(t,n),!t.json&&!t.quiet&&console.log(`Using profile "${r}".`)}var jt={list:Qe,get:ve,preview:ve,create:et,edit:tt,publish:rt,unpublish:ot,delete:xe,trash:xe,restore:it},Ut={list:nt,get:st,collections:at,members:lt,targets:ct},Dt={list:ut,invitations:dt,invite:ft,role:pt,remove:ht,accept:mt,decline:gt},Nt={list:({options:t})=>_t(t),inspect:({id:t})=>It({name:t}),install:({id:t,options:e})=>Et({name:t,options:e})},Bt={query:qt,export:At};async function jr(t,e,r){let o=x({flags:$(t)}),i=new y,n=await U(o,i),a=await I(t,i);if(!a)return;let s;try{s=P(o)}catch(c){return d(t,c.message)}let l=new O({provider:s,credentialStore:a,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new B(l),f={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let c=await jt[r]({client:u,id:e[0],options:f,stdin:process.stdin});if(m(t,{ok:!0,...c}),!t.json&&!t.quiet)if(r==="list"){for(let g of c.data||[])console.log(`${g.id} ${g.status} ${g.title||"(untitled)"}`);c.meta?.nextCursor&&console.log(`Next cursor: ${c.meta.nextCursor}`)}else r==="get"?console.log(`${c.title||"(untitled)"} [${c.status}]
100
- ${c.markdown||""}`):c.dryRun?console.log(`Dry run: ${r} validated; no changes sent.`):console.log(c.url||`${r} completed for ${c.id}.`)}catch(c){if(t.json&&c instanceof p){process.stdout.write(Z({ok:!1,error:{code:c.code,message:c.message,requestId:c.requestId,details:c.details}})+`
101
- `),process.exitCode=c.status===412?3:1;return}return d(t,c,c.status===412?h.CONFLICT:h.ERROR)}}async function Ur(t,e,r){let o=await F(t);if(!o)return;let i=new ee(o.http);try{let n=await Ut[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(r==="targets"){console.log("personal Personal Blog");for(let s of n.organizations||[]){console.log(`${s.target} ${s.role} ${s.name}`);for(let l of s.collections||[])console.log(` collection:${l.id} ${l.name}`)}return}let a=r==="list"?n.data||[]:Array.isArray(n)?n:[n];for(let s of a)console.log([s.id||s.userId||s.orgId,s.role,s.slug||s.username,s.name||s.displayName].filter(Boolean).join(" "))}catch(n){d(t,n,n.status===401||n.status===403?h.AUTH:h.ERROR)}}async function Dr(t,e,r){let o=await F(t);if(!o)return;let i=new re(o.http);try{let n=await Dt[r]({client:i,id:e[0],options:t});if(m(t,{ok:!0,data:n}),t.json||t.quiet)return;if(n.dryRun){console.log(`Dry run: ${n.action} validated; no changes sent.`);return}let a=r==="invitations"?n:r==="list"?n.collaborators||[]:[n];for(let s of a)console.log([s.blogId||s.userId,s.status,s.role,s.username||s.title,s.notificationState].filter(Boolean).join(" "))}catch(n){d(t,n,n.status===401||n.status===403?h.AUTH:h.ERROR)}}async function Nr(t,e,r){try{let o=await Nt[r]({id:e[0],options:t});if(m(t,{ok:!0,data:o}),t.json||t.quiet)return;if(r==="list")for(let i of o)console.log(`${i.name} CLI >= ${i.minimumCliVersion||"unknown"} ${i.description}`);else r==="inspect"?process.stdout.write(o.content):o.dryRun?console.log(`Dry run: install ${o.name} to ${o.target}${o.replace?" (replace)":""}.`):console.log(`Installed ${o.name} at ${o.target}.`)}catch(o){d(t,o)}}async function Br(t,e,r){let o=await F(t);if(!o)return;let i=new oe(o.http),n={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let a=await Bt[r]({client:i,options:n});if(m(t,{ok:!0,data:a}),t.json||t.quiet)return;if(r==="export"){console.log(`Exported ${a.rows} rows to ${a.output}.`);return}let s=a.data;if(console.log(`${s.scope.label} \xB7 ${s.dimension} \xB7 ${s.range.key}`),s.dimension==="overview")for(let[l,u]of Object.entries(s.values.totals))console.log(`${l} ${u} ${s.values.changes[l]}%`);else if(s.dimension==="timeline")s.values.labels.forEach((l,u)=>console.log(`${l} ${s.values.views[u]} ${s.values.reads[u]}`));else{for(let l of s.values)console.log(Object.values(l).join(" "));a.meta?.nextCursor&&console.log(`Next cursor: ${a.meta.nextCursor}`)}}catch(a){d(t,a,a.status===401||a.status===403?h.AUTH:h.ERROR)}}async function Mr(t,e,r){let o=await F(t);if(!o)return;let i=o.client;try{let n=await Pt({client:i,target:r});m(t,{ok:!0,data:n}),!t.json&&!t.quiet&&console.log(`Disconnected ${n.provider}.`)}catch(n){d(t,n)}}var Ct={auth:{login:Lt,status:Ar,whoami:Rr,logout:Tr,revoke:Or,profiles:Cr,use:Lr},blog:Object.fromEntries(Object.keys(jt).map(t=>[t,(e,r)=>jr(e,r,t)])),org:Object.fromEntries(Object.keys(Ut).map(t=>[t,(e,r)=>Ur(e,r,t)])),collab:Object.fromEntries(Object.keys(Dt).map(t=>[t,(e,r)=>Dr(e,r,t)])),skill:Object.fromEntries(Object.keys(Nt).map(t=>[t,(e,r)=>Nr(e,r,t)])),analytics:Object.fromEntries(Object.keys(Bt).map(t=>[t,(e,r)=>Br(e,r,t)])),integrations:{"cloudinary-status":(t,e)=>Ee(t,e,"cloudinary-status"),"cloudinary-disconnect":(t,e)=>Ee(t,e,"cloudinary-disconnect")},disconnect:{cloudinary:(t,e)=>Ee(t,e,"cloudinary-disconnect"),pollinations:(t,e)=>Mr(t,e,"pollinations")}};async function Fr(){let t,e;try{({values:t,positionals:e}=Er({args:process.argv.slice(2),options:Sr,allowPositionals:!0,strict:!0}))}catch(a){process.stderr.write(`Error: Invalid flag. ${a.message}
102
- `),process.exitCode=h.USAGE;return}if(t.help||e.length===0){process.stdout.write($r);return}if(e[0]==="register"){await Pr(t);return}e=He(e);let[r,o]=e,i=Ct[r];if(!i){process.stderr.write(`Error: Unknown command category "${r}".
103
- `),process.stderr.write(`Available categories: ${Object.keys(Ct).join(", ")}
104
- `),process.exitCode=h.USAGE;return}let n=i[o];if(!n){process.stderr.write(`Error: Unknown ${r} command "${o}".
110
+ `)),process.exitCode=o.exitCode||r}async function _(t,e){try{return await Be({allowInsecureFallback:t["allow-insecure-fallback"],profileRegistry:e})}catch(r){return f(t,`${r.message}${t["allow-insecure-fallback"]?"":" Re-run with --allow-insecure-fallback to opt in to non-persistent storage instead."}`),null}}async function Kt(t){let e=I({flags:$(t)}),r=new w,o=E(e.profile),i=t.scope?.length?[...t.scope]:[...Vr];!e.profileExplicit&&!i.includes("lixblogs:profile:read")&&i.push("lixblogs:profile:read");let n;try{n=T(e)}catch(u){return f(t,u.message)}let a=await _(t,r);if(!a)return;let s=()=>{},l;try{l=await Fe({provider:n,credentialStore:a,profileId:o,scopes:i,openBrowser:t.open?Ae:void 0,resolveProfileId:e.profileExplicit?void 0:({accessToken:u})=>We({accessToken:u,apiBaseUrl:e.apiBaseUrl}),onStatus:u=>{if(t.json){u.type!=="pending"&&y(t,{event:u.type,...u});return}if(!t.quiet)if(u.type==="verification_pending"){let d=u.verificationUriComplete||u.verificationUri,c=!!process.stdin.isTTY&&!t["no-input"];process.stdout.write(et({url:d,code:u.userCode,expiresInSeconds:u.expiresInSeconds,profile:e.profileExplicit?o:null,interactive:c,color:we()})),c&&!t.open&&(s=rt({input:process.stdin,open:Ae,url:d}))}else u.type==="approved"?console.log(tt("Access approved by Elixpo Accounts.",we())):u.type==="denied"?console.log(" Access denied."):u.type==="expired"&&console.log(" Device code expired.")}})}finally{s()}if(!l.ok)return f(t,l.reason);await r.add(l.profileId),await r.setActive(l.profileId),y(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 Jr(t){let e=I({flags:$(t)}),r=new w,o=await B(e,r),i=await _(t,r);if(!i)return;let n=await ze({credentialStore:i,profileId:o});if(y(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 M(t){let e=I({flags:$(t)}),r=new w,o=await B(e,r),i=await _(t,r);if(!i)return null;let n;try{n=T(e)}catch(s){return f(t,s),null}let a=new j({provider:n,credentialStore:i,profileId:o,apiBaseUrl:e.apiBaseUrl});return{client:new z(a),http:a,config:e,credentialStore:i,profileId:o}}async function Hr(t){let e=await M(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};y(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){f(t,r,r.status===401||r.status===403?m.AUTH:m.ERROR)}}async function Wr(t){let e=I({flags:$(t)}),r=new URL("/register",e.accountsBaseUrl).toString();if(t["no-input"]){y(t,{ok:!0,registrationUrl:r,next:"lixblogs login"}),!t.json&&!t.quiet&&console.log(r);return}await Ae(r),t.quiet||console.log(`Create your account at ${r}, then approve the device login.`),await Kt(t)}async function Xr(t){let e=I({flags:$(t)}),r=new w,o=await B(e,r),i=await _(t,r);if(!i)return;let n=await Ge({credentialStore:i,profileId:o});y(t,n),!t.json&&!t.quiet&&console.log(`Logged out profile "${o}".`)}async function Kr(t){let e=I({flags:$(t)}),r=new w,o=await B(e,r);if(!t.yes)return f(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let i;try{i=T(e)}catch(s){return f(t,s.message)}let n=await _(t,r);if(!n)return;let a=await Ve({provider:i,credentialStore:n,profileId:o,confirmed:!0});if(!a.ok)return f(t,a.reason);y(t,a),!t.json&&!t.quiet&&console.log(`Revoked and logged out profile "${o}".`)}async function N(t,e,r){let o=I({flags:$(t)}),i=new w,n=await B(o,i);if((r==="cloudinary-disconnect"||r==="pollinations-disconnect")&&!t.yes)return f(t,"This is a destructive action. Re-run with --yes to confirm (interactive confirmation prompt not yet implemented).");let a;try{a=T(o)}catch(c){return f(t,c.message)}let s=await _(t,i);if(!s)return;let l=new j({provider:a,credentialStore:s,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new se(l),d;if(r==="cloudinary-status")d=await Dt({integrationsClient:u});else if(r==="cloudinary-disconnect")d=await Ut({integrationsClient:u,confirmed:!0});else try{d={ok:!0,data:r==="pollinations-status"?await u.pollinationsStatus({refresh:t.force}):await u.pollinationsDisconnect()}}catch(c){d={ok:!1,error:c}}if(!d.ok)return f(t,d.error||d.reason);y(t,d),!t.json&&!t.quiet&&console.log(r==="cloudinary-status"?`Cloudinary: ${d.data.connected?`connected (${d.data.cloudName})`:"not connected"}`:r==="pollinations-status"?`Pollinations: ${d.data.connected?`connected${d.data.handle?` as ${d.data.handle}`:""} \xB7 ${d.data.balance??"unknown"} Pollen`:`${d.data.status}. Connect at ${d.data.connectUrl||"https://blogs.elixpo.com/settings?tab=integrations"}`}`:`${r.startsWith("pollinations")?"Pollinations":"Cloudinary"} connection disconnected.`)}async function Yr(t){let e=new w,r=await _(t,e);if(!r)return;let o=await Je({credentialStore:r,profileRegistry:e});if(y(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 Zr(t,e){let r;try{r=E(e[0])}catch(a){return f(t,a.message)}let o=new w,i=await _(t,o);if(!i)return;let n=await He({credentialStore:i,profileRegistry:o,profileId:r});if(!n.ok)return f(t,n.reason);y(t,n),!t.json&&!t.quiet&&console.log(`Using profile "${r}".`)}var Yt={list:it,get:_e,preview:_e,create:nt,edit:st,publish:at,unpublish:ut,delete:ke,trash:ke,restore:dt,history:lt,"restore-version":ct},Zt={list:pt,get:ft,collections:mt,members:gt,targets:ht},Qt={list:yt,invitations:wt,invite:bt,role:vt,remove:xt,accept:It,decline:Et},er={list:({options:t})=>At(t),inspect:({id:t})=>Rt({name:t}),install:({id:t,options:e})=>Pt({name:t,options:e})},tr={query:Ct,export:Lt},rr={generate:Ft,upload:zt,delete:Gt},or={list:Vt,add:Jt,reply:Ht,delete:Wt};async function Qr(t,e,r){let o=I({flags:$(t)}),i=new w,n=await B(o,i),a=await _(t,i);if(!a)return;let s;try{s=T(o)}catch(c){return f(t,c.message)}let l=new j({provider:s,credentialStore:a,profileId:n,apiBaseUrl:o.apiBaseUrl}),u=new z(l),d={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let c=await Yt[r]({client:u,id:e[0],options:d,stdin:process.stdin});if(y(t,{ok:!0,...c}),!t.json&&!t.quiet)if(r==="list"){for(let p of c.data||[])console.log(`${p.id} ${p.status} ${p.title||"(untitled)"}`);c.meta?.nextCursor&&console.log(`Next cursor: ${c.meta.nextCursor}`)}else if(r==="get")console.log(`${c.title||"(untitled)"} [${c.status}]
111
+ ${c.markdown||""}`);else if(r==="history")for(let p of c.data||[])console.log(`${p.id} ${p.label||"snapshot"} ${p.created_at} ${p.username||"system"}`);else 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 h){process.stdout.write(ee({ok:!1,error:{code:c.code,message:c.message,requestId:c.requestId,details:c.details}})+`
112
+ `),process.exitCode=c.status===412?3:1;return}return f(t,c,c.status===412?m.CONFLICT:m.ERROR)}}async function eo(t,e,r){let o=await M(t);if(!o)return;let i=new re(o.http);try{let n=await Zt[r]({client:i,id:e[0],options:t});if(y(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){f(t,n,n.status===401||n.status===403?m.AUTH:m.ERROR)}}async function to(t,e,r){let o=await M(t);if(o)try{let i=await rr[r]({mediaClient:new ae(o.http),blogClient:o.client,id:e[0],options:t});y(t,{ok:!0,data:i}),!t.json&&!t.quiet&&(console.log(r==="delete"?`Deleted media ${i.id}.`:`${r==="generate"?"Generated":"Uploaded"} image: ${i.media?.url||i.output||i.media?.publicId}`),i.blog&&console.log(`Attached to blog ${t.blog}.`))}catch(i){f(t,i,i.status===401||i.status===403?m.AUTH:m.ERROR)}}async function ro(t,e,r){let o=await M(t);if(o)try{let i=await or[r]({client:o.client,id:e[0],options:t});if(y(t,{ok:!0,data:i}),!t.json&&!t.quiet)if(r==="list")for(let n of i)console.log(`${n.id} ${n.parent_id?"reply":"comment"} ${n.display_name||n.username||"Anonymous"} ${n.content}`);else console.log(`${r} completed for ${i.id}.`)}catch(i){f(t,i,i.status===401||i.status===403?m.AUTH:m.ERROR)}}async function oo(t,e,r){let o=await M(t);if(!o)return;let i=new ie(o.http);try{let n=await Qt[r]({client:i,id:e[0],options:t});if(y(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){f(t,n,n.status===401||n.status===403?m.AUTH:m.ERROR)}}async function io(t,e,r){try{let o=await er[r]({id:e[0],options:t});if(y(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){f(t,o)}}async function no(t,e,r){let o=await M(t);if(!o)return;let i=new ne(o.http),n={...t,limit:t.limit===void 0?void 0:Number.parseInt(t.limit,10)};try{let a=await tr[r]({client:i,options:n});if(y(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){f(t,a,a.status===401||a.status===403?m.AUTH:m.ERROR)}}var Xt={auth:{login:Kt,status:Jr,whoami:Hr,logout:Xr,revoke:Kr,profiles:Yr,use:Zr},blog:Object.fromEntries(Object.keys(Yt).map(t=>[t,(e,r)=>Qr(e,r,t)])),org:Object.fromEntries(Object.keys(Zt).map(t=>[t,(e,r)=>eo(e,r,t)])),collab:Object.fromEntries(Object.keys(Qt).map(t=>[t,(e,r)=>oo(e,r,t)])),skill:Object.fromEntries(Object.keys(er).map(t=>[t,(e,r)=>io(e,r,t)])),analytics:Object.fromEntries(Object.keys(tr).map(t=>[t,(e,r)=>no(e,r,t)])),media:Object.fromEntries(Object.keys(rr).map(t=>[t,(e,r)=>to(e,r,t)])),comment:Object.fromEntries(Object.keys(or).map(t=>[t,(e,r)=>ro(e,r,t)])),integrations:{"cloudinary-status":(t,e)=>N(t,e,"cloudinary-status"),"cloudinary-disconnect":(t,e)=>N(t,e,"cloudinary-disconnect"),"pollinations-status":(t,e)=>N(t,e,"pollinations-status"),"pollinations-disconnect":(t,e)=>N(t,e,"pollinations-disconnect")},disconnect:{cloudinary:(t,e)=>N(t,e,"cloudinary-disconnect"),pollinations:(t,e)=>N(t,e,"pollinations-disconnect")}};async function so(){let t,e;try{({values:t,positionals:e}=Mr({args:process.argv.slice(2),options:zr,allowPositionals:!0,strict:!0}))}catch(a){process.stderr.write(`Error: Invalid flag. ${a.message}
113
+ `),process.exitCode=m.USAGE;return}if(t.help||e.length===0){process.stdout.write(Gr);return}if(e[0]==="register"){await Wr(t);return}e=Ze(e);let[r,o]=e,i=Xt[r];if(!i){process.stderr.write(`Error: Unknown command category "${r}".
114
+ `),process.stderr.write(`Available categories: ${Object.keys(Xt).join(", ")}
115
+ `),process.exitCode=m.USAGE;return}let n=i[o];if(!n){process.stderr.write(`Error: Unknown ${r} command "${o}".
105
116
  `),process.stderr.write(`Available commands: ${Object.keys(i).map(a=>`${r} ${a}`).join(", ")}
106
- `),process.exitCode=h.USAGE;return}await n(t,e.slice(2))}Fr();
117
+ `),process.exitCode=m.USAGE;return}await n(t,e.slice(2))}so();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixpo/lixblogs-cli",
3
- "version": "1.4.6",
3
+ "version": "1.5.2",
4
4
  "description": "Official CLI for LixBlogs — publish, manage, and inspect blogs through the supported API. Built for creators and agent automation.",
5
5
  "author": {
6
6
  "name": "Elixpo",
@@ -36,7 +36,9 @@ lixblogs blog edit BLOG_ID --file post.md --dry-run --json --no-input
36
36
  lixblogs blog edit BLOG_ID --file post.md --json --no-input
37
37
  ```
38
38
 
39
- Metadata-only revisions use `--title`, `--subtitle`, `--slug`, repeatable `--tag`, `--emoji`, `--cover`, `--publication`, and `--collection`. Content inputs `--file`, `--stdin`, `--content`, and `--editor` are mutually exclusive.
39
+ Metadata-only revisions use `--title`, `--subtitle`, `--slug`, repeatable `--tag`, `--emoji`, `--cover`, `--cover-x`, `--cover-y`, `--cover-zoom`, `--publication`, `--collection`, `--member-only` / `--no-member-only`, `--allow-comments` / `--no-comments`, and `--secret` / `--not-secret`. Content inputs `--file`, `--stdin`, `--content`, and `--editor` are mutually exclusive.
40
+
41
+ Use `lixblogs blog history BLOG_ID` to inspect snapshots and `lixblogs blog restore-version BLOG_ID --version VERSION_ID --yes` only with explicit approval. Use the separate `lixblogs-media` skill for uploads or billable Pollinations generation.
40
42
 
41
43
  ## Recovery
42
44
 
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: lixblogs-media
3
+ description: Generate, upload, and attach LixBlogs images through the supported CLI. Use when an agent needs an inline image or cover stored in the creator's selected Cloudinary space, optionally generated through their connected Pollinations BYOP account.
4
+ ---
5
+
6
+ # LixBlogs media
7
+
8
+ Use `@elixpo/lixblogs-cli` 1.5.0 or newer with `--json --no-input`. Never read integration keys, call Pollinations or Cloudinary directly, persist provider URLs, or use session cookies.
9
+
10
+ ## Access and cost
11
+
12
+ - Inspect connection: `lixblogs:media:read`
13
+ - Generate or upload: `lixblogs:media:write`; attaching also needs `lixblogs:blog:read` and `lixblogs:blog:write`
14
+ - Pollinations generation spends the creator's approved Pollen budget. Generate only when the current user request explicitly authorizes that image. One command is one billable attempt; never automatically retry a failed generation.
15
+ - A disconnected, expired, or revoked connection must be repaired by the user at `https://blogs.elixpo.com/settings?tab=integrations`.
16
+
17
+ Check the connection before offering generation:
18
+
19
+ ```bash
20
+ lixblogs integrations pollinations-status --json --no-input
21
+ ```
22
+
23
+ ## Generate and attach
24
+
25
+ Keep `--output`: it retains a local recovery copy if Cloudinary persistence fails, so retry `media upload` instead of paying for another generation.
26
+
27
+ ```bash
28
+ lixblogs media generate --prompt "Editorial illustration of…" --model flux --output image.jpg --json --no-input
29
+ lixblogs media generate --prompt "Wide cover…" --blog BLOG_ID --type cover --attach --output cover.jpg --json --no-input
30
+ lixblogs media generate --prompt "Diagram…" --blog BLOG_ID --type inline --caption "System flow" --attach --output diagram.jpg --json --no-input
31
+ lixblogs media generate --prompt "Restyle this reference…" --reference source.webp --blog BLOG_ID --type inline --attach --output result.jpg --json --no-input
32
+ ```
33
+
34
+ Use a new generation only for a genuinely new explicit request. Duplicate submissions are rejected to prevent double spending. Do not retry `401`, `402`, `403`, or `429` responses automatically.
35
+
36
+ ## Upload existing media
37
+
38
+ ```bash
39
+ lixblogs media upload --file image.webp --blog BLOG_ID --type inline --caption "Alt context" --attach --json --no-input
40
+ lixblogs media upload --file cover.webp --blog BLOG_ID --type cover --attach --json --no-input
41
+ ```
42
+
43
+ Uploads pass through LixBlogs metadata stripping, storage quotas, ownership checks, idempotent tracking, and the creator's selected global or personal Cloudinary space. Use a stable `--upload-id` when retrying the same local file.
44
+
45
+ After attachment, fetch the blog once and confirm the returned media URL appears in the intended cover or content block. Use `lixblogs-author` for placement or prose changes and `lixblogs-publish` for public-state changes.
46
+
47
+ Delete an owned tracked asset only with explicit approval:
48
+
49
+ ```bash
50
+ lixblogs media delete MEDIA_ID --yes --json --no-input
51
+ ```
52
+
53
+ Deletion removes the provider asset and its LixBlogs tracking record. If personal Cloudinary authorization has expired, ask the user to reconnect that storage space; never remove only the database record.
@@ -35,6 +35,7 @@ lixblogs blog preview BLOG_ID --json --no-input
35
35
  ```bash
36
36
  lixblogs blog publish BLOG_ID --etag ETAG --idempotency-key KEY --dry-run --json --no-input
37
37
  lixblogs blog publish BLOG_ID --etag ETAG --idempotency-key KEY --yes --json --no-input
38
+ lixblogs blog publish BLOG_ID --status unlisted --etag ETAG --idempotency-key KEY --yes --json --no-input
38
39
  lixblogs blog unpublish BLOG_ID --etag ETAG --yes --json --no-input
39
40
  lixblogs blog trash BLOG_ID --etag ETAG --yes --json --no-input
40
41
  lixblogs blog restore BLOG_ID --etag ETAG --yes --json --no-input