chirpie 1.0.11 → 1.0.12

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 (2) hide show
  1. package/dist/index.js +114 -49
  2. package/package.json +17 -3
package/dist/index.js CHANGED
@@ -149,7 +149,7 @@ function getClient() {
149
149
  }
150
150
 
151
151
  // src/commands/post.ts
152
- var postCommand = new Command3("post").description("Create a post").argument("<text>", "Post text").option("-a, --account <id>", "Account ID").option("-m, --media <urls...>", "Media URLs (up to 4 images or 1 video)").option("-s, --schedule <datetime>", "Schedule for later (ISO 8601)").option("--json", "Output raw JSON").action(async (text, opts) => {
152
+ var postCommand = new Command3("post").description("Create a post").argument("<text>", "Post text").option("-a, --account <id>", "Account ID").option("-m, --media <urls...>", "Media URLs. Per-platform caps: X/Bluesky/Mastodon 4, LinkedIn 9, Threads/Pinterest/YouTube/GBP/Snapchat/Reddit 1, Instagram/Facebook/Telegram 10, TikTok up to 35 photos.").option("-s, --schedule <datetime>", "Schedule for later (ISO 8601)").option("--json", "Output raw JSON").action(async (text, opts) => {
153
153
  const client = getClient();
154
154
  let accountId = opts.account;
155
155
  if (!accountId) {
@@ -188,7 +188,21 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
188
188
 
189
189
  // src/commands/thread.ts
190
190
  import { Command as Command4 } from "commander";
191
- var threadCommand = new Command4("thread").description("Create a thread").argument("<posts...>", "Thread posts (each argument is one post in the thread)").option("-a, --account <id>", "Account ID").option("-s, --schedule <datetime>", "Schedule for later (ISO 8601)").option("--json", "Output raw JSON").action(async (posts, opts) => {
191
+ import { readFileSync } from "fs";
192
+ function parseMediaSpec(spec) {
193
+ const m = spec.match(/^(\d+):(.+)$/);
194
+ if (!m) return null;
195
+ const index = parseInt(m[1], 10);
196
+ const urls = m[2].split(",").map((u) => u.trim()).filter(Boolean);
197
+ return { index, urls };
198
+ }
199
+ var threadCommand = new Command4("thread").description("Create a thread").argument("[posts...]", "Thread posts (each argument is one post). Omit when using --file.").option("-a, --account <id>", "Account ID").option(
200
+ "-m, --media <specs...>",
201
+ 'Per-post media as "INDEX:url1,url2" (repeatable). Example: -m 0:https://a.png 2:https://b.png,https://c.png'
202
+ ).option(
203
+ "-f, --file <path>",
204
+ "JSON file with a full thread payload: { posts: [{ text, media_urls? }, ...], schedule_at?: string }"
205
+ ).option("-s, --schedule <datetime>", "Schedule for later (ISO 8601)").option("--json", "Output raw JSON").action(async (posts, opts) => {
192
206
  const client = getClient();
193
207
  let accountId = opts.account;
194
208
  if (!accountId) {
@@ -205,15 +219,44 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
205
219
  }
206
220
  accountId = active[0].id;
207
221
  }
208
- if (posts.length < 2) {
209
- error("Thread must have at least 2 posts");
210
- process.exit(1);
222
+ let postPayload;
223
+ let scheduleAt = opts.schedule;
224
+ if (opts.file) {
225
+ try {
226
+ const raw = JSON.parse(readFileSync(opts.file, "utf8"));
227
+ if (!Array.isArray(raw.posts)) throw new Error("File must contain { posts: [...] }");
228
+ postPayload = raw.posts;
229
+ scheduleAt = scheduleAt ?? raw.schedule_at;
230
+ } catch (err) {
231
+ error(err instanceof Error ? err.message : "Failed to read thread file");
232
+ process.exit(1);
233
+ }
234
+ } else {
235
+ if (posts.length < 2) {
236
+ error("Thread must have at least 2 posts (or use --file)");
237
+ process.exit(1);
238
+ }
239
+ postPayload = posts.map((text) => ({ text }));
240
+ if (opts.media) {
241
+ for (const spec of opts.media) {
242
+ const parsed = parseMediaSpec(spec);
243
+ if (!parsed) {
244
+ error(`Invalid --media spec: "${spec}". Expected "INDEX:url1,url2".`);
245
+ process.exit(1);
246
+ }
247
+ if (parsed.index < 0 || parsed.index >= postPayload.length) {
248
+ error(`--media index ${parsed.index} out of range (thread has ${postPayload.length} posts)`);
249
+ process.exit(1);
250
+ }
251
+ postPayload[parsed.index].media_urls = parsed.urls;
252
+ }
253
+ }
211
254
  }
212
255
  try {
213
256
  const thread = await client.createThread({
214
257
  account_id: accountId,
215
- posts: posts.map((text) => ({ text })),
216
- schedule_at: opts.schedule
258
+ posts: postPayload,
259
+ schedule_at: scheduleAt
217
260
  });
218
261
  if (opts.json) {
219
262
  json(thread);
@@ -286,6 +329,54 @@ postsCommand.command("delete <id>").description("Delete a post").action(async (i
286
329
 
287
330
  // src/commands/accounts.ts
288
331
  import { Command as Command6 } from "commander";
332
+ async function readSecretFromStdin(prompt) {
333
+ const stdin = process.stdin;
334
+ process.stderr.write(prompt);
335
+ if (!stdin.isTTY) {
336
+ const readline = await import("readline");
337
+ const rl = readline.createInterface({ input: stdin });
338
+ return new Promise((resolve) => {
339
+ rl.on("line", (line) => {
340
+ rl.close();
341
+ resolve(line.trim());
342
+ });
343
+ });
344
+ }
345
+ return new Promise((resolve) => {
346
+ const chars = [];
347
+ stdin.setRawMode(true);
348
+ stdin.resume();
349
+ stdin.setEncoding("utf8");
350
+ const onData = (ch) => {
351
+ switch (ch) {
352
+ case "\n":
353
+ case "\r":
354
+ case "":
355
+ stdin.setRawMode(false);
356
+ stdin.pause();
357
+ stdin.off("data", onData);
358
+ process.stderr.write("\n");
359
+ resolve(chars.join("").trim());
360
+ break;
361
+ case "":
362
+ stdin.setRawMode(false);
363
+ stdin.pause();
364
+ process.stderr.write("\n");
365
+ process.exit(130);
366
+ break;
367
+ case "\x7F":
368
+ // backspace
369
+ case "\b":
370
+ if (chars.length > 0) chars.pop();
371
+ break;
372
+ default:
373
+ chars.push(ch);
374
+ break;
375
+ }
376
+ };
377
+ stdin.on("data", onData);
378
+ });
379
+ }
289
380
  var accountsCommand = new Command6("accounts").description("List connected social accounts (X, Bluesky, LinkedIn, Threads, Mastodon, Instagram, Facebook, Telegram, Reddit, Pinterest, TikTok, YouTube, Google Business Profile, and Snapchat)").option("--json", "Output raw JSON").action(async (opts) => {
290
381
  const client = getClient();
291
382
  try {
@@ -326,15 +417,7 @@ accountsCommand.command("connect-bluesky").description("Connect a Bluesky accoun
326
417
  try {
327
418
  let appPassword = opts.appPassword;
328
419
  if (!appPassword) {
329
- const readline = await import("readline");
330
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
331
- appPassword = await new Promise((resolve) => {
332
- process.stderr.write("App Password: ");
333
- rl.question("", (answer) => {
334
- rl.close();
335
- resolve(answer.trim());
336
- });
337
- });
420
+ appPassword = await readSecretFromStdin("App Password: ");
338
421
  if (!appPassword) {
339
422
  error("App password is required");
340
423
  process.exit(1);
@@ -425,15 +508,7 @@ accountsCommand.command("connect-telegram").description("Connect a Telegram bot
425
508
  let botToken = opts.botToken;
426
509
  let chatId = opts.chatId;
427
510
  if (!botToken) {
428
- const readline = await import("readline");
429
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
430
- botToken = await new Promise((resolve) => {
431
- process.stderr.write("Bot Token: ");
432
- rl.question("", (answer) => {
433
- rl.close();
434
- resolve(answer.trim());
435
- });
436
- });
511
+ botToken = await readSecretFromStdin("Bot Token: ");
437
512
  if (!botToken) {
438
513
  error("Bot token is required");
439
514
  process.exit(1);
@@ -465,30 +540,20 @@ accountsCommand.command("connect-telegram").description("Connect a Telegram bot
465
540
  process.exit(1);
466
541
  }
467
542
  });
468
- accountsCommand.command("connect-reddit").description("Connect a Reddit account via OAuth").action(async () => {
469
- error("Reddit integration is coming soon. Check chirpie.ai for updates.");
470
- process.exit(1);
471
- });
472
- accountsCommand.command("connect-pinterest").description("Connect a Pinterest account via OAuth").action(async () => {
473
- error("Pinterest integration is coming soon. Check chirpie.ai for updates.");
474
- process.exit(1);
475
- });
476
- accountsCommand.command("connect-tiktok").description("Connect a TikTok account via OAuth").action(async () => {
477
- error("TikTok integration is coming soon. Check chirpie.ai for updates.");
478
- process.exit(1);
479
- });
480
- accountsCommand.command("connect-youtube").description("Connect a YouTube channel via Google OAuth").action(async () => {
481
- error("YouTube integration is coming soon. Check chirpie.ai for updates.");
482
- process.exit(1);
483
- });
484
- accountsCommand.command("connect-google-business").description("Connect a Google Business Profile via Google OAuth").action(async () => {
485
- error("Google Business Profile integration is coming soon. Check chirpie.ai for updates.");
486
- process.exit(1);
487
- });
488
- accountsCommand.command("connect-snapchat").description("Connect a Snapchat account via OAuth").action(async () => {
489
- error("Snapchat integration is coming soon. Check chirpie.ai for updates.");
490
- process.exit(1);
491
- });
543
+ var comingSoonCli = [
544
+ { cmd: "connect-reddit", label: "Reddit" },
545
+ { cmd: "connect-pinterest", label: "Pinterest" },
546
+ { cmd: "connect-tiktok", label: "TikTok" },
547
+ { cmd: "connect-youtube", label: "YouTube" },
548
+ { cmd: "connect-google-business", label: "Google Business Profile" },
549
+ { cmd: "connect-snapchat", label: "Snapchat" }
550
+ ];
551
+ for (const { cmd, label } of comingSoonCli) {
552
+ accountsCommand.command(cmd).description(`Connect a ${label} account (coming soon)`).action(() => {
553
+ error(`${label} integration is coming soon. Follow https://chirpie.ai/docs for updates.`);
554
+ process.exit(1);
555
+ });
556
+ }
492
557
 
493
558
  // src/commands/analytics.ts
494
559
  import { Command as Command7 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chirpie",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Chirpie CLI — post to social media from the command line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,12 +26,26 @@
26
26
  "chirpie",
27
27
  "cli",
28
28
  "social-media",
29
+ "x",
29
30
  "twitter",
30
- "x"
31
+ "bluesky",
32
+ "linkedin",
33
+ "threads",
34
+ "mastodon",
35
+ "instagram",
36
+ "facebook",
37
+ "telegram",
38
+ "reddit",
39
+ "pinterest",
40
+ "tiktok",
41
+ "youtube",
42
+ "google-business",
43
+ "snapchat",
44
+ "scheduling"
31
45
  ],
32
46
  "license": "MIT",
33
47
  "dependencies": {
34
- "@chirpie/sdk": "^1.0.9",
48
+ "@chirpie/sdk": "^1.0.10",
35
49
  "commander": "^13"
36
50
  },
37
51
  "devDependencies": {