postemi 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +685 -0
- package/PROVIDER_SETTINGS.md +374 -0
- package/README.md +126 -2
- package/SKILL.md +1068 -0
- package/SUPPORTED_FILE_TYPES.md +102 -0
- package/dist/index.js +2223 -0
- package/dist/index.js.map +1 -0
- package/examples/multi-platform-post.json +91 -0
- package/examples/multi-platform-with-settings.json +71 -0
- package/examples/post-with-comments.json +55 -0
- package/examples/thread-post.json +68 -0
- package/examples/tiktok-video.json +31 -0
- package/examples/youtube-video.json +29 -0
- package/package.json +72 -4
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Provider-specific settings
|
|
2
|
+
|
|
3
|
+
Every channel Postemi supports, and every field its `--settings` payload accepts.
|
|
4
|
+
|
|
5
|
+
**The channels below are the whole list.** Postemi's server carries inherited
|
|
6
|
+
code for many other providers — Reddit, Mastodon, Medium, Dev.to, Hashnode,
|
|
7
|
+
WordPress, Lemmy, Nostr, VK, Tumblr, Dribbble, Farcaster and others.
|
|
8
|
+
None of them are enabled or supported. Do not build a settings payload for one.
|
|
9
|
+
|
|
10
|
+
| Channel | `__type` | Has settings? | Status |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| X (Twitter) | `x` | yes | Publishing verified |
|
|
13
|
+
| Facebook | `facebook` | yes, all optional | Publishing verified |
|
|
14
|
+
| Instagram | `instagram` | yes | Publishing verified |
|
|
15
|
+
| Threads | `threads` | **no** | Publishing verified |
|
|
16
|
+
| Bluesky | `bluesky` | **no** | Publishing verified |
|
|
17
|
+
| LinkedIn | `linkedin` | yes, all optional | Publishing verified |
|
|
18
|
+
| Telegram | `telegram` | **no** | Publishing verified |
|
|
19
|
+
| Discord | `discord` | yes, `channel` required | Publishing verified |
|
|
20
|
+
| Slack | `slack` | yes, `channel` required | Publishing verified |
|
|
21
|
+
| YouTube | `youtube` | yes | Publishing verified |
|
|
22
|
+
| TikTok | `tiktok` | yes | Publishing verified |
|
|
23
|
+
| Pinterest | `pinterest` | yes, `board` required | BETA: pins visible only to their creator until Pinterest grants Standard access |
|
|
24
|
+
|
|
25
|
+
Source of truth for everything on this page:
|
|
26
|
+
`libraries/nestjs-libraries/src/dtos/posts/providers-settings/*.ts` in the Postemi
|
|
27
|
+
server. If this file and a DTO disagree, the DTO is right — and the
|
|
28
|
+
`integrations:settings` route serves those DTOs as live JSON schema, so you can
|
|
29
|
+
always ask instead of trusting a document:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
postemi integrations:settings <integration-id> | jq '.output.settings'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## How to pass settings
|
|
38
|
+
|
|
39
|
+
### Command line
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
postemi posts:create \
|
|
43
|
+
-c "Your content" \
|
|
44
|
+
-s "2026-12-31T12:00:00Z" \
|
|
45
|
+
--settings '<json>' \
|
|
46
|
+
-i "<integration-id>"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The backend fills in `__type` from the integration's provider. You do not send
|
|
50
|
+
it on the command line.
|
|
51
|
+
|
|
52
|
+
### JSON file
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
postemi posts:create --json post.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"type": "schedule",
|
|
61
|
+
"date": "2026-12-31T12:00:00Z",
|
|
62
|
+
"shortLink": false,
|
|
63
|
+
"tags": [],
|
|
64
|
+
"posts": [{
|
|
65
|
+
"integration": { "id": "<youtube-integration-id>" },
|
|
66
|
+
"value": [{ "content": "Video description", "image": [{ "id": "…", "path": "https://…" }] }],
|
|
67
|
+
"settings": {
|
|
68
|
+
"__type": "youtube",
|
|
69
|
+
"title": "Video title",
|
|
70
|
+
"type": "public",
|
|
71
|
+
"tags": [{ "value": "tech", "label": "Tech" }]
|
|
72
|
+
}
|
|
73
|
+
}]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
You do not have to write `__type` even here: the server stamps it from the
|
|
78
|
+
integration's provider before validating, overwriting whatever you sent. Write
|
|
79
|
+
it for readability if you like — a stale or wrong value cannot break the
|
|
80
|
+
request, because it is replaced, not checked.
|
|
81
|
+
|
|
82
|
+
**Two behaviours worth knowing before you publish:**
|
|
83
|
+
|
|
84
|
+
- **A key without the `posts:publish` scope produces a draft, not a refusal.**
|
|
85
|
+
`posts:create` needs only `posts:draft`; without `posts:publish` the server
|
|
86
|
+
coerces `type` to `draft` and creates one. Read back the `type` in the
|
|
87
|
+
response rather than assuming the one you sent.
|
|
88
|
+
- **One X channel per post.** A single post may not target two X channels,
|
|
89
|
+
whatever the content — X's Developer Policy forbids it and the developer
|
|
90
|
+
account at risk is Postemi's, shared by every customer. Any other combination
|
|
91
|
+
of channels is fine.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## X (Twitter) — `x`
|
|
96
|
+
|
|
97
|
+
| Field | Required | Values |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `who_can_reply_post` | **yes** | `everyone`, `following`, `mentionedUsers`, `subscribers`, `verified` |
|
|
100
|
+
| `community` | no | A community URL matching `https://x.com/i/communities/<digits>`, or empty string |
|
|
101
|
+
| `made_with_ai` | no | boolean |
|
|
102
|
+
| `paid_partnership` | no | boolean |
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
postemi posts:create \
|
|
106
|
+
-c "Announcement" \
|
|
107
|
+
-s "2026-12-31T12:00:00Z" \
|
|
108
|
+
--settings '{"who_can_reply_post":"everyone"}' \
|
|
109
|
+
-i "$X_ID"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A malformed `community` fails validation with the expected format in the
|
|
113
|
+
message. Anything other than the five reply values is a `400`.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Facebook — `facebook`
|
|
118
|
+
|
|
119
|
+
All fields optional.
|
|
120
|
+
|
|
121
|
+
| Field | Values |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `post_type` | `post`, `story` |
|
|
124
|
+
| `url` | A URL to attach |
|
|
125
|
+
| `text_format_preset_id` | A Facebook background preset id |
|
|
126
|
+
|
|
127
|
+
`text_format_preset_id` applies to **text-only posts on Pages** and caps at
|
|
128
|
+
about 130 characters. Facebook exposes no API to enumerate the presets, so the
|
|
129
|
+
server carries a hardcoded catalogue; the ids are opaque strings.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
IMG=$(postemi upload photo.jpg | jq -r '.path')
|
|
133
|
+
postemi posts:create -c "Post text" -s "2026-12-31T12:00:00Z" \
|
|
134
|
+
--settings '{"post_type":"post"}' -m "$IMG" -i "$FB_ID"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Instagram — `instagram`
|
|
140
|
+
|
|
141
|
+
| Field | Required | Values |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `post_type` | **yes** | `post`, `story` |
|
|
144
|
+
| `collaborators` | no | array of `{ "label": "<username>" }` |
|
|
145
|
+
| `audio` | no | `{ id, title?, artist?, image?, audio_volume?, video_volume? }` — volumes 0–100 |
|
|
146
|
+
| `is_trial_reel` | no | boolean |
|
|
147
|
+
| `graduation_strategy` | no | `MANUAL`, `SS_PERFORMANCE` |
|
|
148
|
+
|
|
149
|
+
`audio.id` comes from the `audioSearch` tool:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
postemi integrations:trigger "$IG_ID" audioSearch -d '{"q":"lofi","type":"music"}'
|
|
153
|
+
# type is "music" (default) or "original_sound"; an empty q returns trending audio
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
STORY=$(postemi upload story.jpg | jq -r '.path')
|
|
158
|
+
postemi posts:create -c "" -s "2026-12-31T12:00:00Z" \
|
|
159
|
+
--settings '{"post_type":"story"}' -m "$STORY" -i "$IG_ID"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Threads — `threads`
|
|
165
|
+
|
|
166
|
+
**No settings.** Omit `--settings`. There are no fields to send and inventing
|
|
167
|
+
one is a `400`.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
postemi posts:create -c "Post text" -s "2026-12-31T12:00:00Z" -i "$THREADS_ID"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Bluesky — `bluesky`
|
|
176
|
+
|
|
177
|
+
**No settings.** Same as Threads.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## YouTube — `youtube`
|
|
182
|
+
|
|
183
|
+
| Field | Required | Values |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `title` | **yes** | 2–100 characters |
|
|
186
|
+
| `type` | **yes** | `public`, `private`, `unlisted` |
|
|
187
|
+
| `selfDeclaredMadeForKids` | no | `yes`, `no` |
|
|
188
|
+
| `thumbnail` | no | A media object from `postemi upload` |
|
|
189
|
+
| `tags` | no | array of `{ value, label }` |
|
|
190
|
+
|
|
191
|
+
The post's `-c` content becomes the **video description**. `title` is separate
|
|
192
|
+
and is the video's title.
|
|
193
|
+
|
|
194
|
+
**The tag budget is 500 characters in total across every tag**, not per tag.
|
|
195
|
+
A tag containing whitespace costs two extra characters against that budget,
|
|
196
|
+
because YouTube wraps it in quotes. Over the budget is a `400` naming the
|
|
197
|
+
limit.
|
|
198
|
+
|
|
199
|
+
There is **no `playlistId` field** and no way to add a video to a playlist
|
|
200
|
+
through the API. Earlier documentation claimed otherwise.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
VIDEO=$(postemi upload video.mp4 | jq -r '.path')
|
|
204
|
+
postemi posts:create \
|
|
205
|
+
-c "Full video description…" \
|
|
206
|
+
-s "2026-12-31T12:00:00Z" \
|
|
207
|
+
--settings '{"title":"How to build a CLI","type":"public","selfDeclaredMadeForKids":"no","tags":[{"value":"tech","label":"Tech"},{"value":"tutorial","label":"Tutorial"}]}' \
|
|
208
|
+
-m "$VIDEO" \
|
|
209
|
+
-i "$YT_ID"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Only MP4 uploads. See [SUPPORTED_FILE_TYPES.md](./SUPPORTED_FILE_TYPES.md).
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## LinkedIn — `linkedin`
|
|
217
|
+
|
|
218
|
+
Both fields optional.
|
|
219
|
+
|
|
220
|
+
| Field | Values |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `post_as_images_carousel` | boolean |
|
|
223
|
+
| `carousel_name` | string |
|
|
224
|
+
|
|
225
|
+
There is **no `companyId`**. Posting as a company page is a separate connected
|
|
226
|
+
channel with identifier `linkedin-page`, chosen at connect time — not a setting
|
|
227
|
+
on a personal post.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
postemi posts:create -c "Content" -s "2026-12-31T12:00:00Z" \
|
|
231
|
+
--settings '{"post_as_images_carousel":true,"carousel_name":"Product launch"}' \
|
|
232
|
+
-m "$A,$B,$C" -i "$LI_ID"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## TikTok — `tiktok`
|
|
238
|
+
|
|
239
|
+
| Field | Required | Values |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `title` | no | ≤ 90 characters |
|
|
242
|
+
| `privacy_level` | **yes for `DIRECT_POST`** | `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY` |
|
|
243
|
+
| `content_posting_method` | **yes** | `DIRECT_POST`, `UPLOAD` |
|
|
244
|
+
| `duet` | yes | boolean — video only, `DIRECT_POST` only |
|
|
245
|
+
| `stitch` | yes | boolean — video only, `DIRECT_POST` only |
|
|
246
|
+
| `comment` | yes | boolean — `DIRECT_POST` only |
|
|
247
|
+
| `autoAddMusic` | yes | `yes`, `no` — photo posts only, `DIRECT_POST` only |
|
|
248
|
+
| `brand_content_toggle` | yes | boolean — `DIRECT_POST` only |
|
|
249
|
+
| `brand_organic_toggle` | yes | boolean — `DIRECT_POST` only |
|
|
250
|
+
| `video_made_with_ai` | no | boolean — video only, `DIRECT_POST` only |
|
|
251
|
+
|
|
252
|
+
The field is `privacy_level`. Earlier documentation called it `privacy`; that
|
|
253
|
+
name is silently ignored, which is worse than an error.
|
|
254
|
+
|
|
255
|
+
**`UPLOAD` does not publish.** It sends the media to the user's TikTok app
|
|
256
|
+
inbox as a draft, which they must finish and publish by hand within 24 hours or
|
|
257
|
+
it is discarded — and TikTok ignores every setting above except `title`. Only
|
|
258
|
+
use it when the user explicitly asks to finish the post inside the TikTok app.
|
|
259
|
+
|
|
260
|
+
**`privacy_level` deliberately has no default.** TikTok's Content Sharing
|
|
261
|
+
Guidelines require the person posting to choose it. Ask the user; do not pick
|
|
262
|
+
one on their behalf.
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
VIDEO=$(postemi upload video.mp4 | jq -r '.path')
|
|
266
|
+
postemi posts:create \
|
|
267
|
+
-c "Caption #fyp" \
|
|
268
|
+
-s "2026-12-31T12:00:00Z" \
|
|
269
|
+
--settings '{"title":"Caption","privacy_level":"PUBLIC_TO_EVERYONE","duet":true,"stitch":true,"comment":true,"autoAddMusic":"no","brand_content_toggle":false,"brand_organic_toggle":false,"content_posting_method":"DIRECT_POST"}' \
|
|
270
|
+
-m "$VIDEO" \
|
|
271
|
+
-i "$TT_ID"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Telegram — `telegram`
|
|
277
|
+
|
|
278
|
+
**No settings.** Same as Threads. The channel is fixed when the user connects
|
|
279
|
+
it in Postemi (one shared bot posts into the channel or group they chose).
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Discord — `discord`
|
|
284
|
+
|
|
285
|
+
| Field | Values |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `channel` | **required** — a channel id from the `channels` tool |
|
|
288
|
+
|
|
289
|
+
Get the id first; a channel name is a `400`:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
postemi integrations:trigger "$DISCORD_ID" channels -d '{}' | jq '.output'
|
|
293
|
+
postemi posts:create -c "Release notes are out" -s "2026-12-31T12:00:00Z" \
|
|
294
|
+
--settings '{"channel":"123456789012345678"}' -i "$DISCORD_ID"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Slack — `slack`
|
|
300
|
+
|
|
301
|
+
| Field | Values |
|
|
302
|
+
|---|---|
|
|
303
|
+
| `channel` | **required** — a channel id from the `channels` tool |
|
|
304
|
+
|
|
305
|
+
Same shape as Discord:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
postemi integrations:trigger "$SLACK_ID" channels -d '{}' | jq '.output'
|
|
309
|
+
postemi posts:create -c "Release notes are out" -s "2026-12-31T12:00:00Z" \
|
|
310
|
+
--settings '{"channel":"C0123456789"}' -i "$SLACK_ID"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Pinterest — `pinterest`
|
|
316
|
+
|
|
317
|
+
**BETA.** Until Pinterest grants Postemi Standard access, a pin is visible only
|
|
318
|
+
to the account that created it. Tell the user before you schedule one.
|
|
319
|
+
|
|
320
|
+
| Field | Values |
|
|
321
|
+
|---|---|
|
|
322
|
+
| `board` | **required** — a board id from the `boards` tool |
|
|
323
|
+
| `title` | optional, up to 100 characters |
|
|
324
|
+
| `link` | optional, a URL the pin opens |
|
|
325
|
+
|
|
326
|
+
A pin needs media: one image (up to 5 images in one pin), or a video with a
|
|
327
|
+
cover picture as the second attachment. Get the board id first; a board name is
|
|
328
|
+
refused:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
postemi integrations:trigger "$PINTEREST_ID" boards | jq '.output'
|
|
332
|
+
IMG=$(postemi upload ./pin.jpg | jq -r '.path')
|
|
333
|
+
postemi posts:create -c "Autumn recipes" -m "$IMG" -s "2026-12-31T12:00:00Z" \
|
|
334
|
+
--settings '{"board":"1234567890123456789","title":"Autumn recipes","link":"https://example.com/recipes"}' \
|
|
335
|
+
-i "$PINTEREST_ID"
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Finding your integration ids
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
postemi integrations:list
|
|
344
|
+
# [{ "id": "…", "name": "…", "identifier": "instagram", … }]
|
|
345
|
+
|
|
346
|
+
X_ID=$(postemi integrations:list | jq -r '.[] | select(.identifier=="x") | .id')
|
|
347
|
+
IG_ID=$(postemi integrations:list | jq -r '.[] | select(.identifier=="instagram")| .id')
|
|
348
|
+
YT_ID=$(postemi integrations:list | jq -r '.[] | select(.identifier=="youtube") | .id')
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`identifier` is the provider; `__type` in JSON mode is the same string.
|
|
352
|
+
|
|
353
|
+
If a channel is not in `integrations:list`, the user cannot post to it. For
|
|
354
|
+
Google Business Profile and LinkedIn company pages that is the expected state
|
|
355
|
+
until those platforms approve the app (personal LinkedIn profiles work).
|
|
356
|
+
Pinterest is live as a BETA channel. Say so rather than scheduling a post that will fail.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Validating before you publish
|
|
361
|
+
|
|
362
|
+
Post as a draft first. A draft goes through the same validation as a scheduled
|
|
363
|
+
post, so a bad settings payload fails immediately instead of at publish time:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
postemi posts:create -c "…" -s "2026-12-31T12:00:00Z" -t draft --settings '…' -i "$ID"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Then promote it, or delete it:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
postemi posts:status <post-id> --status schedule
|
|
373
|
+
postemi posts:delete <post-id>
|
|
374
|
+
```
|
package/README.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Postemi CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Social media scheduling from the command line, or from your AI agent.**
|
|
4
|
+
|
|
5
|
+
Postemi (https://postemi.com) is a social media scheduler, and this is its
|
|
6
|
+
command-line tool. It uses the same public API as the web app: what you
|
|
7
|
+
schedule here shows up in your Postemi calendar, and what you schedule there
|
|
8
|
+
you can see here too.
|
|
9
|
+
|
|
10
|
+
Channels: 20 platforms, including Facebook, Instagram, X, LinkedIn, TikTok, YouTube, Threads, Bluesky, Pinterest and Mastodon.
|
|
11
|
+
|
|
12
|
+
[`SKILL.md`](./SKILL.md) is the full command reference, written for AI agents.
|
|
13
|
+
Show it to Claude, ChatGPT, Codex, OpenClaw or Hermes and they can use the
|
|
14
|
+
whole CLI with no further explanation.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g postemi
|
|
20
|
+
postemi --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Node.js 18 or newer.
|
|
24
|
+
|
|
25
|
+
## Sign in
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
postemi auth:login # device flow: opens postemi.com/app/device in the browser
|
|
29
|
+
postemi auth:status # checks the credential
|
|
30
|
+
postemi auth:logout # deletes the stored credential
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You approve the login in the browser while signed in to Postemi, and choose
|
|
34
|
+
there which workspace and which channels the key may reach. The CLI stores it
|
|
35
|
+
in `~/.postemi/credentials.json` (mode `0600`).
|
|
36
|
+
|
|
37
|
+
No account yet? Create one from the terminal:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
postemi auth:signup --email anna@example.com # e-mails a code, asks for it and for the terms
|
|
41
|
+
postemi auth:signup --email anna@example.com --code 123456 --accept-terms --workspace-name "Anna's Cafe"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The person must agree to https://postemi.com/terms and
|
|
45
|
+
https://postemi.com/privacy first. There is no free trial: next come
|
|
46
|
+
`postemi billing:plans` and `postemi billing:subscribe`. You can also sign up
|
|
47
|
+
at https://postemi.com/app/signup and then run `postemi auth:login`.
|
|
48
|
+
|
|
49
|
+
For a server, CI or an agent without a browser, use an API key from
|
|
50
|
+
Settings, Developers in the web app:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export POSTEMI_API_KEY=your_api_key
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Variable | Default | Purpose |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `POSTEMI_API_KEY` | none | API key, when you do not use `auth:login` |
|
|
59
|
+
| `POSTEMI_API_URL` | `https://api.postemi.com` | API base |
|
|
60
|
+
| `POSTEMI_WORKSPACE` | none | Workspace id for every command (same as `--workspace`) |
|
|
61
|
+
| `POSTEMI_TIMEZONE` | none | IANA zone for dates written without an offset |
|
|
62
|
+
| `POSTEMI_AUTH_SERVER` | `https://api.postemi.com` | Where `auth:login` runs the device flow |
|
|
63
|
+
| `POSTEMI_CLIENT_NAME` | `Postemi CLI` | Name shown on the approval page |
|
|
64
|
+
|
|
65
|
+
Stored credentials from `auth:login` take precedence over `POSTEMI_API_KEY`.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
postemi auth:status
|
|
71
|
+
postemi integrations:list # your channels and their ids
|
|
72
|
+
postemi config:set timezone Europe/London # once: how to read dates without an offset
|
|
73
|
+
|
|
74
|
+
IMG=$(postemi upload ./photo.jpg | jq -r '.path')
|
|
75
|
+
postemi posts:create -c "Hello from the terminal" -m "$IMG" \
|
|
76
|
+
-s "2026-12-31 09:00" -i "<integration-id>"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Results are JSON on stdout; status lines and errors go to stderr, and every
|
|
80
|
+
failure exits 1. Run `postemi <command> --help` for each command.
|
|
81
|
+
|
|
82
|
+
## More
|
|
83
|
+
|
|
84
|
+
- [`SKILL.md`](./SKILL.md): every command, flag and rule
|
|
85
|
+
- [`PROVIDER_SETTINGS.md`](./PROVIDER_SETTINGS.md): per-channel settings
|
|
86
|
+
- [`SUPPORTED_FILE_TYPES.md`](./SUPPORTED_FILE_TYPES.md): the eight accepted media types
|
|
87
|
+
- [`examples/`](./examples): ready `--json` payloads
|
|
88
|
+
- MCP server for chat clients: `https://mcp.postemi.com/mcp`
|
|
89
|
+
- Help: https://postemi.com/help, support: norbert@postemi.com
|
|
90
|
+
- Privacy: https://postemi.com/privacy, terms: https://postemi.com/terms
|
|
91
|
+
|
|
92
|
+
## Plugins for AI agents
|
|
93
|
+
|
|
94
|
+
This repository also holds the Postemi plugins, in [`plugins/`](./plugins):
|
|
95
|
+
|
|
96
|
+
| Folder | For | What it is |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| [`plugins/postemi-mcp`](./plugins/postemi-mcp) | Claude (chat, desktop, Code) | The hosted MCP server `https://mcp.postemi.com/mcp` plus a skill |
|
|
99
|
+
| [`plugins/postemi-cli`](./plugins/postemi-cli) | Claude Code | This CLI's skill; the agent runs `postemi` commands |
|
|
100
|
+
| [`plugins/postemi-chatgpt`](./plugins/postemi-chatgpt) | ChatGPT | The ChatGPT app package |
|
|
101
|
+
| [`plugins/postemi-cursor`](./plugins/postemi-cursor) | Cursor | MCP server, skill and commands |
|
|
102
|
+
| [`plugins/postemi-grok`](./plugins/postemi-grok) | Grok Build | MCP server, skill and commands |
|
|
103
|
+
|
|
104
|
+
In Claude Code:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
claude plugin marketplace add norbertlevente/postemi-agent
|
|
108
|
+
claude plugin install postemi@postemi-agent # MCP server
|
|
109
|
+
claude plugin install postemi-cli@postemi-agent # CLI skill
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The skill on its own (for OpenClaw and other agents) is
|
|
113
|
+
[`skills/postemi/SKILL.md`](./skills/postemi/SKILL.md).
|
|
114
|
+
|
|
115
|
+
## Development
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm build # dist/index.js
|
|
120
|
+
pnpm test # type check, build, and scripts/check.mjs (manifests, skill copies, no other-brand text)
|
|
121
|
+
node dist/index.js --help
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Release steps: [`PUBLISHING.md`](./PUBLISHING.md). Changes: [`CHANGELOG.md`](./CHANGELOG.md).
|
|
125
|
+
|
|
126
|
+
License: AGPL-3.0, see [`LICENSE`](./LICENSE).
|
|
127
|
+
Source: https://github.com/norbertlevente/postemi-agent
|