chirpie 1.0.46 → 1.0.48
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 +15 -5
- package/dist/index.js +84 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,13 +30,15 @@ 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, `--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
|
-
| `chirpie posts` | List posts. `--status`, `--account`, `--group`, `--limit` |
|
|
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, `--first-comment` a comment published under the post as soon as it goes out, `-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, `--first-comment` posts a comment under the last part, `-f` takes a JSON payload file |
|
|
35
|
+
| `chirpie posts` | List posts. `--status`, `--account`, `--group`, `--limit`, `--include-hidden` |
|
|
36
36
|
| `chirpie posts get <id>` | Fetch one post |
|
|
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 |
|
|
37
|
+
| `chirpie posts update <id>` | Edit a post that has not published yet, or finish a draft. `--text`, `--media`, `--alt`, `--clear-media`, `--first-comment` (`""` removes it), `--schedule-at`, `--keep-draft`. Leaving `--schedule-at` out keeps the time it already has |
|
|
38
38
|
| `chirpie posts publish <id>` | Send a saved draft now. `-t` sets the final text first |
|
|
39
|
-
| `chirpie posts
|
|
39
|
+
| `chirpie posts first-comment <id>` | Post a first comment that failed, again. It re-sends the text the post already carries |
|
|
40
|
+
| `chirpie posts delete <id>` | Take a post down from the platform. Chirpie keeps it, marked deleted. Deleting any post of a scheduled thread cancels the thread. Instagram and TikTok offer no delete API and refuse with `delete_unsupported` |
|
|
41
|
+
| `chirpie posts hide <id>` / `unhide <id>` | Hide a post from your Chirpie listings, or show it again. Nothing reaches the platform |
|
|
40
42
|
| `chirpie accounts` | List connected accounts, active and inactive |
|
|
41
43
|
| `chirpie accounts activate <id>` / `deactivate <id>` | Choose which accounts publish. Deactivating cancels that account's scheduled posts |
|
|
42
44
|
| `chirpie accounts disconnect <id>` | End a connection. Cancels that account's scheduled posts, frees a plan slot, and removes the stored credential, so reconnecting means authorizing again. Asks first; `-y` skips the prompt |
|
|
@@ -69,6 +71,14 @@ chirpie thread "One" "Two" "Three" -a 550e8400-e29b-41d4-a716-446655440000
|
|
|
69
71
|
|
|
70
72
|
# What is queued
|
|
71
73
|
chirpie posts --status scheduled
|
|
74
|
+
|
|
75
|
+
# Put the link in the first comment. X, Threads, Instagram and Facebook only,
|
|
76
|
+
# and it counts as one post against your monthly quota.
|
|
77
|
+
chirpie post "We rebuilt scheduling this week." \
|
|
78
|
+
--first-comment "Full write-up: https://example.com/blog/scheduling"
|
|
79
|
+
|
|
80
|
+
# A first comment never fails its post. Send one that did not go out again.
|
|
81
|
+
chirpie posts first-comment "$POST_ID"
|
|
72
82
|
```
|
|
73
83
|
|
|
74
84
|
## Drafts
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,22 @@ function apiError(err, fallback) {
|
|
|
31
31
|
const maybe = err;
|
|
32
32
|
if (typeof maybe?.code === "string" && typeof maybe?.message === "string") {
|
|
33
33
|
error(`${maybe.code}: ${maybe.message}`);
|
|
34
|
+
printThreadRollback(err);
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
error(err instanceof Error ? err.message : fallback);
|
|
37
38
|
}
|
|
39
|
+
function printThreadRollback(err) {
|
|
40
|
+
const rollback = err?.details?.thread_rollback;
|
|
41
|
+
const live = rollback?.still_live ?? [];
|
|
42
|
+
if (live.length === 0) return;
|
|
43
|
+
error("Still live on the platform, delete these yourself:");
|
|
44
|
+
for (const part of live) {
|
|
45
|
+
error(
|
|
46
|
+
` post ${part.thread_order + 1}: ${part.platform_post_url ?? part.platform_post_id}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
38
50
|
function info(msg) {
|
|
39
51
|
console.log(`\x1B[36m\u2139\x1B[0m ${msg}`);
|
|
40
52
|
}
|
|
@@ -262,9 +274,9 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
262
274
|
"-a, --account <id>",
|
|
263
275
|
"Account ID. Repeat it to publish the same post to several accounts at once (up to 25)",
|
|
264
276
|
collect
|
|
265
|
-
).option("-m, --media <files...>", MEDIA_OPTION_HELP).option("--alt <text...>", ALT_OPTION_HELP).option(
|
|
277
|
+
).option("-m, --media <files...>", MEDIA_OPTION_HELP).option("--first-comment <text>", "Post this as a comment under the post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota").option("--alt <text...>", ALT_OPTION_HELP).option(
|
|
266
278
|
"--config <json-or-file>",
|
|
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'
|
|
279
|
+
'Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "text": "...", "media_urls": ["..."], "first_comment": "..." } }. Every key must be an account named with --account, and "first_comment": "" publishes that account without the first comment the call is sending'
|
|
268
280
|
).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
281
|
"--draft",
|
|
270
282
|
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
@@ -284,6 +296,7 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
284
296
|
account_ids: accountIds,
|
|
285
297
|
text,
|
|
286
298
|
...media ? { media } : {},
|
|
299
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
287
300
|
...opts.config ? {
|
|
288
301
|
account_configurations: parseAccountConfigurations(
|
|
289
302
|
opts.config
|
|
@@ -317,6 +330,7 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
317
330
|
account_id: accountIds[0],
|
|
318
331
|
text,
|
|
319
332
|
...media ? { media } : {},
|
|
333
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
320
334
|
schedule_at: opts.schedule,
|
|
321
335
|
...opts.draft ? { draft: true } : {}
|
|
322
336
|
});
|
|
@@ -354,7 +368,7 @@ function parseAltSpec(spec) {
|
|
|
354
368
|
alts: m[2].split(",").map((a) => a.trim())
|
|
355
369
|
};
|
|
356
370
|
}
|
|
357
|
-
var threadCommand = new Command4("thread").description("Create a thread").argument("[posts...]", "Thread posts (each argument is one post). Omit when using --file.").option(
|
|
371
|
+
var threadCommand = new Command4("thread").description("Create a thread").argument("[posts...]", "Thread posts (each argument is one post). Omit when using --file.").option("--first-comment <text>", "Post this as a comment under the thread's last post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota").option(
|
|
358
372
|
"-a, --account <id>",
|
|
359
373
|
"Account ID. Repeat it to publish the same thread to several accounts at once (up to 25)",
|
|
360
374
|
collect
|
|
@@ -369,7 +383,7 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
369
383
|
"JSON file with a full thread payload: { posts: [{ text, media_urls? }, ...], schedule_at?: string }. It may also carry account_ids and account_configurations."
|
|
370
384
|
).option(
|
|
371
385
|
"--config <json-or-file>",
|
|
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`
|
|
386
|
+
`Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "posts": [{ "text": "one" }, { "text": "two" }], "first_comment": "..." } }. An account's posts replace the whole thread for that account, and "first_comment": "" publishes that account without the first comment the call is sending`
|
|
373
387
|
).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
388
|
"--draft",
|
|
375
389
|
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
@@ -447,6 +461,7 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
447
461
|
const result = await client.createThread({
|
|
448
462
|
account_ids: accountIds,
|
|
449
463
|
posts: postPayload,
|
|
464
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
450
465
|
...configurations ? { account_configurations: configurations } : {},
|
|
451
466
|
schedule_at: scheduleAt,
|
|
452
467
|
...opts.draft ? { draft: true } : {}
|
|
@@ -475,6 +490,7 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
475
490
|
const thread = await client.createThread({
|
|
476
491
|
account_id: accountIds[0],
|
|
477
492
|
posts: postPayload,
|
|
493
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
478
494
|
schedule_at: scheduleAt,
|
|
479
495
|
...opts.draft ? { draft: true } : {}
|
|
480
496
|
});
|
|
@@ -503,20 +519,21 @@ function wantsJson(opts, cmd) {
|
|
|
503
519
|
}
|
|
504
520
|
|
|
505
521
|
// src/commands/posts.ts
|
|
506
|
-
var postsCommand = new Command5("posts").description("List, get, update, or
|
|
522
|
+
var postsCommand = new Command5("posts").description("List, get, update, delete, hide or unhide posts").option(
|
|
507
523
|
"--status <status>",
|
|
508
524
|
"Filter by status: draft, scheduled, publishing, published, failed, deleted"
|
|
509
525
|
).option("--account <id>", "Filter by account").option(
|
|
510
526
|
"--group <id>",
|
|
511
527
|
"Only the posts of one fan-out, from the group ID a multi-account post returned"
|
|
512
|
-
).option("--limit <n>", "Limit results", "20").option("--json", "Output raw JSON").action(async (opts, cmd) => {
|
|
528
|
+
).option("--limit <n>", "Limit results", "20").option("--include-hidden", "Include posts you have hidden").option("--json", "Output raw JSON").action(async (opts, cmd) => {
|
|
513
529
|
const client = getClient();
|
|
514
530
|
try {
|
|
515
531
|
const posts = await client.listPosts({
|
|
516
532
|
status: opts.status,
|
|
517
533
|
account_id: opts.account,
|
|
518
534
|
group_id: opts.group,
|
|
519
|
-
limit: parseInt(opts.limit)
|
|
535
|
+
limit: parseInt(opts.limit),
|
|
536
|
+
include_hidden: opts.includeHidden
|
|
520
537
|
});
|
|
521
538
|
if (wantsJson(opts, cmd)) {
|
|
522
539
|
json(posts);
|
|
@@ -527,6 +544,7 @@ var postsCommand = new Command5("posts").description("List, get, update, or dele
|
|
|
527
544
|
status: p.status,
|
|
528
545
|
text: p.text.length > 50 ? p.text.slice(0, 50) + "..." : p.text,
|
|
529
546
|
created: p.created_at.slice(0, 10),
|
|
547
|
+
hidden: p.hidden ? "yes" : "",
|
|
530
548
|
// Why a post is not where you expected it. Empty for the ones
|
|
531
549
|
// that are exactly where you left them.
|
|
532
550
|
issue: postFailureReason(p) ?? ""
|
|
@@ -570,6 +588,9 @@ postsCommand.command("update <id>").description(
|
|
|
570
588
|
).option("--alt <text...>", ALT_OPTION_HELP).option("--clear-media", "Remove the media from the post").option(
|
|
571
589
|
"-s, --schedule-at <datetime>",
|
|
572
590
|
"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."
|
|
591
|
+
).option(
|
|
592
|
+
"--first-comment <text>",
|
|
593
|
+
'The comment to post under this one once it publishes. Pass "" to remove the one it has'
|
|
573
594
|
).option(
|
|
574
595
|
"--keep-draft",
|
|
575
596
|
"Keep a draft a draft, so --schedule-at only changes the time it remembers"
|
|
@@ -578,9 +599,9 @@ postsCommand.command("update <id>").description(
|
|
|
578
599
|
error("Pass either --media or --clear-media, not both");
|
|
579
600
|
process.exit(1);
|
|
580
601
|
}
|
|
581
|
-
if (!opts.text && !opts.media && !opts.clearMedia && !opts.scheduleAt && !opts.keepDraft) {
|
|
602
|
+
if (!opts.text && !opts.media && !opts.clearMedia && opts.firstComment === void 0 && !opts.scheduleAt && !opts.keepDraft) {
|
|
582
603
|
error(
|
|
583
|
-
"Nothing to change. Pass --text, --media, --clear-media, --schedule-at or --keep-draft"
|
|
604
|
+
"Nothing to change. Pass --text, --media, --clear-media, --first-comment, --schedule-at or --keep-draft"
|
|
584
605
|
);
|
|
585
606
|
process.exit(1);
|
|
586
607
|
}
|
|
@@ -591,6 +612,7 @@ postsCommand.command("update <id>").description(
|
|
|
591
612
|
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
592
613
|
...opts.clearMedia ? { media: [] } : {},
|
|
593
614
|
...media ? { media } : {},
|
|
615
|
+
...opts.firstComment !== void 0 ? { first_comment: opts.firstComment } : {},
|
|
594
616
|
...opts.scheduleAt ? { schedule_at: opts.scheduleAt } : {},
|
|
595
617
|
...opts.keepDraft ? { draft: true } : {}
|
|
596
618
|
});
|
|
@@ -630,7 +652,7 @@ postsCommand.command("publish <id>").description("Publish a draft now. Every rul
|
|
|
630
652
|
}
|
|
631
653
|
});
|
|
632
654
|
postsCommand.command("delete <id>").description(
|
|
633
|
-
"Delete a post
|
|
655
|
+
"Delete a post from the platform (a scheduled thread is cancelled whole; the post stays in Chirpie marked deleted)"
|
|
634
656
|
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
635
657
|
const client = getClient();
|
|
636
658
|
try {
|
|
@@ -650,6 +672,58 @@ postsCommand.command("delete <id>").description(
|
|
|
650
672
|
process.exit(1);
|
|
651
673
|
}
|
|
652
674
|
});
|
|
675
|
+
postsCommand.command("first-comment <id>").description(
|
|
676
|
+
"Post a first comment that failed, again. Re-sends the text the post already carries"
|
|
677
|
+
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
678
|
+
const client = getClient();
|
|
679
|
+
try {
|
|
680
|
+
const post = await client.retryFirstComment(id);
|
|
681
|
+
if (wantsJson(opts, cmd)) {
|
|
682
|
+
json(post);
|
|
683
|
+
} else {
|
|
684
|
+
success(`First comment posted on ${post.id}`);
|
|
685
|
+
}
|
|
686
|
+
} catch (err) {
|
|
687
|
+
apiError(err, "Failed to post the first comment");
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
postsCommand.command("hide <id>").description(
|
|
692
|
+
"Hide a post from your Chirpie listings (nothing reaches the platform)"
|
|
693
|
+
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
694
|
+
const client = getClient();
|
|
695
|
+
try {
|
|
696
|
+
const result = await client.hidePost(id);
|
|
697
|
+
if (wantsJson(opts, cmd)) {
|
|
698
|
+
json(result);
|
|
699
|
+
} else {
|
|
700
|
+
const n = result.hidden_ids.length;
|
|
701
|
+
success(
|
|
702
|
+
n > 1 ? `Hidden from Chirpie (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${id} hidden from Chirpie. It is unchanged on the platform.`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
} catch (err) {
|
|
706
|
+
error(err instanceof Error ? err.message : "Failed to hide post");
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
postsCommand.command("unhide <id>").description("Put a hidden post back in your Chirpie listings").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
711
|
+
const client = getClient();
|
|
712
|
+
try {
|
|
713
|
+
const result = await client.unhidePost(id);
|
|
714
|
+
if (wantsJson(opts, cmd)) {
|
|
715
|
+
json(result);
|
|
716
|
+
} else {
|
|
717
|
+
const n = result.hidden_ids.length;
|
|
718
|
+
success(
|
|
719
|
+
n > 1 ? `Showing again (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${id} is showing again`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
} catch (err) {
|
|
723
|
+
error(err instanceof Error ? err.message : "Failed to unhide post");
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
653
727
|
|
|
654
728
|
// src/commands/accounts.ts
|
|
655
729
|
import { Command as Command6 } from "commander";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chirpie",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
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.45",
|
|
48
48
|
"commander": "^13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|