posthive-cli 0.1.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.
- package/README.md +81 -0
- package/dist/index.js +283 -0
- package/package.json +51 -0
- package/skills/posthive/SKILL.md +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# posthive-cli
|
|
2
|
+
|
|
3
|
+
Posthive CLI — schedule posts to 11 social platforms from the command line or any AI agent that can run shell commands (Claude Code, OpenClaw, Cursor, custom pipelines).
|
|
4
|
+
|
|
5
|
+
**Supported platforms:** Bluesky, Threads, Instagram, LinkedIn, Mastodon, YouTube, Facebook Pages, Pinterest, Telegram, X (Twitter), Nostr
|
|
6
|
+
|
|
7
|
+
Every command outputs structured JSON, so LLMs and scripts can parse results directly.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g posthive-cli
|
|
15
|
+
# or run without installing
|
|
16
|
+
npx posthive-cli help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
You need a Posthive account and an API key ([posthive.co](https://posthive.co) → Settings → API Keys, Pro/Team plan).
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export POSTHIVE_API_KEY=ph_your_key_here
|
|
25
|
+
export POSTHIVE_API_URL=https://api.posthive.co # or your self-hosted URL
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# List connected accounts (start here — you need account IDs)
|
|
32
|
+
posthive accounts:list
|
|
33
|
+
|
|
34
|
+
# Create a draft post (default: drafts require approval before publishing)
|
|
35
|
+
posthive posts:create --content "Hello world" --accounts acc_1,acc_2
|
|
36
|
+
|
|
37
|
+
# Schedule directly
|
|
38
|
+
posthive posts:create --content "Launch day!" --accounts acc_1 --schedule 2026-07-10T09:00:00Z
|
|
39
|
+
|
|
40
|
+
# First comment automation
|
|
41
|
+
posthive posts:create --content "We shipped something big" --accounts acc_1 \
|
|
42
|
+
--first-comment "Full details: https://example.com"
|
|
43
|
+
|
|
44
|
+
# Upload media, then attach it
|
|
45
|
+
posthive upload ./screenshot.png
|
|
46
|
+
posthive posts:create --content "Sneak peek" --accounts acc_1 --media <returned_url>
|
|
47
|
+
|
|
48
|
+
# Manage the queue
|
|
49
|
+
posthive posts:list --status draft
|
|
50
|
+
posthive posts:get <post_id>
|
|
51
|
+
posthive posts:update <post_id> --content "Fixed typo"
|
|
52
|
+
posthive posts:approve <post_id> --schedule 2026-07-10T09:00:00Z
|
|
53
|
+
posthive posts:duplicate <post_id>
|
|
54
|
+
posthive posts:delete <post_id>
|
|
55
|
+
|
|
56
|
+
# Templates
|
|
57
|
+
posthive templates:list
|
|
58
|
+
posthive templates:use <template_id> --accounts acc_1
|
|
59
|
+
|
|
60
|
+
# Full command reference
|
|
61
|
+
posthive help
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Use with AI agents
|
|
65
|
+
|
|
66
|
+
The package ships with a [skill](./skills/posthive/SKILL.md) that teaches agents the full command set, platform character limits, and draft-first workflow. Works with Claude Code, OpenClaw, and any agent framework that supports skills or shell execution.
|
|
67
|
+
|
|
68
|
+
Posts created by agents default to **drafts** — nothing publishes without approval unless `--schedule` is passed explicitly. Human in the loop by design.
|
|
69
|
+
|
|
70
|
+
Prefer MCP? Use [`posthive-mcp`](https://www.npmjs.com/package/posthive-mcp) for Claude Desktop, Cursor, and other MCP clients.
|
|
71
|
+
|
|
72
|
+
## Links
|
|
73
|
+
|
|
74
|
+
- [Posthive](https://posthive.co)
|
|
75
|
+
- [Documentation](https://posthive.co/docs)
|
|
76
|
+
- [For Developers](https://posthive.co/for-developers)
|
|
77
|
+
- [GitHub](https://github.com/AstaBlackClove/posthive)
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
AGPL-3.0
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Posthive CLI
|
|
4
|
+
*
|
|
5
|
+
* Schedule posts to 11 social platforms from the command line or any
|
|
6
|
+
* AI agent that can run shell commands (Claude Code, OpenClaw, Cursor).
|
|
7
|
+
*
|
|
8
|
+
* Every command outputs structured JSON for easy parsing by LLMs and scripts.
|
|
9
|
+
*
|
|
10
|
+
* Required env vars:
|
|
11
|
+
* POSTHIVE_API_KEY — API key from Posthive Settings → API Keys
|
|
12
|
+
* POSTHIVE_API_URL — Base URL of the Posthive API (default: https://api.posthive.co)
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { basename } from "node:path";
|
|
16
|
+
const API_KEY = process.env.POSTHIVE_API_KEY;
|
|
17
|
+
const API_URL = (process.env.POSTHIVE_API_URL ?? "https://api.posthive.co").replace(/\/$/, "");
|
|
18
|
+
// ─── Output helpers ──────────────────────────────────────────────────────────
|
|
19
|
+
function out(data) {
|
|
20
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
function fail(message, code = 1) {
|
|
24
|
+
process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
|
|
25
|
+
process.exit(code);
|
|
26
|
+
}
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const [command = "help", ...rest] = argv;
|
|
29
|
+
const positional = [];
|
|
30
|
+
const flags = {};
|
|
31
|
+
for (let i = 0; i < rest.length; i++) {
|
|
32
|
+
const arg = rest[i];
|
|
33
|
+
if (arg.startsWith("--")) {
|
|
34
|
+
const key = arg.slice(2);
|
|
35
|
+
const next = rest[i + 1];
|
|
36
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
37
|
+
flags[key] = next;
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
flags[key] = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
positional.push(arg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { command, positional, flags };
|
|
49
|
+
}
|
|
50
|
+
function str(flags, key) {
|
|
51
|
+
const v = flags[key];
|
|
52
|
+
return typeof v === "string" ? v : undefined;
|
|
53
|
+
}
|
|
54
|
+
function list(flags, key) {
|
|
55
|
+
const v = str(flags, key);
|
|
56
|
+
return v ? v.split(",").map(s => s.trim()).filter(Boolean) : undefined;
|
|
57
|
+
}
|
|
58
|
+
// ─── HTTP helper ─────────────────────────────────────────────────────────────
|
|
59
|
+
async function api(method, path, body) {
|
|
60
|
+
let res;
|
|
61
|
+
try {
|
|
62
|
+
res = await fetch(`${API_URL}${path}`, {
|
|
63
|
+
method,
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
66
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
67
|
+
},
|
|
68
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
fail(`Cannot reach Posthive API at ${API_URL} — check POSTHIVE_API_URL and your network. (${err instanceof Error ? err.message : String(err)})`);
|
|
73
|
+
}
|
|
74
|
+
const text = await res.text();
|
|
75
|
+
let json;
|
|
76
|
+
try {
|
|
77
|
+
json = JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
json = { raw: text };
|
|
81
|
+
}
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const rec = json;
|
|
84
|
+
const msg = rec?.error ?? rec?.message ?? text;
|
|
85
|
+
fail(`API ${res.status}: ${typeof msg === "string" ? msg : JSON.stringify(msg)}`);
|
|
86
|
+
}
|
|
87
|
+
return json;
|
|
88
|
+
}
|
|
89
|
+
// ─── Help text ───────────────────────────────────────────────────────────────
|
|
90
|
+
const HELP = {
|
|
91
|
+
usage: "posthive <command> [args] [--flags]",
|
|
92
|
+
env: {
|
|
93
|
+
POSTHIVE_API_KEY: "required — API key from Posthive Settings → API Keys",
|
|
94
|
+
POSTHIVE_API_URL: "optional — API base URL (default: https://api.posthive.co)",
|
|
95
|
+
},
|
|
96
|
+
commands: {
|
|
97
|
+
"accounts:list": "List connected social accounts and their IDs",
|
|
98
|
+
"posts:create": "Create a post. --content <text> --accounts <id,id> [--schedule <ISO>] [--first-comment <text>] [--media <url,url>] [--media-type post|reel|story] [--youtube-type short|video] [--dry-run]. Saved as DRAFT unless --schedule is given.",
|
|
99
|
+
"posts:list": "List posts. [--status pending|draft|done|failed] [--limit <n>]",
|
|
100
|
+
"posts:get": "Get post details. posts:get <post_id>",
|
|
101
|
+
"posts:update": "Update a pending/draft post. posts:update <post_id> [--content <text>] [--schedule <ISO>] [--first-comment <text>]",
|
|
102
|
+
"posts:approve": "Approve a draft and schedule it. posts:approve <post_id> --schedule <ISO>",
|
|
103
|
+
"posts:duplicate": "Clone a post as a new draft. posts:duplicate <post_id>",
|
|
104
|
+
"posts:delete": "Delete a pending/draft post. posts:delete <post_id>",
|
|
105
|
+
"templates:list": "List saved post templates",
|
|
106
|
+
"templates:use": "Create a post from a template. templates:use <template_id> --accounts <id,id> [--content <override>] [--schedule <ISO>]",
|
|
107
|
+
"upload": "Upload an image or video. upload <file_path> — returns a media URL for posts:create --media",
|
|
108
|
+
"help": "Show this help",
|
|
109
|
+
},
|
|
110
|
+
notes: [
|
|
111
|
+
"All output is JSON.",
|
|
112
|
+
"Posts default to DRAFT status — review them in Posthive → Posts, or pass --schedule to schedule directly.",
|
|
113
|
+
"Get account IDs from accounts:list before creating posts.",
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
117
|
+
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
118
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
119
|
+
out(HELP);
|
|
120
|
+
}
|
|
121
|
+
if (!API_KEY) {
|
|
122
|
+
fail("POSTHIVE_API_KEY environment variable is required. Get a key at Posthive → Settings → API Keys.");
|
|
123
|
+
}
|
|
124
|
+
switch (command) {
|
|
125
|
+
case "accounts:list": {
|
|
126
|
+
out(await api("GET", "/api/v1/accounts"));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "posts:create": {
|
|
130
|
+
const content = str(flags, "content");
|
|
131
|
+
const accounts = list(flags, "accounts");
|
|
132
|
+
if (!content)
|
|
133
|
+
fail("--content is required");
|
|
134
|
+
if (!accounts?.length)
|
|
135
|
+
fail("--accounts is required (comma-separated account IDs; see accounts:list)");
|
|
136
|
+
const schedule = str(flags, "schedule");
|
|
137
|
+
const body = {
|
|
138
|
+
content,
|
|
139
|
+
accountIds: accounts,
|
|
140
|
+
draft: !schedule,
|
|
141
|
+
...(schedule ? { scheduledFor: schedule } : {}),
|
|
142
|
+
...(list(flags, "media") ? { images: list(flags, "media") } : {}),
|
|
143
|
+
...(str(flags, "media-type") ? { mediaType: str(flags, "media-type") } : {}),
|
|
144
|
+
...(str(flags, "youtube-type") ? { youtubeType: str(flags, "youtube-type") } : {}),
|
|
145
|
+
...(str(flags, "first-comment") ? { commentText: str(flags, "first-comment") } : {}),
|
|
146
|
+
...(flags["dry-run"] === true ? { dryRun: true } : {}),
|
|
147
|
+
};
|
|
148
|
+
const data = await api("POST", "/api/v1/posts", body);
|
|
149
|
+
out({
|
|
150
|
+
...data,
|
|
151
|
+
_note: schedule
|
|
152
|
+
? "Post scheduled — will publish at the given time."
|
|
153
|
+
: "Post saved as DRAFT. Approve with posts:approve or review in Posthive → Posts.",
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "posts:list": {
|
|
158
|
+
const params = new URLSearchParams();
|
|
159
|
+
const status = str(flags, "status");
|
|
160
|
+
const limit = str(flags, "limit");
|
|
161
|
+
if (status)
|
|
162
|
+
params.set("status", status);
|
|
163
|
+
if (limit)
|
|
164
|
+
params.set("limit", limit);
|
|
165
|
+
const qs = params.size ? `?${params}` : "";
|
|
166
|
+
out(await api("GET", `/api/v1/posts${qs}`));
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "posts:get": {
|
|
170
|
+
const id = positional[0];
|
|
171
|
+
if (!id)
|
|
172
|
+
fail("Usage: posthive posts:get <post_id>");
|
|
173
|
+
out(await api("GET", `/api/v1/posts/${encodeURIComponent(id)}`));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "posts:update": {
|
|
177
|
+
const id = positional[0];
|
|
178
|
+
if (!id)
|
|
179
|
+
fail("Usage: posthive posts:update <post_id> [--content] [--schedule] [--first-comment]");
|
|
180
|
+
const body = {
|
|
181
|
+
...(str(flags, "content") !== undefined ? { content: str(flags, "content") } : {}),
|
|
182
|
+
...(str(flags, "schedule") !== undefined ? { scheduledFor: str(flags, "schedule") } : {}),
|
|
183
|
+
...(str(flags, "first-comment") !== undefined ? { commentText: str(flags, "first-comment") } : {}),
|
|
184
|
+
};
|
|
185
|
+
if (Object.keys(body).length === 0) {
|
|
186
|
+
fail("Provide at least one of --content, --schedule, --first-comment");
|
|
187
|
+
}
|
|
188
|
+
out(await api("PATCH", `/api/v1/posts/${encodeURIComponent(id)}`, body));
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "posts:approve": {
|
|
192
|
+
const id = positional[0];
|
|
193
|
+
const schedule = str(flags, "schedule");
|
|
194
|
+
if (!id)
|
|
195
|
+
fail("Usage: posthive posts:approve <post_id> --schedule <ISO datetime>");
|
|
196
|
+
if (!schedule)
|
|
197
|
+
fail("--schedule is required (ISO 8601 datetime, must be in the future)");
|
|
198
|
+
const data = await api("POST", `/api/v1/posts/${encodeURIComponent(id)}/approve`, { scheduledFor: schedule });
|
|
199
|
+
out({ ...data, _note: "Draft approved and scheduled." });
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "posts:duplicate": {
|
|
203
|
+
const id = positional[0];
|
|
204
|
+
if (!id)
|
|
205
|
+
fail("Usage: posthive posts:duplicate <post_id>");
|
|
206
|
+
const data = await api("POST", `/api/v1/posts/${encodeURIComponent(id)}/duplicate`);
|
|
207
|
+
out({ ...data, _note: "Duplicated as a new draft." });
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "posts:delete": {
|
|
211
|
+
const id = positional[0];
|
|
212
|
+
if (!id)
|
|
213
|
+
fail("Usage: posthive posts:delete <post_id>");
|
|
214
|
+
out(await api("DELETE", `/api/v1/posts/${encodeURIComponent(id)}`));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "templates:list": {
|
|
218
|
+
out(await api("GET", "/api/v1/templates"));
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "templates:use": {
|
|
222
|
+
const id = positional[0];
|
|
223
|
+
const accounts = list(flags, "accounts");
|
|
224
|
+
if (!id)
|
|
225
|
+
fail("Usage: posthive templates:use <template_id> --accounts <id,id>");
|
|
226
|
+
if (!accounts?.length)
|
|
227
|
+
fail("--accounts is required (comma-separated account IDs)");
|
|
228
|
+
const schedule = str(flags, "schedule");
|
|
229
|
+
const body = {
|
|
230
|
+
accountIds: accounts,
|
|
231
|
+
draft: !schedule,
|
|
232
|
+
...(schedule ? { scheduledFor: schedule } : {}),
|
|
233
|
+
...(str(flags, "content") ? { contentOverride: str(flags, "content") } : {}),
|
|
234
|
+
...(str(flags, "first-comment") ? { firstCommentOverride: str(flags, "first-comment") } : {}),
|
|
235
|
+
};
|
|
236
|
+
const data = await api("POST", `/api/v1/templates/${encodeURIComponent(id)}/use`, body);
|
|
237
|
+
out({
|
|
238
|
+
...data,
|
|
239
|
+
_note: schedule ? "Post created from template and scheduled." : "Post created from template as DRAFT.",
|
|
240
|
+
});
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "upload": {
|
|
244
|
+
const filePath = positional[0];
|
|
245
|
+
if (!filePath)
|
|
246
|
+
fail("Usage: posthive upload <file_path>");
|
|
247
|
+
let buf;
|
|
248
|
+
try {
|
|
249
|
+
buf = await readFile(filePath);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
fail(`Cannot read file: ${filePath}`);
|
|
253
|
+
}
|
|
254
|
+
const form = new FormData();
|
|
255
|
+
form.append("file", new Blob([new Uint8Array(buf)]), basename(filePath));
|
|
256
|
+
let res;
|
|
257
|
+
try {
|
|
258
|
+
res = await fetch(`${API_URL}/upload`, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
261
|
+
body: form,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
fail(`Cannot reach Posthive API at ${API_URL} — check POSTHIVE_API_URL and your network. (${err instanceof Error ? err.message : String(err)})`);
|
|
266
|
+
}
|
|
267
|
+
const text = await res.text();
|
|
268
|
+
let json;
|
|
269
|
+
try {
|
|
270
|
+
json = JSON.parse(text);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
json = { raw: text };
|
|
274
|
+
}
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
fail(`Upload failed (${res.status}): ${text}`);
|
|
277
|
+
}
|
|
278
|
+
out(json);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
default:
|
|
282
|
+
fail(`Unknown command: ${command}. Run "posthive help" for usage.`);
|
|
283
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "posthive-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Posthive CLI — schedule posts to 11 social platforms from the command line or any AI agent (Claude Code, OpenClaw, Cursor)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"posthive": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"skills"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"prepublishOnly": "tsc"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"posthive",
|
|
21
|
+
"cli",
|
|
22
|
+
"social-media",
|
|
23
|
+
"scheduler",
|
|
24
|
+
"ai-agent",
|
|
25
|
+
"skill",
|
|
26
|
+
"claude",
|
|
27
|
+
"openclaw",
|
|
28
|
+
"bluesky",
|
|
29
|
+
"threads",
|
|
30
|
+
"linkedin",
|
|
31
|
+
"instagram",
|
|
32
|
+
"mastodon"
|
|
33
|
+
],
|
|
34
|
+
"author": "Posthive",
|
|
35
|
+
"license": "AGPL-3.0",
|
|
36
|
+
"homepage": "https://posthive.co/for-developers",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/AstaBlackClove/posthive"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/AstaBlackClove/posthive/issues"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "^5.5.4",
|
|
46
|
+
"@types/node": "^20.14.12"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: posthive
|
|
3
|
+
description: Schedule social media posts to 11 platforms (Bluesky, Threads, Instagram, LinkedIn, Mastodon, YouTube, Facebook Pages, Pinterest, Telegram, X/Twitter, Nostr) via the Posthive CLI. Use when the user asks to create, schedule, list, update, or delete social media posts, or manage their posting queue.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Posthive — social media scheduling
|
|
7
|
+
|
|
8
|
+
Schedule posts to 11 social platforms from the command line. Every command outputs structured JSON.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
Requires two environment variables:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
export POSTHIVE_API_KEY=ph_xxx # Posthive → Settings → API Keys (Pro/Team plan)
|
|
16
|
+
export POSTHIVE_API_URL=https://api.posthive.co # or your self-hosted URL
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Run commands with `npx posthive-cli` or `posthive` if installed globally (`npm i -g posthive-cli`).
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
1. **Always start with `accounts:list`** to get valid account IDs — never guess IDs.
|
|
24
|
+
2. Create posts with `posts:create`. **Posts are saved as DRAFTS by default** — the user reviews them in Posthive before anything publishes. Only pass `--schedule` when the user explicitly asks to schedule directly.
|
|
25
|
+
3. Use `posts:approve` to promote a draft to the scheduled queue.
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# List connected accounts (do this first)
|
|
31
|
+
posthive accounts:list
|
|
32
|
+
|
|
33
|
+
# Create a draft post
|
|
34
|
+
posthive posts:create --content "Hello world" --accounts id1,id2
|
|
35
|
+
|
|
36
|
+
# Create with first comment (link/hashtags go here, not the caption)
|
|
37
|
+
posthive posts:create --content "Big launch today" --accounts id1 --first-comment "Details: https://example.com"
|
|
38
|
+
|
|
39
|
+
# Schedule directly (skips draft review — only when user explicitly asks)
|
|
40
|
+
posthive posts:create --content "Hello" --accounts id1 --schedule 2026-07-10T09:00:00Z
|
|
41
|
+
|
|
42
|
+
# With media (upload first, then reference the returned URL)
|
|
43
|
+
posthive upload ./image.png
|
|
44
|
+
posthive posts:create --content "Check this out" --accounts id1 --media https://...returned-url...
|
|
45
|
+
|
|
46
|
+
# Instagram Reel / Story (media required)
|
|
47
|
+
posthive posts:create --content "New reel" --accounts ig_id --media <video_url> --media-type reel
|
|
48
|
+
|
|
49
|
+
# YouTube Short
|
|
50
|
+
posthive posts:create --content "Title here" --accounts yt_id --media <video_url> --youtube-type short
|
|
51
|
+
|
|
52
|
+
# Queue management
|
|
53
|
+
posthive posts:list --status draft
|
|
54
|
+
posthive posts:get <post_id>
|
|
55
|
+
posthive posts:update <post_id> --content "Fixed typo"
|
|
56
|
+
posthive posts:approve <post_id> --schedule 2026-07-10T09:00:00Z
|
|
57
|
+
posthive posts:duplicate <post_id>
|
|
58
|
+
posthive posts:delete <post_id>
|
|
59
|
+
|
|
60
|
+
# Templates
|
|
61
|
+
posthive templates:list
|
|
62
|
+
posthive templates:use <template_id> --accounts id1 --content "Optional override"
|
|
63
|
+
|
|
64
|
+
# Dry run (full pipeline, no real API calls)
|
|
65
|
+
posthive posts:create --content "Test" --accounts id1 --dry-run
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Platform notes
|
|
69
|
+
|
|
70
|
+
| Platform | Char limit | Notes |
|
|
71
|
+
|----------|-----------|-------|
|
|
72
|
+
| Bluesky | 300 | |
|
|
73
|
+
| Threads | 500 | |
|
|
74
|
+
| Mastodon | 500 | |
|
|
75
|
+
| X (Twitter) | 280 | Pro/Team plan, no links |
|
|
76
|
+
| LinkedIn | 3,000 | Link in first comment performs better |
|
|
77
|
+
| Instagram | 2,200 | Image/video required; use --media-type for reel/story |
|
|
78
|
+
| YouTube | — | Video required; use --youtube-type short or video |
|
|
79
|
+
| Pinterest | 500 | Image required |
|
|
80
|
+
| Facebook Pages | 63,206 | |
|
|
81
|
+
| Telegram | 4,096 | |
|
|
82
|
+
| Nostr | — | |
|
|
83
|
+
|
|
84
|
+
When posting to multiple platforms at once, keep content within the smallest char limit of the selected platforms, or create separate posts per platform with tailored content.
|
|
85
|
+
|
|
86
|
+
## Best practices
|
|
87
|
+
|
|
88
|
+
- Keep a human in the loop: default to drafts, let the user approve.
|
|
89
|
+
- Timestamps are ISO 8601 UTC (e.g. `2026-07-10T09:00:00Z`). Confirm the user's timezone before scheduling.
|
|
90
|
+
- Put links and hashtag walls in `--first-comment` rather than the main content on LinkedIn and Instagram.
|
|
91
|
+
- On errors, the CLI exits non-zero and prints `{"error": "..."}` to stdout.
|