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.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/dist/index.js +84 -10
  3. 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 delete <id>` | Delete a post. Deleting any post of a scheduled thread cancels the thread |
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 delete posts").option(
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 (deleting any post of a scheduled thread cancels the thread)"
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.46",
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.43",
47
+ "@chirpie/sdk": "^1.0.45",
48
48
  "commander": "^13"
49
49
  },
50
50
  "devDependencies": {