chirpie 1.0.45 → 1.0.46
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 +23 -3
- package/dist/index.js +71 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,11 +30,12 @@ export CHIRPIE_API_KEY=chirpie_sk_...
|
|
|
30
30
|
| Command | What it does |
|
|
31
31
|
|---------|--------------|
|
|
32
32
|
| `chirpie login` / `logout` / `auth` / `whoami` | Manage credentials |
|
|
33
|
-
| `chirpie post <text>` | Create a post. `-a` account (repeat it for several accounts), `-m` media files or URLs, `--alt` descriptions, `--config` per-account overrides, `-s` schedule |
|
|
34
|
-
| `chirpie thread <post> <post> ...` | Create a 2 to 25 post thread
|
|
33
|
+
| `chirpie post <text>` | Create a post. `-a` account (repeat it for several accounts), `-m` media files or URLs, `--alt` descriptions, `--config` per-account overrides, `-s` schedule, `--draft` save without sending |
|
|
34
|
+
| `chirpie thread <post> <post> ...` | Create a 2 to 25 post thread, or 1 to 25 with `--draft`. `-a` is repeatable, `--config` takes per-account posts, `-f` takes a JSON payload file |
|
|
35
35
|
| `chirpie posts` | List posts. `--status`, `--account`, `--group`, `--limit` |
|
|
36
36
|
| `chirpie posts get <id>` | Fetch one post |
|
|
37
|
-
| `chirpie posts update <id>` | Edit a post that has not published yet. `--text`, `--media`, `--alt`, `--clear-media`, `--schedule-at`. Leaving `--schedule-at` out keeps the time it already has |
|
|
37
|
+
| `chirpie posts update <id>` | Edit a post that has not published yet, or finish a draft. `--text`, `--media`, `--alt`, `--clear-media`, `--schedule-at`, `--keep-draft`. Leaving `--schedule-at` out keeps the time it already has |
|
|
38
|
+
| `chirpie posts publish <id>` | Send a saved draft now. `-t` sets the final text first |
|
|
38
39
|
| `chirpie posts delete <id>` | Delete a post. Deleting any post of a scheduled thread cancels the thread |
|
|
39
40
|
| `chirpie accounts` | List connected accounts, active and inactive |
|
|
40
41
|
| `chirpie accounts activate <id>` / `deactivate <id>` | Choose which accounts publish. Deactivating cancels that account's scheduled posts |
|
|
@@ -70,6 +71,25 @@ chirpie thread "One" "Two" "Three" -a 550e8400-e29b-41d4-a716-446655440000
|
|
|
70
71
|
chirpie posts --status scheduled
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
## Drafts
|
|
75
|
+
|
|
76
|
+
`--draft` saves a post or a thread without sending it. Nothing reaches the platform and nothing counts against your quota until you promote it. Anything that would go wrong is printed back, one warning per line.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
chirpie post "Half an idea" --draft
|
|
80
|
+
chirpie thread "Opening line" --draft # a draft thread may be one post
|
|
81
|
+
chirpie posts --status draft # what is saved
|
|
82
|
+
|
|
83
|
+
# Change only the time it remembers
|
|
84
|
+
chirpie posts update "$POST_ID" --schedule-at 2027-04-02T09:00:00Z --keep-draft
|
|
85
|
+
|
|
86
|
+
# Promote it: queue it, or send it now
|
|
87
|
+
chirpie posts update "$POST_ID" --schedule-at 2027-04-02T09:00:00Z
|
|
88
|
+
chirpie posts publish "$POST_ID"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Promoting runs every rule a normal post runs and takes the quota, so a draft that would be refused stays a draft, unchanged. A draft thread is promoted whole.
|
|
92
|
+
|
|
73
93
|
## Several accounts at once
|
|
74
94
|
|
|
75
95
|
Repeat `-a` to publish the same post to up to 25 accounts in one call.
|
package/dist/index.js
CHANGED
|
@@ -227,6 +227,14 @@ function parseAccountConfigurations(value) {
|
|
|
227
227
|
}
|
|
228
228
|
return parsed;
|
|
229
229
|
}
|
|
230
|
+
function printDraftWarnings(warnings) {
|
|
231
|
+
if (!Array.isArray(warnings)) return;
|
|
232
|
+
for (const warning of warnings) {
|
|
233
|
+
const entry = warning;
|
|
234
|
+
if (typeof entry?.message !== "string") continue;
|
|
235
|
+
info(`${entry.platform} ${entry.account_id}: ${entry.message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
230
238
|
function printFanOut(groupId, lines) {
|
|
231
239
|
const failed = lines.filter((line) => !line.success);
|
|
232
240
|
console.log(`Group ${groupId}`);
|
|
@@ -257,7 +265,10 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
257
265
|
).option("-m, --media <files...>", MEDIA_OPTION_HELP).option("--alt <text...>", ALT_OPTION_HELP).option(
|
|
258
266
|
"--config <json-or-file>",
|
|
259
267
|
'Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "text": "...", "media_urls": ["..."] } }. Every key must be an account named with --account'
|
|
260
|
-
).option("-s, --schedule <datetime>", "Schedule for later. Any ISO 8601 timestamp with a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00). Posts publish within ~5 minutes of the scheduled time.").option(
|
|
268
|
+
).option("-s, --schedule <datetime>", "Schedule for later. Any ISO 8601 timestamp with a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00). Posts publish within ~5 minutes of the scheduled time.").option(
|
|
269
|
+
"--draft",
|
|
270
|
+
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
271
|
+
).option("--json", "Output raw JSON").action(async (text, opts) => {
|
|
261
272
|
const client = getClient();
|
|
262
273
|
const named = opts.account ?? [];
|
|
263
274
|
if (opts.config && named.length === 0) {
|
|
@@ -278,13 +289,15 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
278
289
|
opts.config
|
|
279
290
|
)
|
|
280
291
|
} : {},
|
|
281
|
-
schedule_at: opts.schedule
|
|
292
|
+
schedule_at: opts.schedule,
|
|
293
|
+
...opts.draft ? { draft: true } : {}
|
|
282
294
|
});
|
|
283
295
|
if (opts.json) {
|
|
284
296
|
json(result);
|
|
285
297
|
if (result.results.some((r) => !r.success)) process.exit(1);
|
|
286
298
|
return;
|
|
287
299
|
}
|
|
300
|
+
if ("warnings" in result) printDraftWarnings(result.warnings);
|
|
288
301
|
const failed = printFanOut(
|
|
289
302
|
result.group_id,
|
|
290
303
|
result.results.map((r) => ({
|
|
@@ -304,10 +317,14 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
304
317
|
account_id: accountIds[0],
|
|
305
318
|
text,
|
|
306
319
|
...media ? { media } : {},
|
|
307
|
-
schedule_at: opts.schedule
|
|
320
|
+
schedule_at: opts.schedule,
|
|
321
|
+
...opts.draft ? { draft: true } : {}
|
|
308
322
|
});
|
|
309
323
|
if (opts.json) {
|
|
310
324
|
json(post);
|
|
325
|
+
} else if (post.status === "draft") {
|
|
326
|
+
success(`Draft saved (${post.id})`);
|
|
327
|
+
if ("warnings" in post) printDraftWarnings(post.warnings);
|
|
311
328
|
} else if (post.status === "scheduled") {
|
|
312
329
|
success(`Post scheduled for ${post.scheduled_at} (${post.id})`);
|
|
313
330
|
} else {
|
|
@@ -353,7 +370,10 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
353
370
|
).option(
|
|
354
371
|
"--config <json-or-file>",
|
|
355
372
|
`Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "posts": [{ "text": "one" }, { "text": "two" }] } }. An account's posts replace the whole thread for that account`
|
|
356
|
-
).option("-s, --schedule <datetime>", "Schedule for later. Any ISO 8601 timestamp with a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00). Threads publish within ~5 minutes of the scheduled time.").option(
|
|
373
|
+
).option("-s, --schedule <datetime>", "Schedule for later. Any ISO 8601 timestamp with a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00). Threads publish within ~5 minutes of the scheduled time.").option(
|
|
374
|
+
"--draft",
|
|
375
|
+
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
376
|
+
).option("--json", "Output raw JSON").action(async (posts, opts) => {
|
|
357
377
|
const client = getClient();
|
|
358
378
|
let postPayload;
|
|
359
379
|
let scheduleAt = opts.schedule;
|
|
@@ -375,8 +395,10 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
375
395
|
process.exit(1);
|
|
376
396
|
}
|
|
377
397
|
} else {
|
|
378
|
-
if (posts.length < 2) {
|
|
379
|
-
error(
|
|
398
|
+
if (posts.length < (opts.draft ? 1 : 2)) {
|
|
399
|
+
error(
|
|
400
|
+
opts.draft ? "Pass at least one post (or use --file)" : "Thread must have at least 2 posts (or use --file)"
|
|
401
|
+
);
|
|
380
402
|
process.exit(1);
|
|
381
403
|
}
|
|
382
404
|
postPayload = posts.map((text) => ({ text }));
|
|
@@ -426,13 +448,15 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
426
448
|
account_ids: accountIds,
|
|
427
449
|
posts: postPayload,
|
|
428
450
|
...configurations ? { account_configurations: configurations } : {},
|
|
429
|
-
schedule_at: scheduleAt
|
|
451
|
+
schedule_at: scheduleAt,
|
|
452
|
+
...opts.draft ? { draft: true } : {}
|
|
430
453
|
});
|
|
431
454
|
if (opts.json) {
|
|
432
455
|
json(result);
|
|
433
456
|
if (result.results.some((r) => !r.success)) process.exit(1);
|
|
434
457
|
return;
|
|
435
458
|
}
|
|
459
|
+
if ("warnings" in result) printDraftWarnings(result.warnings);
|
|
436
460
|
const failed = printFanOut(
|
|
437
461
|
result.group_id,
|
|
438
462
|
result.results.map((r) => ({
|
|
@@ -451,10 +475,14 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
451
475
|
const thread = await client.createThread({
|
|
452
476
|
account_id: accountIds[0],
|
|
453
477
|
posts: postPayload,
|
|
454
|
-
schedule_at: scheduleAt
|
|
478
|
+
schedule_at: scheduleAt,
|
|
479
|
+
...opts.draft ? { draft: true } : {}
|
|
455
480
|
});
|
|
456
481
|
if (opts.json) {
|
|
457
482
|
json(thread);
|
|
483
|
+
} else if (thread.status === "draft") {
|
|
484
|
+
success(`Draft thread saved with ${thread.posts.length} posts (${thread.id})`);
|
|
485
|
+
if ("warnings" in thread) printDraftWarnings(thread.warnings);
|
|
458
486
|
} else {
|
|
459
487
|
success(`Thread created with ${thread.posts.length} posts (${thread.id})`);
|
|
460
488
|
}
|
|
@@ -475,7 +503,10 @@ function wantsJson(opts, cmd) {
|
|
|
475
503
|
}
|
|
476
504
|
|
|
477
505
|
// src/commands/posts.ts
|
|
478
|
-
var postsCommand = new Command5("posts").description("List, get, update, or delete posts").option(
|
|
506
|
+
var postsCommand = new Command5("posts").description("List, get, update, or delete posts").option(
|
|
507
|
+
"--status <status>",
|
|
508
|
+
"Filter by status: draft, scheduled, publishing, published, failed, deleted"
|
|
509
|
+
).option("--account <id>", "Filter by account").option(
|
|
479
510
|
"--group <id>",
|
|
480
511
|
"Only the posts of one fan-out, from the group ID a multi-account post returned"
|
|
481
512
|
).option("--limit <n>", "Limit results", "20").option("--json", "Output raw JSON").action(async (opts, cmd) => {
|
|
@@ -532,20 +563,25 @@ postsCommand.command("get <id>").description("Get a post by ID").option("--json"
|
|
|
532
563
|
}
|
|
533
564
|
});
|
|
534
565
|
postsCommand.command("update <id>").description(
|
|
535
|
-
"Edit a post that has not published yet. Leaving --schedule-at out keeps the time
|
|
566
|
+
"Edit a post that has not published yet, or finish a draft. Leaving --schedule-at out keeps the time a queued post already has"
|
|
536
567
|
).option("-t, --text <text>", "Replacement post text").option(
|
|
537
568
|
"-m, --media <files...>",
|
|
538
569
|
"Replacement image or video files on this machine, or public URLs, following the same per-platform rules as creating a post. Pass --clear-media to remove the media instead."
|
|
539
570
|
).option("--alt <text...>", ALT_OPTION_HELP).option("--clear-media", "Remove the media from the post").option(
|
|
540
571
|
"-s, --schedule-at <datetime>",
|
|
541
|
-
"New publish time. Any ISO 8601 timestamp with a timezone (2027-04-01T14:00:00Z or 2027-04-01T16:00:00+02:00). Omit it to keep the time the post already has. Rescheduling one part of a thread moves the whole thread."
|
|
572
|
+
"New publish time. Any ISO 8601 timestamp with a timezone (2027-04-01T14:00:00Z or 2027-04-01T16:00:00+02:00). Omit it to keep the time the post already has. Rescheduling one part of a thread moves the whole thread. On a draft this queues it, unless you also pass --keep-draft."
|
|
573
|
+
).option(
|
|
574
|
+
"--keep-draft",
|
|
575
|
+
"Keep a draft a draft, so --schedule-at only changes the time it remembers"
|
|
542
576
|
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
543
577
|
if (opts.media && opts.clearMedia) {
|
|
544
578
|
error("Pass either --media or --clear-media, not both");
|
|
545
579
|
process.exit(1);
|
|
546
580
|
}
|
|
547
|
-
if (!opts.text && !opts.media && !opts.clearMedia && !opts.scheduleAt) {
|
|
548
|
-
error(
|
|
581
|
+
if (!opts.text && !opts.media && !opts.clearMedia && !opts.scheduleAt && !opts.keepDraft) {
|
|
582
|
+
error(
|
|
583
|
+
"Nothing to change. Pass --text, --media, --clear-media, --schedule-at or --keep-draft"
|
|
584
|
+
);
|
|
549
585
|
process.exit(1);
|
|
550
586
|
}
|
|
551
587
|
const client = getClient();
|
|
@@ -555,24 +591,44 @@ postsCommand.command("update <id>").description(
|
|
|
555
591
|
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
556
592
|
...opts.clearMedia ? { media: [] } : {},
|
|
557
593
|
...media ? { media } : {},
|
|
558
|
-
...opts.scheduleAt ? { schedule_at: opts.scheduleAt } : {}
|
|
594
|
+
...opts.scheduleAt ? { schedule_at: opts.scheduleAt } : {},
|
|
595
|
+
...opts.keepDraft ? { draft: true } : {}
|
|
559
596
|
});
|
|
560
597
|
if (wantsJson(opts, cmd)) {
|
|
561
598
|
json(post);
|
|
562
599
|
} else {
|
|
563
600
|
const moved = post.rescheduled_post_ids ?? [];
|
|
564
|
-
|
|
601
|
+
const promoted = post.promoted_from_draft_id ?? post.promoted_from_draft_ids?.[0];
|
|
602
|
+
success(promoted ? `Draft ${promoted} is now ${post.status}` : `Post ${post.id} updated`);
|
|
565
603
|
if (opts.scheduleAt) {
|
|
566
604
|
success(
|
|
567
605
|
moved.length > 1 ? `Thread rescheduled for ${post.scheduled_at} (${moved.length} posts)` : `Scheduled for ${post.scheduled_at}`
|
|
568
606
|
);
|
|
569
607
|
}
|
|
608
|
+
if (post.warnings?.length) printDraftWarnings(post.warnings);
|
|
570
609
|
}
|
|
571
610
|
} catch (err) {
|
|
572
611
|
apiError(err, "Failed to update post");
|
|
573
612
|
process.exit(1);
|
|
574
613
|
}
|
|
575
614
|
});
|
|
615
|
+
postsCommand.command("publish <id>").description("Publish a draft now. Every rule and the monthly quota apply").option("-t, --text <text>", "Final post text, saved before publishing").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
616
|
+
const client = getClient();
|
|
617
|
+
try {
|
|
618
|
+
const post = await client.updatePost(id, {
|
|
619
|
+
publish: true,
|
|
620
|
+
...opts.text !== void 0 ? { text: opts.text } : {}
|
|
621
|
+
});
|
|
622
|
+
if (wantsJson(opts, cmd)) {
|
|
623
|
+
json(post);
|
|
624
|
+
} else {
|
|
625
|
+
success(`Draft ${id} published (${post.id})`);
|
|
626
|
+
}
|
|
627
|
+
} catch (err) {
|
|
628
|
+
apiError(err, "Failed to publish draft");
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
576
632
|
postsCommand.command("delete <id>").description(
|
|
577
633
|
"Delete a post (deleting any post of a scheduled thread cancels the thread)"
|
|
578
634
|
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chirpie",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
4
4
|
"description": "Chirpie CLI: post to social media from the command line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
],
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chirpie/sdk": "^1.0.
|
|
47
|
+
"@chirpie/sdk": "^1.0.43",
|
|
48
48
|
"commander": "^13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|