maeve-cli 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +765 -640
  2. package/dist/index.js +4105 -1564
  3. package/package.json +12 -12
package/README.md CHANGED
@@ -1,640 +1,765 @@
1
- # Maeve CLI
2
-
3
- A command-line tool for the Maeve public API. Schedule posts, manage media, run analytics, and send client reviews from a terminal or a script.
4
-
5
- [![npm](https://img.shields.io/npm/v/maeve-cli.svg)](https://www.npmjs.com/package/maeve-cli)
6
- [![node](https://img.shields.io/node/v/maeve-cli.svg)](https://nodejs.org)
7
- [![license](https://img.shields.io/npm/l/maeve-cli.svg)](./LICENSE)
8
-
9
- ## Install
10
-
11
- Run without installing:
12
-
13
- ```bash
14
- npx maeve-cli auth:status
15
- pnpm dlx maeve-cli auth:status
16
- ```
17
-
18
- Install globally:
19
-
20
- ```bash
21
- npm install -g maeve-cli
22
- maeve auth:status
23
- ```
24
-
25
- Or with pnpm:
26
-
27
- ```bash
28
- pnpm add -g maeve-cli
29
- maeve auth:status
30
- ```
31
-
32
- Node 22 or newer is required.
33
-
34
- ## Quick start
35
-
36
- ```bash
37
- maeve auth:login
38
- maeve workspaces:list
39
- maeve integrations:list --workspace <id>
40
- ```
41
-
42
- ## Authentication
43
-
44
- There are two ways to sign in. Pick one.
45
-
46
- ### API key (for CI, servers, scripts)
47
-
48
- ```bash
49
- export MAEVE_API_KEY="ezb_live_..."
50
- export MAEVE_API_URL="https://api.maevesocial.com"
51
- ```
52
-
53
- PowerShell:
54
-
55
- ```powershell
56
- $env:MAEVE_API_KEY="ezb_live_..."
57
- $env:MAEVE_API_URL="https://api.maevesocial.com"
58
- ```
59
-
60
- Prefer the env var over `--api-key`. Flags can leak into shell history and process lists.
61
-
62
- For scoped keys stored under a named environment variable, pass the variable name instead of the secret value:
63
-
64
- ```bash
65
- maeve --api-key-env MAEVE_API_KEY_LILY_DIA_JEWELLERY_DEMO_KEY auth:status
66
- maeve --api-key-env MAEVE_API_KEY_LILY_DIA_JEWELLERY_DEMO_KEY media:labels:list --workspace <id>
67
- ```
68
-
69
- When `--api-key-env` is provided, that named variable must exist; the CLI will not silently fall back to `MAEVE_API_KEY`.
70
-
71
- ### Browser login (for humans on a laptop)
72
-
73
- ```bash
74
- maeve auth:login # opens a browser, prints a short code
75
- maeve auth:whoami # shows the signed-in user and workspaces
76
- maeve auth:logout # revokes the local token
77
- ```
78
-
79
- `auth:login` stores a revocable `ezb_cli_...` token for the API URL. Tokens expire after 7 days. They are never permanent API keys.
80
-
81
- Use `--no-browser` if you want the URL printed instead of opened.
82
-
83
- ### Credential precedence
84
-
85
- The CLI uses the first credential it finds:
86
-
87
- 1. `--api-key`
88
- 2. `--api-key-env <name>`
89
- 3. `MAEVE_API_KEY`
90
- 4. Stored CLI login token
91
-
92
- ### Where the token is stored
93
-
94
- | Platform | Path |
95
- | -------- | --------------------------------------------------- |
96
- | Windows | `%APPDATA%\Maeve\cli-auth.json` |
97
- | macOS | `~/Library/Application Support/Maeve/cli-auth.json` |
98
- | Linux | `${XDG_CONFIG_HOME:-~/.config}/maeve/cli-auth.json` |
99
-
100
- The file holds one credential per API URL. Token values are never printed by `auth:status` or any other command.
101
-
102
- ## Output
103
-
104
- Successful commands write JSON to stdout. Errors write structured JSON to stderr and exit non-zero. Pipe to `jq`:
105
-
106
- ```bash
107
- maeve workspaces:list | jq -r '.data[].id'
108
- maeve content:list --workspace "$WORKSPACE_ID" --status scheduled | jq '.data | length'
109
- ```
110
-
111
- ## Commands
112
-
113
- ### Auth
114
-
115
- ```bash
116
- maeve auth:status
117
- maeve auth:login
118
- maeve auth:logout
119
- maeve auth:whoami
120
- ```
121
-
122
- ### Workspaces
123
-
124
- ```bash
125
- maeve workspaces:list
126
- ```
127
-
128
- ### Integrations
129
-
130
- `integrations:capabilities` returns safe publishing metadata for a connected account: post types, media types, setting field names, dynamic option keys, and capability flags. It does not expose tokens, scopes, or raw provider payloads. Use `integrations:options` with a key from that response to fetch read-only provider data such as Pinterest boards or YouTube categories.
131
-
132
- ```bash
133
- maeve integrations:list --workspace <id>
134
- maeve integrations:capabilities --workspace <id> --integration <id>
135
- maeve integrations:options --workspace <id> --integration <id> --key pinterest-boards
136
- maeve integrations:options --workspace <id> --integration <id> --key youtube-video-categories --json options.json
137
- ```
138
-
139
- Example option body:
140
-
141
- ```json
142
- { "regionCode": "AU" }
143
- ```
144
-
145
- ### Media
146
-
147
- ```bash
148
- maeve media:list --workspace <id>
149
- maeve media:list --workspace <id> --type image --label-ids <id,id> --favorite
150
- maeve media:list --workspace <id> --state deleted
151
- maeve media:upload ./image.png --workspace <id>
152
- maeve media:get --workspace <id> --id <mediaId>
153
- maeve media:usage-history --workspace <id> --id <mediaId>
154
- maeve media:update --workspace <id> --id <mediaId> --json media-update.json
155
- maeve media:download-url --workspace <id> --id <mediaId>
156
- maeve media:view-url --workspace <id> --id <mediaId>
157
- maeve media:archive --workspace <id> --id <mediaId>
158
- maeve media:delete --workspace <id> --id <mediaId>
159
- maeve media:restore --workspace <id> --id <mediaId>
160
- maeve media:delete-permanent --workspace <id> --id <mediaId> --yes
161
- ```
162
-
163
- `media:delete` and `media:bulk-delete` move active media to the Bin. The API returns `deleteAfter`; media attached to scheduled posts is blocked with `MEDIA_ATTACHED_TO_SCHEDULED_POST`. Use `media:restore` to bring an item back from the Bin, or `media:delete-permanent` to remove it for good (requires `--yes`).
164
-
165
- Folders and labels:
166
-
167
- ```bash
168
- maeve media:folders:list --workspace <id> --parent-id root
169
- maeve media:folders:get --workspace <id> --id <folderId>
170
- maeve media:folders:path --workspace <id> --id <folderId>
171
- maeve media:folders:create --workspace <id> --json media-folder.json
172
- maeve media:folders:update --workspace <id> --id <folderId> --json media-folder.json
173
- maeve media:folders:move --workspace <id> --id <folderId> --json media-folder-move.json
174
- maeve media:folders:delete --workspace <id> --id <folderId>
175
- maeve media:labels:list --workspace <id> --search approved
176
- maeve media:labels:create --workspace <id> --json media-label.json
177
- maeve media:labels:update --workspace <id> --id <labelId> --json media-label.json
178
- maeve media:labels:delete --workspace <id> --id <labelId>
179
- maeve media:labels:attach --workspace <id> --id <mediaId> --json media-label-ids.json
180
- maeve media:labels:detach --workspace <id> --id <mediaId> --json media-label-ids.json
181
- maeve media:labels:usage --workspace <id> --label-ids <id,id>
182
- maeve media:labels:health --workspace <id>
183
- maeve media:labels:archive --workspace <id> --id <labelId>
184
- maeve media:labels:restore --workspace <id> --id <labelId>
185
- maeve media:labels:merge --workspace <id> --id <sourceLabelId> --json media-label-merge.json
186
- ```
187
-
188
- `media:labels:*` is the canonical Media Room organization command family. If a global install still shows `media:tags:*`, upgrade `maeve-cli`. `media:labels:merge` folds the source label into a target; its payload is `{ "targetLabelId": "<id>" }`.
189
-
190
- Label groups:
191
-
192
- ```bash
193
- maeve media:label-groups:list --workspace <id>
194
- maeve media:label-groups:create --workspace <id> --json label-group.json
195
- maeve media:label-groups:update --workspace <id> --id <groupId> --json label-group.json
196
- maeve media:label-groups:reorder --workspace <id> --json label-group-order.json
197
- maeve media:label-groups:add-labels --workspace <id> --id <groupId> --json label-ids.json
198
- maeve media:label-groups:ungroup-labels --workspace <id> --json label-ids.json
199
- maeve media:label-groups:delete --workspace <id> --id <groupId> --json label-group-delete.json
200
- ```
201
-
202
- `media:label-groups:reorder` takes the full `{ "groupIds": [...] }` order. `add-labels` and `ungroup-labels` take `{ "labelIds": [...] }`. `delete` takes `{ "mode": "archive-labels" }` or `{ "mode": "ungroup-labels" }` to say what happens to the labels in the group.
203
-
204
- Bulk operations:
205
-
206
- ```bash
207
- maeve media:bulk-archive --workspace <id> --json media-ids.json
208
- maeve media:bulk-move --workspace <id> --json media-bulk-move.json
209
- maeve media:bulk-label --workspace <id> --json media-bulk-labels.json
210
- maeve media:bulk-unlabel --workspace <id> --json media-bulk-labels.json
211
- maeve media:bulk-delete --workspace <id> --json media-ids.json --yes
212
- maeve media:bulk-restore --workspace <id> --json media-ids.json
213
- maeve media:bulk-delete-forever --workspace <id> --json media-ids.json --yes
214
- ```
215
-
216
- ### Content
217
-
218
- ```bash
219
- maeve content:create --workspace <id> --json examples/create-content.json
220
- maeve content:update --workspace <id> --id <contentId> --json update-content.json
221
- maeve content:list --workspace <id> --status scheduled
222
- maeve content:list --workspace <id> --workflow-statuses approved,needs_changes
223
- maeve content:get --workspace <id> --id <contentId>
224
- maeve content:schedule --workspace <id> --id <contentId> --scheduled-at "2026-05-01T10:00:00+10:00"
225
- maeve content:intended-time --workspace <id> --id <contentId> --scheduled-at "2026-05-01T10:00:00+10:00"
226
- maeve content:notes --workspace <id> --id <contentId> --notes "<p>Planning notes</p>"
227
- maeve content:notes --workspace <id> --id <contentId> --json notes.json
228
- maeve content:publish --workspace <id> --id <contentId> --yes
229
- maeve content:published-caption --workspace <id> --id <contentId> --json published-caption.json --yes
230
- maeve content:archive --workspace <id> --id <contentId>
231
- maeve content:restore --workspace <id> --id <contentId>
232
- maeve content:retry --workspace <id> --id <contentId>
233
- maeve content:delete --workspace <id> --id <contentId>
234
- maeve content:recurring-occurrence:cancel --workspace <id> --occurrence <occurrenceId> --yes
235
- maeve content:recurring-series:cancel --workspace <id> --series <seriesId> --effective-occurrence <occurrenceId> [--expected-version <number>] --yes
236
- maeve content:failed-count --workspace <id>
237
- maeve content:pending-approval-count --workspace <id>
238
- ```
239
-
240
- `content:delete` refuses content that belongs to a recurring series; use the recurring cancel commands instead. `content:list` includes a `recurringMetadata` object on recurring-linked items with the `seriesId`, `occurrenceId`, and `seriesVersion` those commands need. `content:recurring-occurrence:cancel` cancels a single occurrence. `content:recurring-series:cancel` cancels the series and every not-yet-published occurrence from the effective occurrence forward. Both require `--yes` because they remove scheduled publishing work.
241
-
242
- Approvals, comments, and activity:
243
-
244
- ```bash
245
- maeve content:approval-history --workspace <id>
246
- maeve content:approval-history:client-batch --workspace <id> --batch <batchId>
247
- maeve content:approval-history:internal --workspace <id> --record <recordId>
248
- maeve content:comment --workspace <id> --id <contentId> --json comment.json
249
- maeve content:comment-attachments:init --workspace <id> --json attachment.json
250
- maeve content:comment-attachments:complete --workspace <id> --attachment <attachmentId>
251
- maeve content:comment-attachments:abort --workspace <id> --attachment <attachmentId>
252
- maeve content:activity:react --workspace <id> --activity <activityId> --json reaction.json
253
- maeve content:activity:unreact --workspace <id> --activity <activityId> --emoji thumbs-up
254
- maeve content:request-approval --workspace <id> --id <contentId> --json approval.json --yes
255
- maeve content:decision --workspace <id> --id <contentId> --json decision.json
256
- maeve content:withdraw --workspace <id> --id <contentId>
257
- maeve content:resubmit --workspace <id> --id <contentId> --json approval.json --yes
258
- maeve content:history --workspace <id> --id <contentId>
259
- maeve content:reopen-client-review --workspace <id> --id <contentId>
260
- ```
261
-
262
- ### Client reviews
263
-
264
- ```bash
265
- maeve client-reviews:create --workspace <id> --json client-review.json
266
- maeve client-reviews:open --workspace <id>
267
- maeve client-reviews:get --workspace <id> --batch <batchId>
268
- maeve client-reviews:add-post --workspace <id> --batch <batchId> --json post-id.json
269
- maeve client-reviews:remove-post --workspace <id> --batch <batchId> --json post-id.json
270
- maeve client-reviews:send --workspace <id> --batch <batchId> --yes
271
- maeve client-reviews:resend --workspace <id> --batch <batchId> --yes
272
- maeve client-reviews:cancel --workspace <id> --batch <batchId>
273
- maeve client-reviews:override --workspace <id> --batch <batchId> --json override.json
274
- maeve client-reviews:update-participant --workspace <id> --batch <batchId> --json participant.json
275
- maeve client-reviews:comment --workspace <id> --batch <batchId> --json comment.json
276
- ```
277
-
278
- ### Campaigns
279
-
280
- ```bash
281
- maeve campaigns:list --workspace <id> --include-phases
282
- maeve campaigns:get --workspace <id> --id <campaignId>
283
- maeve campaigns:content --workspace <id> --id <campaignId> --status scheduled
284
- maeve campaigns:create --workspace <id> --json campaign.json
285
- maeve campaigns:update --workspace <id> --id <campaignId> --json campaign.json
286
- maeve campaigns:archive --workspace <id> --id <campaignId>
287
- maeve campaigns:restore --workspace <id> --id <campaignId>
288
- maeve campaigns:phases:list --workspace <id> --id <campaignId>
289
- maeve campaigns:phases:create --workspace <id> --id <campaignId> --json campaign-phase.json
290
- maeve campaigns:phases:replace --workspace <id> --id <campaignId> --json campaign-phases.json
291
- maeve campaigns:phases:update --workspace <id> --id <campaignId> --phase-id <phaseId> --json campaign-phase.json
292
- maeve campaigns:phases:delete --workspace <id> --id <campaignId> --phase-id <phaseId> --yes
293
- ```
294
-
295
- Use `campaigns:list` and `campaigns:get` to find the campaign and phase IDs that
296
- `content:create` and `content:update` accept in `campaignId` / `campaignPhaseId`. Create
297
- payload: `name`, `startDate`, and `endDate` are required (dates are ISO 8601); `color`,
298
- `goal`, `description`, `targetPlatforms`, `estimatedBudget`, `actualBudget`, and
299
- `budgetCurrency` are optional. `campaigns:phases:replace` takes the full `{ "phases": [...] }`
300
- array.
301
-
302
- ```json
303
- {
304
- "name": "May launch",
305
- "startDate": "2026-05-01",
306
- "endDate": "2026-05-31",
307
- "color": "#2563EB",
308
- "targetPlatforms": ["instagram", "facebook"]
309
- }
310
- ```
311
-
312
- ### Strategy
313
-
314
- Read-only. The CLI exposes the strategy reads the public API serves; strategy is edited in
315
- the app.
316
-
317
- ```bash
318
- maeve strategy:foundation --workspace <id>
319
- maeve strategy:goals --workspace <id>
320
- maeve strategy:goals:metrics --workspace <id>
321
- maeve strategy:goal --workspace <id> --id <goalId>
322
- ```
323
-
324
- ### Taxonomy and hashtags
325
-
326
- ```bash
327
- maeve taxonomy:labels --workspace <id>
328
- maeve taxonomy:pillars --workspace <id>
329
- maeve taxonomy:formats --workspace <id>
330
- maeve hashtags:list --workspace <id>
331
- maeve hashtags:create --workspace <id> --json hashtags.json
332
- maeve hashtags:update --workspace <id> --id <hashtagGroupId> --json hashtags.json
333
- maeve hashtags:delete --workspace <id> --id <hashtagGroupId>
334
- ```
335
-
336
- ### Analytics
337
-
338
- ```bash
339
- maeve analytics:summary --workspace <id> --days 30
340
- maeve analytics:summary --workspace <id> --integration <id> --days all
341
- maeve analytics:summary --workspace <id> --integration-ids <id,id>
342
- maeve analytics:health --workspace <id> --integration <id>
343
- maeve analytics:posts --workspace <id> --integration <id> --limit 12 --offset 0
344
- maeve analytics:posts-aggregate --workspace <id> --integration-ids <id,id> --sort-by views --days 30
345
- maeve analytics:post --workspace <id> --id <contentId>
346
- maeve analytics:demographics --workspace <id> --integration <id>
347
- maeve analytics:report --workspace <id> --provider instagram --json analytics-report.json --output report.pdf --yes
348
- ```
349
-
350
- `analytics:summary` works on one integration or aggregates across many. Use `--integration-ids` to pick a set, or omit it for all accessible integrations. `--days` accepts `1` to `90` or `all`.
351
-
352
- `analytics:posts` is single-integration. `analytics:posts-aggregate` accepts `--integration-ids` and `--sort-by recent|engagement|views`.
353
-
354
- `analytics:report` is standard-plan only. It saves the PDF to `--output`. Stdout stays JSON metadata.
355
-
356
- ### Inbox
357
-
358
- ```bash
359
- maeve inbox:threads --workspace <id> --read unread
360
- maeve inbox:messages --workspace <id> --thread <threadId>
361
- maeve inbox:stats --workspace <id>
362
- maeve inbox:read --workspace <id> --thread <threadId>
363
- maeve inbox:unread --workspace <id> --thread <threadId>
364
- maeve inbox:read-all --workspace <id> --json inbox-read-all.json --yes
365
- maeve inbox:resolve --workspace <id> --thread <threadId>
366
- maeve inbox:reopen --workspace <id> --thread <threadId>
367
- maeve inbox:resolve-all --workspace <id> --json inbox-resolve-all.json --yes
368
- maeve inbox:resolve-message --workspace <id> --message <messageId>
369
- maeve inbox:reopen-message --workspace <id> --message <messageId>
370
- maeve inbox:reply --workspace <id> --thread <threadId> --json inbox-reply.json --yes
371
- maeve inbox:note --workspace <id> --thread <threadId> --json inbox-note.json
372
- maeve inbox:moderate --workspace <id> --message <messageId> --json inbox-moderate.json --yes
373
- maeve inbox:retry-message --workspace <id> --message <messageId> --yes
374
- maeve inbox:delete-failed --workspace <id> --message <messageId> --yes
375
- ```
376
-
377
- `inbox:resolve` and `inbox:resolve-message` accept an optional `--json` file with a resolution reason and note. `inbox:resolve-all` resolves every thread matching the filter payload and requires `--yes`. The reopen commands take no body.
378
-
379
- ### Grid planner
380
-
381
- ```bash
382
- maeve grid:list --workspace <id> --integration <integrationId>
383
- maeve grid:create --workspace <id> --json grid-item.json
384
- maeve grid:update --workspace <id> --item <itemId> --json grid-update.json
385
- maeve grid:delete --workspace <id> --item <itemId> --yes
386
- maeve grid:reorder --workspace <id> --json grid-reorder.json
387
- maeve grid:replace-media --workspace <id> --item <itemId> --json grid-media.json
388
- maeve grid:set-cover --workspace <id> --item <itemId> --json grid-cover.json
389
- maeve grid:remove-cover --workspace <id> --item <itemId>
390
- maeve grid:promote --workspace <id> --item <itemId> --json grid-promote.json --yes
391
- ```
392
-
393
- ### Boosts
394
-
395
- Read-only, standard plan. The CLI reads boost state for reporting but does not create, pause,
396
- resume, delete, or estimate boosts, because those spend Meta ad budget. Run those from the
397
- app.
398
-
399
- ```bash
400
- maeve boosts:list --workspace <id> --statuses active,paused
401
- maeve boosts:get --workspace <id> --id <boostId>
402
- maeve boosts:performance --workspace <id> --id <boostId>
403
- maeve boosts:ad-accounts --workspace <id> --integration <integrationId>
404
- maeve boosts:boosted-content-ids --workspace <id>
405
- maeve boosts:by-content --workspace <id> --content <contentId>
406
- ```
407
-
408
- ## Payloads
409
-
410
- ### Content
411
-
412
- `content:create` reads a JSON file and sends an `Idempotency-Key` header for safe retries. Pass `--idempotency-key <key>` to reuse a known key, or omit it to let the CLI generate one. Omitted `intent` defaults to `draft`; use `schedule` with `scheduledAt`, or `publish_now` plus CLI `--yes` for explicit immediate publishing. `content:update` uses the same shape except `intent`, but every accepted field is optional and at least one must be present.
413
-
414
- ```json
415
- {
416
- "integrationId": "00000000-0000-4000-8000-000000000001",
417
- "intent": "draft",
418
- "internalTitle": "Launch planning card",
419
- "captions": {
420
- "canonical": "Launch post copy"
421
- },
422
- "contentMedia": [
423
- {
424
- "mediaId": "00000000-0000-4000-8000-000000000007",
425
- "order": 0,
426
- "cover": {
427
- "thumbOffsetMs": 2500
428
- }
429
- }
430
- ],
431
- "pillarIds": ["00000000-0000-4000-8000-000000000002"],
432
- "formatIds": ["00000000-0000-4000-8000-000000000003"],
433
- "labelIds": ["00000000-0000-4000-8000-000000000004"],
434
- "campaignId": "00000000-0000-4000-8000-000000000005",
435
- "campaignPhaseId": "00000000-0000-4000-8000-000000000006"
436
- }
437
- ```
438
-
439
- Common fields:
440
-
441
- - `integrationId`: integration to post to.
442
- - `intent`: `draft`, `schedule`, or `publish_now`. Omitted intent defaults to `draft`.
443
- - `internalTitle`: internal planning title. Never published.
444
- - `publishTitle`: provider-facing title for platforms that support or require one.
445
- - `captions`: canonical publish text and optional platform overrides.
446
- - `notes`: internal rich-text notes. Never published. Max 100000 characters.
447
- - `contentMedia`: array of uploaded media relationships, with optional per-media crops, tags, and cover metadata.
448
- - `scheduledAt`: ISO 8601 with timezone, e.g. `2026-05-01T10:00:00+10:00`. Required only with `intent: "schedule"`.
449
- - `postType`: `post`, `reel`, `story`, or `thread`.
450
- - `settings`: provider settings from integration capabilities only. Media metadata belongs in `contentMedia`.
451
- - `firstComment`, `shareToFeed`: optional publish behavior fields.
452
- - `pillarIds`, `formatIds`, `labelIds`: arrays of workspace taxonomy UUIDs.
453
- - `campaignId`, `campaignPhaseId`: optional root-post campaign links. Use `null` to clear when updating.
454
- - `priority`: optional root-post priority, `urgent`, `high`, `medium`, or `low`.
455
- - `threadMessages`: items for thread-style content using `captions` and optional `contentMedia`.
456
-
457
- `content:published-caption` edits the provider caption for an already-published Facebook item and requires `--yes`.
458
-
459
- ```json
460
- {
461
- "message": "Updated Facebook caption"
462
- }
463
- ```
464
-
465
- ### Approvals and client reviews
466
-
467
- Approval and client review commands need a standard workspace plan and the role shown in each command's `--help`. Payloads mirror the public API DTOs.
468
-
469
- Internal approval request:
470
-
471
- ```json
472
- {
473
- "approverIds": ["00000000-0000-4000-8000-000000000001"],
474
- "policy": "any"
475
- }
476
- ```
477
-
478
- Client review batch:
479
-
480
- ```json
481
- {
482
- "contentIds": ["00000000-0000-4000-8000-000000000001"],
483
- "workflowMode": "client",
484
- "client": {
485
- "reviewers": [{ "name": "Client Reviewer", "email": "reviewer@example.com" }],
486
- "policy": "all",
487
- "inviteNote": "Please review when you have a moment.",
488
- "batchLabel": "May launch"
489
- }
490
- }
491
- ```
492
-
493
- Use `workflowMode: "internal_client"` with an `internal` object containing `approverIds` and `policy` when internal approval is required before client review.
494
-
495
- `content:create` requires `--yes` when the payload has `intent: "publish_now"`, `content:publish` requires `--yes` because it queues external publishing, and `content:published-caption` requires `--yes` because it edits already-published provider content. `content:request-approval`, `content:resubmit`, `client-reviews:send`, and `client-reviews:resend` all require `--yes` because they may notify people.
496
-
497
- ### Inbox
498
-
499
- Inbox commands take explicit thread or message IDs. Public replies, moderation, retries, deletes, and `read-all` require `--yes`.
500
-
501
- Reply:
502
-
503
- ```json
504
- {
505
- "content": "Thanks for reaching out.",
506
- "parentMessageId": "00000000-0000-4000-8000-000000000001"
507
- }
508
- ```
509
-
510
- `content` is optional when an `attachment` is provided:
511
-
512
- ```json
513
- {
514
- "attachment": {
515
- "type": "image",
516
- "url": "https://cdn.example.com/reply.png"
517
- }
518
- }
519
- ```
520
-
521
- Internal note:
522
-
523
- ```json
524
- { "content": "Follow up with the team before replying." }
525
- ```
526
-
527
- Moderation. Allowed actions: `hide`, `unhide`, `delete`.
528
-
529
- ```json
530
- { "action": "hide" }
531
- ```
532
-
533
- Read-all takes the same filter shape as listing threads. Use `{}` only when you want all matching threads marked read.
534
-
535
- ```json
536
- {
537
- "platform": "instagram",
538
- "status": "open",
539
- "integrationId": "00000000-0000-4000-8000-000000000001",
540
- "messageType": "comment"
541
- }
542
- ```
543
-
544
- ### Grid planner
545
-
546
- Visual-only item:
547
-
548
- ```json
549
- {
550
- "integrationId": "00000000-0000-4000-8000-000000000001",
551
- "kind": "visual_only",
552
- "mediaIds": ["00000000-0000-4000-8000-000000000002"],
553
- "note": "Plan this visual",
554
- "settings": { "aspectRatio": 1 }
555
- }
556
- ```
557
-
558
- Linked content item:
559
-
560
- ```json
561
- {
562
- "integrationId": "00000000-0000-4000-8000-000000000001",
563
- "kind": "linked_post",
564
- "linkedContentId": "00000000-0000-4000-8000-000000000003"
565
- }
566
- ```
567
-
568
- Reorder needs the full item ID order for the integration:
569
-
570
- ```json
571
- {
572
- "integrationId": "00000000-0000-4000-8000-000000000001",
573
- "itemIds": ["00000000-0000-4000-8000-000000000004"]
574
- }
575
- ```
576
-
577
- Media, cover, and promote payloads:
578
-
579
- ```json
580
- { "integrationId": "00000000-0000-4000-8000-000000000001", "mediaIds": ["00000000-0000-4000-8000-000000000002"] }
581
- ```
582
-
583
- ```json
584
- { "integrationId": "00000000-0000-4000-8000-000000000001", "mediaId": "00000000-0000-4000-8000-000000000005" }
585
- ```
586
-
587
- ```json
588
- { "integrationId": "00000000-0000-4000-8000-000000000001" }
589
- ```
590
-
591
- `grid:delete` and `grid:promote` require `--yes`.
592
-
593
- ### Media
594
-
595
- Supported upload extensions:
596
-
597
- - `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.avif`
598
- - `.mp4`, `.mov`
599
-
600
- Images cap at 50 MB, videos at 512 MB.
601
-
602
- Update payload:
603
-
604
- ```json
605
- {
606
- "filename": "campaign-hero.png",
607
- "altText": "Campaign hero image",
608
- "isFavorite": true,
609
- "folderId": null
610
- }
611
- ```
612
-
613
- Folder and label:
614
-
615
- ```json
616
- { "name": "Launch assets" }
617
- ```
618
-
619
- ```json
620
- { "name": "Approved", "color": "#22cc88" }
621
- ```
622
-
623
- Bulk payloads are explicit ID lists:
624
-
625
- ```json
626
- { "mediaIds": ["00000000-0000-4000-8000-000000000001"] }
627
- ```
628
-
629
- ```json
630
- {
631
- "mediaIds": ["00000000-0000-4000-8000-000000000001"],
632
- "labelIds": ["00000000-0000-4000-8000-000000000002"]
633
- }
634
- ```
635
-
636
- `media:bulk-delete` moves active media to the Bin and requires `--yes`. Use `media:list --state deleted` to inspect Bin items.
637
-
638
- ## License
639
-
640
- MIT (c) 2026 LILY DIA PTY LTD.
1
+ # Maeve CLI
2
+
3
+ A command-line tool for the Maeve public API. Schedule posts, manage media, run analytics, and send client reviews from a terminal or a script.
4
+
5
+ The current package exposes 181 commands across 15 command groups. It is a `/v1` REST client
6
+ and never calls MCP. Inbox automation rules and the 33-operation Workbench v2 API are not CLI
7
+ surfaces. See [`SCOPE.md`](./SCOPE.md) for the complete inclusion and exemption rules.
8
+
9
+ [![npm](https://img.shields.io/npm/v/maeve-cli.svg)](https://www.npmjs.com/package/maeve-cli)
10
+ [![node](https://img.shields.io/node/v/maeve-cli.svg)](https://nodejs.org)
11
+ [![license](https://img.shields.io/npm/l/maeve-cli.svg)](./LICENSE)
12
+
13
+ ## Install
14
+
15
+ Run without installing:
16
+
17
+ ```bash
18
+ npx maeve-cli auth:status
19
+ pnpm dlx maeve-cli auth:status
20
+ ```
21
+
22
+ Install globally:
23
+
24
+ ```bash
25
+ npm install -g maeve-cli
26
+ maeve auth:status
27
+ ```
28
+
29
+ Or with pnpm:
30
+
31
+ ```bash
32
+ pnpm add -g maeve-cli
33
+ maeve auth:status
34
+ ```
35
+
36
+ Node 22 or newer is required.
37
+
38
+ ## Quick start
39
+
40
+ ```bash
41
+ maeve auth:login
42
+ maeve workspaces:list
43
+ maeve integrations:list --workspace <id>
44
+ ```
45
+
46
+ ## Authentication
47
+
48
+ There are two ways to sign in. Pick one.
49
+
50
+ ### API key (for CI, servers, scripts)
51
+
52
+ ```bash
53
+ export MAEVE_API_KEY="ezb_live_..."
54
+ export MAEVE_API_URL="https://api.maevesocial.com"
55
+ ```
56
+
57
+ PowerShell:
58
+
59
+ ```powershell
60
+ $env:MAEVE_API_KEY="ezb_live_..."
61
+ $env:MAEVE_API_URL="https://api.maevesocial.com"
62
+ ```
63
+
64
+ Prefer the env var over `--api-key`. Flags can leak into shell history and process lists.
65
+
66
+ For scoped keys stored under a named environment variable, pass the variable name instead of the secret value:
67
+
68
+ ```bash
69
+ maeve --api-key-env MAEVE_API_KEY_LILY_DIA_JEWELLERY_DEMO_KEY auth:status
70
+ maeve --api-key-env MAEVE_API_KEY_LILY_DIA_JEWELLERY_DEMO_KEY media:labels:list --workspace <id>
71
+ ```
72
+
73
+ When `--api-key-env` is provided, that named variable must exist; the CLI will not silently fall back to `MAEVE_API_KEY`.
74
+
75
+ ### Browser login (for humans on a laptop)
76
+
77
+ ```bash
78
+ maeve auth:login # opens a browser, prints a short code
79
+ maeve auth:whoami # shows the signed-in user and workspaces
80
+ maeve auth:logout # revokes the local token
81
+ ```
82
+
83
+ `auth:login` stores a revocable `ezb_cli_...` token for the API URL. Tokens expire after 7 days. They are never permanent API keys.
84
+
85
+ Use `--no-browser` if you want the URL printed instead of opened.
86
+
87
+ ### Credential precedence
88
+
89
+ The CLI uses the first credential it finds:
90
+
91
+ 1. `--api-key`
92
+ 2. `--api-key-env <name>`
93
+ 3. `MAEVE_API_KEY`
94
+ 4. Stored CLI login token
95
+
96
+ ### Where the token is stored
97
+
98
+ | Platform | Path |
99
+ | -------- | --------------------------------------------------- |
100
+ | Windows | `%APPDATA%\Maeve\cli-auth.json` |
101
+ | macOS | `~/Library/Application Support/Maeve/cli-auth.json` |
102
+ | Linux | `${XDG_CONFIG_HOME:-~/.config}/maeve/cli-auth.json` |
103
+
104
+ The file holds one credential per API URL. Token values are never printed by `auth:status` or any other command.
105
+
106
+ ## Output
107
+
108
+ Successful commands write JSON to stdout. Errors write structured JSON to stderr and exit non-zero. Pipe to `jq`:
109
+
110
+ ```bash
111
+ maeve workspaces:list | jq -r '.data[].id'
112
+ maeve content:list --workspace "$WORKSPACE_ID" --status scheduled | jq '.data | length'
113
+ ```
114
+
115
+ ## Commands
116
+
117
+ ### Auth
118
+
119
+ ```bash
120
+ maeve auth:status
121
+ maeve auth:login
122
+ maeve auth:logout
123
+ maeve auth:whoami
124
+ ```
125
+
126
+ ### Workspaces
127
+
128
+ ```bash
129
+ maeve workspaces:list
130
+ ```
131
+
132
+ ### Integrations
133
+
134
+ `integrations:pinterest:create-board --workspace <id> --integration <id> --json board.json --yes` creates one board on the identified Pinterest account using the web's existing operation. The JSON is `{ "name": "My board", "privacy": "SECRET" }`; privacy accepts `PUBLIC` or `SECRET` and defaults to `PUBLIC`. You need integration management permission. The result contains `data.board.id`, `name` and `privacy`. This is not idempotent. If the response is lost, inspect boards and native Pinterest before retrying. Board deletion is not exposed.
135
+
136
+ `integrations:capabilities` returns safe publishing metadata for a connected account: post types, media types, setting field names, dynamic option keys, and capability flags. It does not expose tokens, scopes, or raw provider payloads. Use `integrations:options` with a key from that response to fetch read-only provider data such as Pinterest boards or YouTube categories.
137
+
138
+ ```bash
139
+ maeve integrations:list --workspace <id>
140
+ maeve integrations:capabilities --workspace <id> --integration <id>
141
+ maeve integrations:options --workspace <id> --integration <id> --key pinterest-boards
142
+ maeve integrations:options --workspace <id> --integration <id> --key youtube-video-categories --json options.json
143
+ ```
144
+
145
+ Example option body:
146
+
147
+ ```json
148
+ { "regionCode": "AU" }
149
+ ```
150
+
151
+ For TikTok Commercial Music Library clips, use `--key tiktok-cml-tracks --json options.json` with:
152
+
153
+ ```json
154
+ { "countryCode": "AU", "genre": "ALL", "dateRange": "7DAY" }
155
+ ```
156
+
157
+ This uses the existing Business account connection. Choose a returned full-song or trending `songClipId` for `settings.tiktok_music`; keep the account binding and both volume values. The chart country is not a licensing guarantee. Selected clips support direct video publishing, not photo content or delivery to TikTok drafts.
158
+
159
+ TikTok content reads include `deliveryMode`: `direct` or `tiktok_drafts`. A `sent` item delivered to TikTok drafts still needs review and publication in the TikTok app. Its publishing-state label explains that next step; `sent` alone is not proof of a public TikTok publication.
160
+
161
+ ### Media
162
+
163
+ ```bash
164
+ maeve media:list --workspace <id>
165
+ maeve media:list --workspace <id> --type image --label-ids <id,id> --favorite
166
+ maeve media:list --workspace <id> --state deleted
167
+ maeve media:upload ./image.png --workspace <id>
168
+ maeve media:get --workspace <id> --id <mediaId>
169
+ maeve media:usage-history --workspace <id> --id <mediaId>
170
+ maeve media:update --workspace <id> --id <mediaId> --json media-update.json
171
+ maeve media:download-url --workspace <id> --id <mediaId>
172
+ maeve media:view-url --workspace <id> --id <mediaId>
173
+ maeve media:archive --workspace <id> --id <mediaId>
174
+ maeve media:delete --workspace <id> --id <mediaId>
175
+ maeve media:restore --workspace <id> --id <mediaId>
176
+ maeve media:delete-permanent --workspace <id> --id <mediaId> --yes
177
+ ```
178
+
179
+ `media:delete` and `media:bulk-delete` move active media to the Bin. The API returns `deleteAfter`; media attached to scheduled posts is blocked with `MEDIA_ATTACHED_TO_SCHEDULED_POST`. Use `media:restore` to bring an item back from the Bin, or `media:delete-permanent` to remove it for good (requires `--yes`).
180
+
181
+ Folders and labels:
182
+
183
+ ```bash
184
+ maeve media:folders:list --workspace <id> --parent-id root
185
+ maeve media:folders:get --workspace <id> --id <folderId>
186
+ maeve media:folders:path --workspace <id> --id <folderId>
187
+ maeve media:folders:create --workspace <id> --json media-folder.json
188
+ maeve media:folders:update --workspace <id> --id <folderId> --json media-folder.json
189
+ maeve media:folders:move --workspace <id> --id <folderId> --json media-folder-move.json
190
+ maeve media:folders:delete --workspace <id> --id <folderId>
191
+ maeve media:labels:list --workspace <id> --search approved
192
+ maeve media:labels:create --workspace <id> --json media-label.json
193
+ maeve media:labels:update --workspace <id> --id <labelId> --json media-label.json
194
+ maeve media:labels:delete --workspace <id> --id <labelId>
195
+ maeve media:labels:attach --workspace <id> --id <mediaId> --json media-label-ids.json
196
+ maeve media:labels:detach --workspace <id> --id <mediaId> --json media-label-ids.json
197
+ maeve media:labels:usage --workspace <id> --label-ids <id,id>
198
+ maeve media:labels:health --workspace <id>
199
+ maeve media:labels:archive --workspace <id> --id <labelId>
200
+ maeve media:labels:restore --workspace <id> --id <labelId>
201
+ maeve media:labels:merge --workspace <id> --id <sourceLabelId> --json media-label-merge.json
202
+ ```
203
+
204
+ `media:labels:*` is the canonical Media Room organization command family. If a global install still shows `media:tags:*`, upgrade `maeve-cli`. `media:labels:merge` folds the source label into a target; its payload is `{ "targetLabelId": "<id>" }`.
205
+
206
+ Label groups:
207
+
208
+ ```bash
209
+ maeve media:label-groups:list --workspace <id>
210
+ maeve media:label-groups:create --workspace <id> --json label-group.json
211
+ maeve media:label-groups:update --workspace <id> --id <groupId> --json label-group.json
212
+ maeve media:label-groups:reorder --workspace <id> --json label-group-order.json
213
+ maeve media:label-groups:add-labels --workspace <id> --id <groupId> --json label-ids.json
214
+ maeve media:label-groups:ungroup-labels --workspace <id> --json label-ids.json
215
+ maeve media:label-groups:delete --workspace <id> --id <groupId> --json label-group-delete.json
216
+ ```
217
+
218
+ `media:label-groups:reorder` takes the full `{ "groupIds": [...] }` order. `add-labels` and `ungroup-labels` take `{ "labelIds": [...] }`. `delete` takes `{ "mode": "archive-labels" }` or `{ "mode": "ungroup-labels" }` to say what happens to the labels in the group.
219
+
220
+ Bulk operations:
221
+
222
+ ```bash
223
+ maeve media:bulk-archive --workspace <id> --json media-ids.json
224
+ maeve media:bulk-move --workspace <id> --json media-bulk-move.json
225
+ maeve media:bulk-label --workspace <id> --json media-bulk-labels.json
226
+ maeve media:bulk-unlabel --workspace <id> --json media-bulk-labels.json
227
+ maeve media:bulk-delete --workspace <id> --json media-ids.json --yes
228
+ maeve media:bulk-restore --workspace <id> --json media-ids.json
229
+ maeve media:bulk-delete-forever --workspace <id> --json media-ids.json --yes
230
+ ```
231
+
232
+ ### Content
233
+
234
+ ```bash
235
+ maeve content:create --workspace <id> --json examples/create-content.json
236
+ maeve content:update --workspace <id> --id <contentId> --json update-content.json
237
+ maeve content:list --workspace <id> --status scheduled
238
+ maeve content:list --workspace <id> --workflow-statuses approved,needs_changes
239
+ maeve content:get --workspace <id> --id <contentId>
240
+ maeve content:schedule --workspace <id> --id <contentId> --scheduled-at "2026-05-01T10:00:00+10:00"
241
+ maeve content:intended-time --workspace <id> --id <contentId> --scheduled-at "2026-05-01T10:00:00+10:00"
242
+ maeve content:notes --workspace <id> --id <contentId> --notes "<p>Planning notes</p>"
243
+ maeve content:notes --workspace <id> --id <contentId> --json notes.json
244
+ maeve content:publish --workspace <id> --id <contentId> --yes
245
+ maeve content:published-caption --workspace <id> --id <contentId> --json published-caption.json --yes
246
+ maeve content:archive --workspace <id> --id <contentId>
247
+ maeve content:restore --workspace <id> --id <contentId>
248
+ maeve content:retry --workspace <id> --id <contentId>
249
+ maeve content:delete --workspace <id> --id <contentId>
250
+ maeve content:recurring-occurrence:cancel --workspace <id> --occurrence <occurrenceId> --yes
251
+ maeve content:recurring-series:cancel --workspace <id> --series <seriesId> --effective-occurrence <occurrenceId> [--expected-version <number>] --yes
252
+ maeve content:failed-count --workspace <id>
253
+ maeve content:pending-approval-count --workspace <id>
254
+ ```
255
+
256
+ `content:delete` refuses content that belongs to a recurring series; use the recurring cancel commands instead. `content:list` includes a `recurringMetadata` object on recurring-linked items with the `seriesId`, `occurrenceId`, and `seriesVersion` those commands need. `content:recurring-occurrence:cancel` cancels a single occurrence. `content:recurring-series:cancel` cancels the series and every not-yet-published occurrence from the effective occurrence forward. Both require `--yes` because they remove scheduled publishing work.
257
+
258
+ Approvals, comments, and activity:
259
+
260
+ ```bash
261
+ maeve content:approval-history --workspace <id>
262
+ maeve content:approval-history:client-batch --workspace <id> --batch <batchId>
263
+ maeve content:approval-history:internal --workspace <id> --record <recordId>
264
+ maeve content:comment --workspace <id> --id <contentId> --json comment.json
265
+ maeve content:comment-attachments:init --workspace <id> --json attachment.json
266
+ maeve content:comment-attachments:complete --workspace <id> --attachment <attachmentId>
267
+ maeve content:comment-attachments:abort --workspace <id> --attachment <attachmentId>
268
+ maeve content:activity:react --workspace <id> --activity <activityId> --json reaction.json
269
+ maeve content:activity:unreact --workspace <id> --activity <activityId> --emoji thumbs-up
270
+ maeve content:request-approval --workspace <id> --id <contentId> --json approval.json --yes
271
+ maeve content:decision --workspace <id> --id <contentId> --json decision.json
272
+ maeve content:withdraw --workspace <id> --id <contentId>
273
+ maeve content:resubmit --workspace <id> --id <contentId> --json approval.json --yes
274
+ maeve content:history --workspace <id> --id <contentId>
275
+ maeve content:reopen-client-review --workspace <id> --id <contentId>
276
+ ```
277
+
278
+ ### Client reviews
279
+
280
+ ```bash
281
+ maeve client-reviews:create --workspace <id> --json client-review.json
282
+ maeve client-reviews:open --workspace <id>
283
+ maeve client-reviews:get --workspace <id> --batch <batchId>
284
+ maeve client-reviews:add-post --workspace <id> --batch <batchId> --json post-id.json
285
+ maeve client-reviews:remove-post --workspace <id> --batch <batchId> --json post-id.json
286
+ maeve client-reviews:send --workspace <id> --batch <batchId> --yes
287
+ maeve client-reviews:resend --workspace <id> --batch <batchId> --yes
288
+ maeve client-reviews:cancel --workspace <id> --batch <batchId>
289
+ maeve client-reviews:override --workspace <id> --batch <batchId> --json override.json
290
+ maeve client-reviews:update-participant --workspace <id> --batch <batchId> --json participant.json
291
+ maeve client-reviews:comment --workspace <id> --batch <batchId> --json comment.json
292
+ ```
293
+
294
+ ### Campaigns
295
+
296
+ ```bash
297
+ maeve campaigns:list --workspace <id> --include-phases
298
+ maeve campaigns:get --workspace <id> --id <campaignId>
299
+ maeve campaigns:content --workspace <id> --id <campaignId> --status scheduled
300
+ maeve campaigns:create --workspace <id> --json campaign.json
301
+ maeve campaigns:update --workspace <id> --id <campaignId> --json campaign.json
302
+ maeve campaigns:archive --workspace <id> --id <campaignId>
303
+ maeve campaigns:restore --workspace <id> --id <campaignId>
304
+ maeve campaigns:phases:list --workspace <id> --id <campaignId>
305
+ maeve campaigns:phases:create --workspace <id> --id <campaignId> --json campaign-phase.json
306
+ maeve campaigns:phases:replace --workspace <id> --id <campaignId> --json campaign-phases.json
307
+ maeve campaigns:phases:update --workspace <id> --id <campaignId> --phase-id <phaseId> --json campaign-phase.json
308
+ maeve campaigns:phases:delete --workspace <id> --id <campaignId> --phase-id <phaseId> --yes
309
+ ```
310
+
311
+ Use `campaigns:list` and `campaigns:get` to find the campaign and phase IDs that
312
+ `content:create` and `content:update` accept in `campaignId` / `campaignPhaseId`. Create
313
+ payload: `name`, `startDate`, and `endDate` are required (dates are ISO 8601); `color`,
314
+ `goal`, `description`, `targetPlatforms`, `estimatedBudget`, `actualBudget`, and
315
+ `budgetCurrency` are optional. `campaigns:phases:replace` takes the full `{ "phases": [...] }`
316
+ array.
317
+
318
+ ```json
319
+ {
320
+ "name": "May launch",
321
+ "startDate": "2026-05-01",
322
+ "endDate": "2026-05-31",
323
+ "color": "#2563EB",
324
+ "targetPlatforms": ["instagram", "facebook"]
325
+ }
326
+ ```
327
+
328
+ ### Strategy
329
+
330
+ Read-only. The CLI exposes the strategy reads the public API serves; strategy is edited in
331
+ the app.
332
+
333
+ ```bash
334
+ maeve strategy:foundation --workspace <id>
335
+ maeve strategy:goals --workspace <id>
336
+ maeve strategy:goals:metrics --workspace <id>
337
+ maeve strategy:goal --workspace <id> --id <goalId>
338
+ ```
339
+
340
+ ### Taxonomy and hashtags
341
+
342
+ ```bash
343
+ maeve taxonomy:labels --workspace <id>
344
+ maeve taxonomy:pillars --workspace <id>
345
+ maeve taxonomy:formats --workspace <id>
346
+ maeve hashtags:list --workspace <id>
347
+ maeve hashtags:create --workspace <id> --json hashtags.json
348
+ maeve hashtags:update --workspace <id> --id <hashtagGroupId> --json hashtags.json
349
+ maeve hashtags:delete --workspace <id> --id <hashtagGroupId>
350
+ ```
351
+
352
+ ### Analytics
353
+
354
+ ```bash
355
+ maeve analytics:summary --workspace <id> --days 30
356
+ maeve analytics:summary --workspace <id> --integration <id> --days all
357
+ maeve analytics:summary --workspace <id> --integration-ids <id,id>
358
+ maeve analytics:health --workspace <id> --integration <id>
359
+ maeve analytics:posts --workspace <id> --integration <id> --limit 12 --offset 0
360
+ maeve analytics:posts-aggregate --workspace <id> --integration-ids <id,id> --sort-by views --days 30
361
+ maeve analytics:post --workspace <id> --id <contentId>
362
+ maeve analytics:demographics --workspace <id> --integration <id>
363
+ maeve analytics:report --workspace <id> --provider instagram --json analytics-report.json --output report.pdf --yes
364
+ ```
365
+
366
+ `analytics:summary` works on one integration or aggregates across many. Use `--integration-ids` to pick a set, or omit it for all accessible integrations. `--days` accepts `1` to `90` or `all`.
367
+
368
+ `analytics:posts` is single-integration. `analytics:posts-aggregate` accepts `--integration-ids` and `--sort-by recent|engagement|views`.
369
+
370
+ `analytics:report` is standard-plan only. It saves the PDF to `--output`. Stdout stays JSON metadata.
371
+
372
+ ### Inbox
373
+
374
+ ```bash
375
+ maeve inbox:threads --workspace <id> --read unread
376
+ maeve inbox:messages --workspace <id> --thread <threadId>
377
+ maeve inbox:stats --workspace <id>
378
+ maeve inbox:read --workspace <id> --thread <threadId>
379
+ maeve inbox:unread --workspace <id> --thread <threadId>
380
+ maeve inbox:read-all --workspace <id> --json inbox-read-all.json --yes
381
+ maeve inbox:resolve --workspace <id> --thread <threadId>
382
+ maeve inbox:reopen --workspace <id> --thread <threadId>
383
+ maeve inbox:resolve-all --workspace <id> --json inbox-resolve-all.json --yes
384
+ maeve inbox:resolve-message --workspace <id> --message <messageId>
385
+ maeve inbox:reopen-message --workspace <id> --message <messageId>
386
+ maeve inbox:reply --workspace <id> --thread <threadId> --json inbox-reply.json --yes
387
+ maeve inbox:note --workspace <id> --thread <threadId> --json inbox-note.json
388
+ maeve inbox:moderate --workspace <id> --message <messageId> --json inbox-moderate.json --yes
389
+ maeve inbox:retry-message --workspace <id> --message <messageId> --yes
390
+ maeve inbox:delete-failed --workspace <id> --message <messageId> --yes
391
+ maeve inbox:tags --workspace <id> --include-archived
392
+ maeve inbox:tags:create --workspace <id> --name "Needs follow-up" --color "#2563eb"
393
+ maeve inbox:tags:update --workspace <id> --tag <tagId> --name "Replied"
394
+ maeve inbox:tags:archive --workspace <id> --tag <tagId>
395
+ maeve inbox:tags:restore --workspace <id> --tag <tagId>
396
+ maeve inbox:tags:delete --workspace <id> --tag <tagId> --yes
397
+ maeve inbox:tags:reorder --workspace <id> --json inbox-tag-order.json
398
+ maeve inbox:tag-thread --workspace <id> --thread <threadId> --tag <tagId>
399
+ ```
400
+
401
+ `inbox:resolve` and `inbox:resolve-message` accept an optional `--json` file with a resolution reason and note. `inbox:resolve-all` resolves every thread matching the filter payload and requires `--yes`. The reopen commands take no body.
402
+
403
+ `inbox:tag-thread` replaces the complete tag set on one thread. Repeat `--tag` to assign
404
+ several tags, or omit it to clear all tags. `inbox:tags:delete` removes the tag from every
405
+ conversation and therefore requires `--yes`.
406
+
407
+ ### Task board
408
+
409
+ ```bash
410
+ maeve tasks:list --workspace <id>
411
+ maeve tasks:archive --workspace <id> --limit 50
412
+ maeve tasks:get --workspace <id> --id <taskId> --include-comments --include-activity
413
+ maeve tasks:for-post --workspace <id> --post-id <postId>
414
+ maeve tasks:linkable-posts --workspace <id> --query "launch" --stage drafting
415
+ maeve tasks:comments --workspace <id> --id <taskId>
416
+ maeve tasks:activity --workspace <id> --id <taskId> --limit 50
417
+ maeve tasks:create --workspace <id> --json task.json
418
+ maeve tasks:update --workspace <id> --id <taskId> --json task-update.json
419
+ maeve tasks:move --workspace <id> --id <taskId> --json task-move.json
420
+ maeve tasks:archive-task --workspace <id> --id <taskId>
421
+ maeve tasks:restore --workspace <id> --id <taskId>
422
+ maeve tasks:delete --workspace <id> --id <taskId> --yes
423
+ maeve tasks:comments:create --workspace <id> --id <taskId> --json task-comment.json
424
+ maeve tasks:checklist:create --workspace <id> --id <taskId> --json checklist-item.json
425
+ maeve tasks:checklist:update --workspace <id> --id <taskId> --item <itemId> --json checklist-update.json
426
+ maeve tasks:checklist:move --workspace <id> --id <taskId> --item <itemId> --json checklist-move.json
427
+ maeve tasks:checklist:delete --workspace <id> --id <taskId> --item <itemId> --yes
428
+ ```
429
+
430
+ `tasks:archive` lists archived tasks; `tasks:archive-task` archives one task. Archived tasks
431
+ are read-only until `tasks:restore` brings them back. `tasks:delete` and
432
+ `tasks:checklist:delete` require `--yes`; deleted tasks cannot be restored.
433
+
434
+ ### Grid planner
435
+
436
+ ```bash
437
+ maeve grid:list --workspace <id> --integration <integrationId>
438
+ maeve grid:create --workspace <id> --json grid-item.json
439
+ maeve grid:update --workspace <id> --item <itemId> --json grid-update.json
440
+ maeve grid:delete --workspace <id> --item <itemId> --yes
441
+ maeve grid:reorder --workspace <id> --json grid-reorder.json
442
+ maeve grid:replace-media --workspace <id> --item <itemId> --json grid-media.json
443
+ maeve grid:set-cover --workspace <id> --item <itemId> --json grid-cover.json
444
+ maeve grid:remove-cover --workspace <id> --item <itemId>
445
+ maeve grid:promote --workspace <id> --item <itemId> --json grid-promote.json --yes
446
+ ```
447
+
448
+ ### Boosts
449
+
450
+ Read-only, standard plan. The CLI reads boost state for reporting but does not create, pause,
451
+ resume, delete, or estimate boosts, because those spend Meta ad budget. Run those from the
452
+ app.
453
+
454
+ ```bash
455
+ maeve boosts:list --workspace <id> --statuses active,paused
456
+ maeve boosts:get --workspace <id> --id <boostId>
457
+ maeve boosts:performance --workspace <id> --id <boostId>
458
+ maeve boosts:ad-accounts --workspace <id> --integration <integrationId>
459
+ maeve boosts:boosted-content-ids --workspace <id>
460
+ maeve boosts:by-content --workspace <id> --content <contentId>
461
+ ```
462
+
463
+ ## Payloads
464
+
465
+ ### Content
466
+
467
+ `content:create` reads a JSON file and sends an `Idempotency-Key` header for safe retries. Pass `--idempotency-key <key>` to reuse a known key, or omit it to let the CLI generate one. Omitted `intent` defaults to `draft`; use `schedule` with `scheduledAt`, or `publish_now` plus CLI `--yes` for explicit immediate publishing. `content:update` uses the same shape except `intent`, but every accepted field is optional and at least one must be present.
468
+
469
+ ```json
470
+ {
471
+ "integrationId": "00000000-0000-4000-8000-000000000001",
472
+ "intent": "draft",
473
+ "internalTitle": "Launch planning card",
474
+ "captions": {
475
+ "canonical": "Launch post copy"
476
+ },
477
+ "contentMedia": [
478
+ {
479
+ "mediaId": "00000000-0000-4000-8000-000000000007",
480
+ "order": 0,
481
+ "cover": {
482
+ "thumbOffsetMs": 2500
483
+ }
484
+ }
485
+ ],
486
+ "pillarIds": ["00000000-0000-4000-8000-000000000002"],
487
+ "formatIds": ["00000000-0000-4000-8000-000000000003"],
488
+ "labelIds": ["00000000-0000-4000-8000-000000000004"],
489
+ "campaignId": "00000000-0000-4000-8000-000000000005",
490
+ "campaignPhaseId": "00000000-0000-4000-8000-000000000006"
491
+ }
492
+ ```
493
+
494
+ Common fields:
495
+
496
+ - `integrationId`: integration to post to.
497
+ - `intent`: `draft`, `schedule`, or `publish_now`. Omitted intent defaults to `draft`.
498
+ - `internalTitle`: internal planning title. Never published.
499
+ - `publishTitle`: provider-facing title for platforms that support or require one.
500
+ - `captions`: canonical publish text and optional platform overrides.
501
+ - `notes`: internal rich-text notes. Never published. Max 100000 characters.
502
+ - `contentMedia`: array of uploaded media relationships, with optional per-media crops, tags, and cover metadata.
503
+ - `scheduledAt`: ISO 8601 with timezone, e.g. `2026-05-01T10:00:00+10:00`. Required only with `intent: "schedule"`.
504
+ - `postType`: `post`, `reel`, `story`, or `thread`.
505
+ - `settings`: provider settings from integration capabilities only. Media metadata belongs in `contentMedia`.
506
+ - `firstComment`, `shareToFeed`: optional publish behavior fields.
507
+ - `pillarIds`, `formatIds`, `labelIds`: arrays of workspace taxonomy UUIDs.
508
+ - `campaignId`, `campaignPhaseId`: optional root-post campaign links. Use `null` to clear when updating.
509
+ - `priority`: optional root-post priority, `urgent`, `high`, `medium`, or `low`.
510
+ - `threadMessages`: items for thread-style content using `captions` and optional `contentMedia`.
511
+
512
+ For X, inspect `integrations:capabilities` for the selected account first. Root text uses `captions.canonical` with an optional `captions.overrides.x`. Omit `overrides` on an edit to preserve existing overrides; `{}` clears them and `{ "x": "" }` keeps an explicitly empty X caption. Replies use only `captions.canonical`; nonempty reply overrides are rejected. Omit `threadMessages` to preserve replies or send `[]` to remove them. Each supplied reply array replaces the saved reply sequence. Use `content:get` afterward to read the current reply IDs.
513
+
514
+ X supports up to 20 replies and four attachments per message. Put media in `contentMedia` with unique `order` values and supported per-platform `crops`. Use `media:get` and `media:update` for media-record `altText`, including `""` to clear it. X publishes this metadata for images and GIFs, not videos. Custom covers, tags and first comments are not X composer controls. Standard text uses a 280 weighted-character limit; longer text requires confirmed subscription eligibility. Standard videos are limited to 140 seconds and 512MB; the existing blue-verified path allows up to 10 minutes. A successful draft save proves neither entitlement nor publishing readiness. Maeve blocks URLs in resolved root and reply captions at publishing handoff.
515
+
516
+ Read `content:get` after publishing to capture the X root and reply `platformPostId` and `permalink` values. Historical publications may have missing reply identities or links. A queued response is not publication proof; verify the native posts before cleanup. Pace draft creation and replay, which both count toward the API request limit, and stop on a rate-limit response before resuming after its cooldown.
517
+
518
+ The web content header uses the root `organizationalTitle`. Change it with `content:metadata --id <id> --json <file>` and `{"organizationalTitle":null}` to clear it. `content:update` changes the edition's `internalTitle`; it does not rename the shared root or sibling editions. Other metadata fields are optional, and empty assignment arrays clear those assignments.
519
+
520
+ `settings.xCommunityUrl` targets the root only. Its existing parser extracts digits after `communities/`; a nonempty value without that pattern is ignored and publishing uses the normal account timeline. Set `""` to clear it. Verify the community and the account's access before publishing. Creative edits do not reschedule content; use `content:schedule` or `content:revert-to-draft`. Archive and restore target the published thread root and include its replies. Deleting published X content removes only the selected root or reply from X and retains its sent audit record; deleting the root does not delete its replies. For multiple drafts, run `content:create` once per item with a stable, distinct `--idempotency-key`, retain each successful ID, and read every result. Do not create fresh content to recover an uncertain publication.
521
+
522
+ For Google Business Profile, choose the integration for the exact business location with `integrations:list` and inspect `integrations:capabilities`. An account can contain multiple locations. Use `postType: "post"` and `settings.googlePostType: "standard"`, `"event"` or `"offer"`. Text uses `captions.canonical` and optionally `captions.overrides["google-business-profile"]`. Omit overrides on update to preserve them, use `{}` to clear them, or use an explicitly empty platform string to retain an empty override. The published summary is trimmed and limited to 1,500 characters. Existing caption URL restrictions still apply.
523
+
524
+ Google updates replace the supplied `settings` object. Omit `settings` to preserve it. To remove a button, send the remaining settings without `googleCallToActionType` and `googleCallToActionUrl`; do not send an empty CTA enum. For example, `{"settings":{"googlePostType":"standard"}}` removes the previous CTA and event/offer fields. Use `{"settings":{}}` to reset all Google settings to their defaults; `content:get` returns `settings: null` when no public settings remain. Retain any language override or other wanted fields in a replacement.
525
+
526
+ The six CTA values are `book`, `order`, `shop`, `learn_more`, `sign_up` and `call`. All except `call` require an HTTPS `googleCallToActionUrl`. CALL uses the business location phone number; omit its URL. Offers cannot have a CTA. Event and Offer require `googleEventTitle`, `googleEventStartAt` and `googleEventEndAt`. The event title is separate from the root `organizationalTitle` and edition `internalTitle`. Use two dates such as `2026-10-01` and `2026-10-02`, or two local wall-clock times such as `2026-10-01T09:15` and `2026-10-01T10:45`. The end must be later. Do not include `Z` or a timezone offset. Use minute precision. These ranges do not schedule publication: use the existing scheduling commands and an explicit publication instant with timezone. Creative updates preserve the existing publication time.
527
+
528
+ Offer-only fields are `googleOfferCouponCode`, `googleOfferRedeemOnlineUrl` (HTTPS) and `googleOfferTerms`. Remove them when leaving Offer; remove event fields when returning to Standard. `googleLanguageCode` overrides the connected location's language; omission or an empty string falls back to that saved language when available.
529
+
530
+ Google supports text or one image, not threads, video or multiple images. Attach an owned media record through `contentMedia`, replace it with a new single relationship, or send `[]` to remove it. The current Google provider sends the original HTTPS image URL and does not apply relationship crops or media-record `altText`. Those saved metadata values do not prove a crop or alternative text was published. The image MIME check is not proof of GIF acceptance or compliance with Google's media requirements.
531
+
532
+ Read `content:get` for the stored Google `platformPostId` (`accounts/.../locations/.../localPosts/...`) and safe `permalink`. A queue response, sent status or resource name alone is not proof of native visibility. Existing selected deletion uses this identity and retains the publication audit record. Google has no product published-caption edit command. For draft matrices use one `content:create` per item with distinct stable idempotency keys, retain successful IDs, replay identical inputs with the same key, and use a new key for a corrected failed item. Never publish or schedule a draft matrix as part of draft validation.
533
+
534
+ `content:published-caption` edits the provider caption for an already-published Facebook item and requires `--yes`.
535
+
536
+ ```json
537
+ {
538
+ "message": "Updated Facebook caption"
539
+ }
540
+ ```
541
+
542
+ ### Approvals and client reviews
543
+
544
+ Approval and client review commands need a standard workspace plan and the role shown in each command's `--help`. Payloads mirror the public API DTOs.
545
+
546
+ Internal approval request:
547
+
548
+ ```json
549
+ {
550
+ "approverIds": ["00000000-0000-4000-8000-000000000001"],
551
+ "policy": "any"
552
+ }
553
+ ```
554
+
555
+ Client review batch:
556
+
557
+ ```json
558
+ {
559
+ "contentIds": ["00000000-0000-4000-8000-000000000001"],
560
+ "workflowMode": "client",
561
+ "client": {
562
+ "reviewers": [{ "name": "Client Reviewer", "email": "reviewer@example.com" }],
563
+ "policy": "all",
564
+ "inviteNote": "Please review when you have a moment.",
565
+ "batchLabel": "May launch"
566
+ }
567
+ }
568
+ ```
569
+
570
+ Use `workflowMode: "internal_client"` with an `internal` object containing `approverIds` and `policy` when internal approval is required before client review.
571
+
572
+ `content:create` requires `--yes` when the payload has `intent: "publish_now"`, `content:publish` requires `--yes` because it queues external publishing, and `content:published-caption` requires `--yes` because it edits already-published provider content. `content:request-approval`, `content:resubmit`, `client-reviews:send`, and `client-reviews:resend` all require `--yes` because they may notify people.
573
+
574
+ ### Inbox
575
+
576
+ Inbox commands take explicit thread or message IDs. Public replies, moderation, retries, deletes, and `read-all` require `--yes`.
577
+
578
+ Reply:
579
+
580
+ ```json
581
+ {
582
+ "content": "Thanks for reaching out.",
583
+ "parentMessageId": "00000000-0000-4000-8000-000000000001"
584
+ }
585
+ ```
586
+
587
+ `content` is optional when an `attachment` is provided:
588
+
589
+ ```json
590
+ {
591
+ "attachment": {
592
+ "type": "image",
593
+ "url": "https://cdn.example.com/reply.png"
594
+ }
595
+ }
596
+ ```
597
+
598
+ Internal note:
599
+
600
+ ```json
601
+ { "content": "Follow up with the team before replying." }
602
+ ```
603
+
604
+ Moderation. Allowed actions: `hide`, `unhide`, `delete`.
605
+
606
+ ```json
607
+ { "action": "hide" }
608
+ ```
609
+
610
+ Read-all takes the same filter shape as listing threads. Use `{}` only when you want all matching threads marked read.
611
+
612
+ ```json
613
+ {
614
+ "platform": "instagram",
615
+ "status": "open",
616
+ "integrationId": "00000000-0000-4000-8000-000000000001",
617
+ "messageType": "comment"
618
+ }
619
+ ```
620
+
621
+ ### Task board
622
+
623
+ Create:
624
+
625
+ ```json
626
+ {
627
+ "title": "Draft launch teaser",
628
+ "description": "Short teaser for the spring drop.",
629
+ "priority": "high",
630
+ "dueAt": "2026-09-01",
631
+ "assigneeIds": ["00000000-0000-4000-8000-000000000001"],
632
+ "checklistItemTitles": ["Write caption", "Pick cover image"]
633
+ }
634
+ ```
635
+
636
+ Update payloads carry only the fields to change. Pass `null` to clear a value or remove the
637
+ content post link. Assignee and label arrays are full replacement sets.
638
+
639
+ ```json
640
+ { "priority": null, "postId": null, "dueAt": "2026-09-05" }
641
+ ```
642
+
643
+ Move (omit both anchors to place the task at the top of the column):
644
+
645
+ ```json
646
+ { "targetColumnId": "00000000-0000-4000-8000-000000000002", "afterTaskId": null }
647
+ ```
648
+
649
+ Comment (@mentions notify workspace members):
650
+
651
+ ```json
652
+ { "content": "Ready for review @Brie." }
653
+ ```
654
+
655
+ Checklist create, update, and move:
656
+
657
+ ```json
658
+ { "title": "Write caption" }
659
+ ```
660
+
661
+ ```json
662
+ { "isCompleted": true }
663
+ ```
664
+
665
+ ```json
666
+ { "beforeItemId": "00000000-0000-4000-8000-000000000003" }
667
+ ```
668
+
669
+ ### Grid planner
670
+
671
+ Visual-only item:
672
+
673
+ ```json
674
+ {
675
+ "integrationId": "00000000-0000-4000-8000-000000000001",
676
+ "kind": "visual_only",
677
+ "mediaIds": ["00000000-0000-4000-8000-000000000002"],
678
+ "note": "Plan this visual",
679
+ "settings": { "aspectRatio": 1 }
680
+ }
681
+ ```
682
+
683
+ Linked content item:
684
+
685
+ ```json
686
+ {
687
+ "integrationId": "00000000-0000-4000-8000-000000000001",
688
+ "kind": "linked_post",
689
+ "linkedContentId": "00000000-0000-4000-8000-000000000003"
690
+ }
691
+ ```
692
+
693
+ Reorder needs the full item ID order for the integration:
694
+
695
+ ```json
696
+ {
697
+ "integrationId": "00000000-0000-4000-8000-000000000001",
698
+ "itemIds": ["00000000-0000-4000-8000-000000000004"]
699
+ }
700
+ ```
701
+
702
+ Media, cover, and promote payloads:
703
+
704
+ ```json
705
+ { "integrationId": "00000000-0000-4000-8000-000000000001", "mediaIds": ["00000000-0000-4000-8000-000000000002"] }
706
+ ```
707
+
708
+ ```json
709
+ { "integrationId": "00000000-0000-4000-8000-000000000001", "mediaId": "00000000-0000-4000-8000-000000000005" }
710
+ ```
711
+
712
+ ```json
713
+ { "integrationId": "00000000-0000-4000-8000-000000000001" }
714
+ ```
715
+
716
+ `grid:delete` and `grid:promote` require `--yes`.
717
+
718
+ ### Media
719
+
720
+ Supported upload extensions:
721
+
722
+ - `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.avif`
723
+ - `.mp4`, `.mov`
724
+
725
+ Images cap at 50 MB, videos at 512 MB.
726
+
727
+ Update payload:
728
+
729
+ ```json
730
+ {
731
+ "filename": "campaign-hero.png",
732
+ "altText": "Campaign hero image",
733
+ "isFavorite": true,
734
+ "folderId": null
735
+ }
736
+ ```
737
+
738
+ Folder and label:
739
+
740
+ ```json
741
+ { "name": "Launch assets" }
742
+ ```
743
+
744
+ ```json
745
+ { "name": "Approved", "color": "#22cc88" }
746
+ ```
747
+
748
+ Bulk payloads are explicit ID lists:
749
+
750
+ ```json
751
+ { "mediaIds": ["00000000-0000-4000-8000-000000000001"] }
752
+ ```
753
+
754
+ ```json
755
+ {
756
+ "mediaIds": ["00000000-0000-4000-8000-000000000001"],
757
+ "labelIds": ["00000000-0000-4000-8000-000000000002"]
758
+ }
759
+ ```
760
+
761
+ `media:bulk-delete` moves active media to the Bin and requires `--yes`. Use `media:list --state deleted` to inspect Bin items.
762
+
763
+ ## License
764
+
765
+ MIT (c) 2026 LILY DIA PTY LTD.