typefully 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { Command } from "commander";
6
6
 
7
7
  // src/commands/aliases.ts
8
8
  import fs3 from "fs";
9
+ import * as p from "@clack/prompts";
10
+ import pc4 from "picocolors";
9
11
 
10
12
  // src/utils/config.ts
11
13
  import fs from "fs";
@@ -18,7 +20,8 @@ import pc from "picocolors";
18
20
  import { z } from "zod/v4";
19
21
  var ConfigSchema = z.object({
20
22
  apiKey: z.string().optional(),
21
- defaultSocialSetId: z.union([z.string(), z.number()]).optional()
23
+ defaultSocialSetId: z.union([z.string(), z.number()]).optional(),
24
+ defaultPlatforms: z.array(z.string()).optional()
22
25
  });
23
26
  var PLATFORMS = ["x", "linkedin", "threads", "bluesky", "mastodon"];
24
27
 
@@ -112,6 +115,14 @@ async function requireApiKey() {
112
115
  console.error("");
113
116
  return { source: configPath, key: apiKey };
114
117
  }
118
+ function getDefaultPlatforms() {
119
+ const localPath = path.join(process.cwd(), LOCAL_CONFIG_FILE);
120
+ const localConfig = readConfigFile(localPath);
121
+ if (localConfig?.defaultPlatforms?.length) return localConfig.defaultPlatforms;
122
+ const globalConfig = readConfigFile(GLOBAL_CONFIG_FILE);
123
+ if (globalConfig?.defaultPlatforms?.length) return globalConfig.defaultPlatforms;
124
+ return null;
125
+ }
115
126
  function getDefaultSocialSetId() {
116
127
  const localPath = path.join(process.cwd(), LOCAL_CONFIG_FILE);
117
128
  const localConfig = readConfigFile(localPath);
@@ -185,9 +196,9 @@ var NOOP_SPINNER = {
185
196
  fail() {
186
197
  }
187
198
  };
188
- function spin(text4) {
199
+ function spin(text5) {
189
200
  if (isJsonMode()) return NOOP_SPINNER;
190
- return ora({ text: text4, color: "cyan" });
201
+ return ora({ text: text5, color: "cyan" });
191
202
  }
192
203
 
193
204
  // src/utils/api.ts
@@ -208,12 +219,12 @@ async function apiRequest(method, endpoint, body, opts = {}) {
208
219
  }
209
220
  const res = await fetch(url, fetchOpts);
210
221
  if (!res.ok) {
211
- const text5 = await res.text().catch(() => "");
222
+ const text6 = await res.text().catch(() => "");
212
223
  let parsed;
213
224
  try {
214
- parsed = JSON.parse(text5);
225
+ parsed = JSON.parse(text6);
215
226
  } catch {
216
- parsed = text5;
227
+ parsed = text6;
217
228
  }
218
229
  if (exitOnError) {
219
230
  console.error(JSON.stringify({ error: `HTTP ${res.status}`, response: parsed }, null, 2));
@@ -224,12 +235,12 @@ async function apiRequest(method, endpoint, body, opts = {}) {
224
235
  err.status = res.status;
225
236
  throw err;
226
237
  }
227
- const text4 = await res.text();
228
- if (!text4) return {};
238
+ const text5 = await res.text();
239
+ if (!text5) return {};
229
240
  try {
230
- return JSON.parse(text4);
241
+ return JSON.parse(text5);
231
242
  } catch {
232
- return text4;
243
+ return text5;
233
244
  }
234
245
  }
235
246
  function sleep(ms) {
@@ -239,8 +250,8 @@ function sleep(ms) {
239
250
  // src/utils/helpers.ts
240
251
  import path2 from "path";
241
252
  import pc2 from "picocolors";
242
- function splitThreadText(text4) {
243
- return text4.split(/\r?\n[ \t]*---[ \t]*\r?\n/).filter((t) => t.trim());
253
+ function splitThreadText(text5) {
254
+ return text5.split(/\r?\n[ \t]*---[ \t]*\r?\n/).filter((t) => t.trim());
244
255
  }
245
256
  function sanitizeFilename(filename) {
246
257
  const ext = path2.extname(filename).toLowerCase();
@@ -313,18 +324,26 @@ function formatSocialSetsForDisplay(socialSets) {
313
324
  // src/commands/drafts.ts
314
325
  import fs2 from "fs";
315
326
  import pc3 from "picocolors";
327
+ import terminalLink from "terminal-link";
316
328
  function enabledPlatforms(draft) {
317
329
  if (!draft.platforms) return "";
318
330
  return Object.entries(draft.platforms).filter(([, v]) => v.enabled).map(([k]) => k).join(" \xB7 ");
319
331
  }
320
- function firstPostText(draft) {
321
- if (!draft.platforms) return "";
322
- for (const config of Object.values(
323
- draft.platforms
324
- )) {
325
- if (config.enabled && config.posts?.[0]?.text) {
326
- const t = config.posts[0].text;
327
- return t.length > 80 ? `${t.slice(0, 80)}\u2026` : t;
332
+ function firstPostText(draft, maxLen = 80) {
333
+ if (draft.text) {
334
+ const t = String(draft.text);
335
+ return t.length > maxLen ? `${t.slice(0, maxLen)}\u2026` : t;
336
+ }
337
+ if (Array.isArray(draft.posts)) {
338
+ const t = draft.posts[0]?.text;
339
+ if (t) return t.length > maxLen ? `${t.slice(0, maxLen)}\u2026` : t;
340
+ }
341
+ if (draft.platforms) {
342
+ for (const config of Object.values(
343
+ draft.platforms
344
+ )) {
345
+ const t = config.posts?.[0]?.text;
346
+ if (t) return t.length > maxLen ? `${t.slice(0, maxLen)}\u2026` : t;
328
347
  }
329
348
  }
330
349
  return "";
@@ -368,10 +387,18 @@ function renderDraft(data, verb) {
368
387
  const id = String(data.id ?? "");
369
388
  const status = String(data.status ?? "draft");
370
389
  const platforms = enabledPlatforms(data);
390
+ const preview = firstPostText(data);
371
391
  const prefix = verb ? `${pc3.green("\u2713")} ${verb} ` : "";
392
+ const draftUrl = String(data.share_url ?? `https://typefully.com/?d=${id}`);
393
+ const linkedId = terminalLink(pc3.bold(id), draftUrl);
372
394
  console.log("");
373
- console.log(` ${prefix}${pc3.bold(id)} \xB7 ${status} \xB7 ${pc3.dim(platforms)}`);
374
- if (data.share_url) console.log(pc3.dim(` Share: ${data.share_url}`));
395
+ console.log(` ${prefix}${linkedId} \xB7 ${statusBadge(status)} \xB7 ${pc3.cyan(platforms)}`);
396
+ if (preview) console.log(` ${pc3.dim(preview)}`);
397
+ if (data.scheduled_at) {
398
+ const date = new Date(String(data.scheduled_at)).toLocaleString();
399
+ console.log(` ${pc3.dim("Scheduled:")} ${pc3.yellow(date)}`);
400
+ }
401
+ if (data.share_url) console.log(` ${pc3.dim("Share:")} ${pc3.cyan(String(data.share_url))}`);
375
402
  console.log("");
376
403
  }
377
404
  async function getFirstConnectedPlatform(socialSetId) {
@@ -403,10 +430,10 @@ function registerDraftsCommand(program2) {
403
430
  if (opts.status) params.set("status", opts.status);
404
431
  if (opts.tag) params.set("tag", opts.tag);
405
432
  if (opts.sort) params.set("order_by", opts.sort);
406
- const spinner = spin("Fetching drafts\u2026");
407
- spinner.start();
433
+ const spinner3 = spin("Fetching drafts\u2026");
434
+ spinner3.start();
408
435
  const data = await apiRequest("GET", `/social-sets/${id}/drafts?${params}`);
409
- spinner.stop();
436
+ spinner3.stop();
410
437
  display(data, () => renderDraftsList(data));
411
438
  });
412
439
  cmd.command("get").description("Get a specific draft").argument("[first_arg]", "social_set_id or draft_id").argument("[second_arg]", "draft_id (when first arg is social_set_id)").option("--social-set-id <id>", "Social set ID via flag (first arg becomes draft_id)").option("--use-default", "Confirm using default social set").action(
@@ -418,22 +445,22 @@ function registerDraftsCommand(program2) {
418
445
  !!opts.useDefault,
419
446
  opts.socialSetId
420
447
  );
421
- const spinner = spin("Fetching draft\u2026");
422
- spinner.start();
448
+ const spinner3 = spin("Fetching draft\u2026");
449
+ spinner3.start();
423
450
  const data = await apiRequest("GET", `/social-sets/${socialSetId}/drafts/${draftId}`);
424
- spinner.stop();
451
+ spinner3.stop();
425
452
  display(data, () => renderDraft(data));
426
453
  }
427
454
  );
428
455
  cmd.command("create").description("Create a new draft").argument("[social_set_id]", "Social set ID (uses default if omitted)").option("--social-set-id <id>", "Social set ID via flag (overrides positional)").option("--text <text>", "Post content (use --- on its own line for threads)").option("-f, --file <path>", "Read content from file").option("--platform <platforms>", "Comma-separated platforms").option("--all", "Post to all connected platforms").option("--media <media_ids>", "Comma-separated media IDs").option("--title <title>", "Draft title (internal only)").option("--schedule <time>", '"now", "next-free-slot", or ISO datetime').option("--tags <tags>", "Comma-separated tag slugs").option("--reply-to <url>", "URL of X post to reply to").option("--community <id>", "X community ID").option("--share", "Generate a public share URL").option("--scratchpad <text>", "Internal notes/scratchpad").option("--notes <text>", "Internal notes/scratchpad (alias for --scratchpad)").action(async (socialSetId, opts) => {
429
456
  const id = resolveSocialSetId(opts.socialSetId ?? socialSetId);
430
- let text4 = opts.text;
457
+ let text5 = opts.text;
431
458
  if (opts.file) {
432
459
  const filePath = opts.file;
433
460
  if (!fs2.existsSync(filePath)) exitWithError(`File not found: ${filePath}`);
434
- text4 = fs2.readFileSync(filePath, "utf-8");
461
+ text5 = fs2.readFileSync(filePath, "utf-8");
435
462
  }
436
- if (!text4) exitWithError("--text or --file is required");
463
+ if (!text5) exitWithError("--text or --file is required");
437
464
  if (opts.all && opts.platform) {
438
465
  exitWithError("Cannot use both --all and --platform flags");
439
466
  }
@@ -443,13 +470,20 @@ function registerDraftsCommand(program2) {
443
470
  if (allPlatforms.length === 0) exitWithError("No connected platforms found");
444
471
  platformList = [...allPlatforms];
445
472
  } else if (opts.platform) {
446
- platformList = opts.platform.split(",").map((p) => p.trim());
473
+ platformList = opts.platform.split(",").map((p3) => p3.trim());
447
474
  } else {
448
- const defaultPlatform = await getFirstConnectedPlatform(id);
449
- if (!defaultPlatform) exitWithError("No connected platforms found. Specify --platform");
450
- platformList = [defaultPlatform];
475
+ const saved = getDefaultPlatforms();
476
+ if (saved?.length) {
477
+ const connected = await getAllConnectedPlatforms(id);
478
+ platformList = saved.filter((p3) => connected.includes(p3));
479
+ if (platformList.length === 0) platformList = [...connected];
480
+ } else {
481
+ const defaultPlatform = await getFirstConnectedPlatform(id);
482
+ if (!defaultPlatform) exitWithError("No connected platforms found. Specify --platform");
483
+ platformList = [defaultPlatform];
484
+ }
451
485
  }
452
- const posts = splitThreadText(text4);
486
+ const posts = splitThreadText(text5);
453
487
  const mediaIds = opts.media ? opts.media.split(",").map((m) => m.trim()) : [];
454
488
  const postsArray = posts.map((postText, index) => {
455
489
  const post = { text: postText };
@@ -476,10 +510,10 @@ function registerDraftsCommand(program2) {
476
510
  if (opts.share) body.share = true;
477
511
  const scratchpad = opts.notes ?? opts.scratchpad;
478
512
  if (scratchpad) body.scratchpad_text = scratchpad;
479
- const spinner = spin("Creating draft\u2026");
480
- spinner.start();
513
+ const spinner3 = spin("Creating draft\u2026");
514
+ spinner3.start();
481
515
  const data = await apiRequest("POST", `/social-sets/${id}/drafts`, body);
482
- spinner.stop();
516
+ spinner3.stop();
483
517
  display(data, () => renderDraft(data, "Draft created"));
484
518
  });
485
519
  cmd.command("update").description("Update an existing draft").argument("[first_arg]", "social_set_id or draft_id").argument("[second_arg]", "draft_id (when first arg is social_set_id)").option("--social-set-id <id>", "Social set ID via flag (first arg becomes draft_id)").option("--text <text>", "New post content").option("-f, --file <path>", "Read content from file").option("--platform <platforms>", "Comma-separated platforms").option("--media <media_ids>", "Comma-separated media IDs").option("-a, --append", "Append to existing thread").option("--title <title>", "New draft title").option("--schedule <time>", '"now", "next-free-slot", or ISO datetime').option("--tags <tags>", "Comma-separated tag slugs").option("--share", "Generate a public share URL").option("--scratchpad <text>", "Internal notes/scratchpad").option("--notes <text>", "Internal notes/scratchpad (alias for --scratchpad)").option("--use-default", "Confirm using default social set").action(
@@ -491,14 +525,14 @@ function registerDraftsCommand(program2) {
491
525
  !!opts.useDefault,
492
526
  opts.socialSetId
493
527
  );
494
- let text4 = opts.text;
528
+ let text5 = opts.text;
495
529
  if (opts.file) {
496
530
  const filePath = opts.file;
497
531
  if (!fs2.existsSync(filePath)) exitWithError(`File not found: ${filePath}`);
498
- text4 = fs2.readFileSync(filePath, "utf-8");
532
+ text5 = fs2.readFileSync(filePath, "utf-8");
499
533
  }
500
534
  const body = {};
501
- if (text4) {
535
+ if (text5) {
502
536
  const mediaIds = opts.media ? opts.media.split(",").map((m) => m.trim()) : [];
503
537
  const existing = await apiRequest(
504
538
  "GET",
@@ -506,7 +540,7 @@ function registerDraftsCommand(program2) {
506
540
  );
507
541
  let platformList;
508
542
  if (opts.platform) {
509
- platformList = opts.platform.split(",").map((p) => p.trim());
543
+ platformList = opts.platform.split(",").map((p3) => p3.trim());
510
544
  } else {
511
545
  const existingPlatforms = existing.platforms;
512
546
  platformList = Object.entries(existingPlatforms ?? {}).filter(([, config]) => config.enabled).map(([platform]) => platform);
@@ -526,11 +560,11 @@ function registerDraftsCommand(program2) {
526
560
  break;
527
561
  }
528
562
  }
529
- const newPost = { text: text4 };
563
+ const newPost = { text: text5 };
530
564
  if (mediaIds.length > 0) newPost.media_ids = mediaIds;
531
565
  postsArray = [...existingPosts, newPost];
532
566
  } else {
533
- const posts = splitThreadText(text4);
567
+ const posts = splitThreadText(text5);
534
568
  postsArray = posts.map((postText, index) => {
535
569
  const post = { text: postText };
536
570
  if (index === 0 && mediaIds.length > 0) post.media_ids = mediaIds;
@@ -538,8 +572,8 @@ function registerDraftsCommand(program2) {
538
572
  });
539
573
  }
540
574
  const platformsObj = {};
541
- for (const p of platformList) {
542
- platformsObj[p] = { enabled: true, posts: postsArray };
575
+ for (const p3 of platformList) {
576
+ platformsObj[p3] = { enabled: true, posts: postsArray };
543
577
  }
544
578
  body.platforms = platformsObj;
545
579
  }
@@ -556,14 +590,14 @@ function registerDraftsCommand(program2) {
556
590
  "At least one option is required (--text, --file, --title, --schedule, --share, --scratchpad/--notes, or --tags)"
557
591
  );
558
592
  }
559
- const spinner = spin("Updating draft\u2026");
560
- spinner.start();
593
+ const spinner3 = spin("Updating draft\u2026");
594
+ spinner3.start();
561
595
  const data = await apiRequest(
562
596
  "PATCH",
563
597
  `/social-sets/${socialSetId}/drafts/${draftId}`,
564
598
  body
565
599
  );
566
- spinner.stop();
600
+ spinner3.stop();
567
601
  display(data, () => renderDraft(data, "Draft updated"));
568
602
  }
569
603
  );
@@ -576,10 +610,10 @@ function registerDraftsCommand(program2) {
576
610
  !!opts.useDefault,
577
611
  opts.socialSetId
578
612
  );
579
- const spinner = spin("Deleting draft\u2026");
580
- spinner.start();
613
+ const spinner3 = spin("Deleting draft\u2026");
614
+ spinner3.start();
581
615
  await apiRequest("DELETE", `/social-sets/${socialSetId}/drafts/${draftId}`);
582
- spinner.succeed("Draft deleted");
616
+ spinner3.succeed("Draft deleted");
583
617
  display({ success: true, message: "Draft deleted" }, () => {
584
618
  });
585
619
  }
@@ -594,12 +628,12 @@ function registerDraftsCommand(program2) {
594
628
  opts.socialSetId
595
629
  );
596
630
  if (!opts.time) exitWithError('--time is required (use "next-free-slot" or ISO datetime)');
597
- const spinner = spin("Scheduling draft\u2026");
598
- spinner.start();
631
+ const spinner3 = spin("Scheduling draft\u2026");
632
+ spinner3.start();
599
633
  const data = await apiRequest("PATCH", `/social-sets/${socialSetId}/drafts/${draftId}`, {
600
634
  publish_at: opts.time
601
635
  });
602
- spinner.stop();
636
+ spinner3.stop();
603
637
  display(data, () => renderDraft(data, "Draft scheduled"));
604
638
  }
605
639
  );
@@ -612,12 +646,12 @@ function registerDraftsCommand(program2) {
612
646
  !!opts.useDefault,
613
647
  opts.socialSetId
614
648
  );
615
- const spinner = spin("Publishing draft\u2026");
616
- spinner.start();
649
+ const spinner3 = spin("Publishing draft\u2026");
650
+ spinner3.start();
617
651
  const data = await apiRequest("PATCH", `/social-sets/${socialSetId}/drafts/${draftId}`, {
618
652
  publish_at: "now"
619
653
  });
620
- spinner.stop();
654
+ spinner3.stop();
621
655
  display(data, () => renderDraft(data, "Draft published"));
622
656
  }
623
657
  );
@@ -627,15 +661,15 @@ function registerDraftsCommand(program2) {
627
661
  function registerAliasCommands(program2) {
628
662
  program2.command("create-draft").description('Create a draft \u2014 alias for "drafts create" with positional text').argument("[text]", "Draft text (or use --text/--file)").option("--social-set-id <id>", "Social set ID (uses default if omitted)").option("--text <text>", "Post content (overrides positional text)").option("-f, --file <path>", "Read content from file").option("--platform <platforms>", "Comma-separated platforms").option("--all", "Post to all connected platforms").option("--media <media_ids>", "Comma-separated media IDs").option("--title <title>", "Draft title (internal only)").option("--schedule <time>", '"now", "next-free-slot", or ISO datetime').option("--tags <tags>", "Comma-separated tag slugs").option("--reply-to <url>", "URL of X post to reply to").option("--community <id>", "X community ID").option("--share", "Generate a public share URL").option("--scratchpad <text>", "Internal notes/scratchpad").option("--notes <text>", "Internal notes/scratchpad (alias for --scratchpad)").action(async (positionalText, opts) => {
629
663
  const id = requireSocialSetId(opts.socialSetId ?? null);
630
- let text4;
664
+ let text5;
631
665
  if (opts.file) {
632
666
  const filePath = opts.file;
633
667
  if (!fs3.existsSync(filePath)) exitWithError(`File not found: ${filePath}`);
634
- text4 = fs3.readFileSync(filePath, "utf-8");
668
+ text5 = fs3.readFileSync(filePath, "utf-8");
635
669
  } else {
636
- text4 = opts.text ?? positionalText;
670
+ text5 = opts.text ?? positionalText;
637
671
  }
638
- if (!text4)
672
+ if (!text5)
639
673
  exitWithError("Draft text is required (provide as argument, or use --text/--file)");
640
674
  if (opts.all && opts.platform) exitWithError("Cannot use both --all and --platform flags");
641
675
  let platformList;
@@ -644,13 +678,20 @@ function registerAliasCommands(program2) {
644
678
  if (allPlatforms.length === 0) exitWithError("No connected platforms found");
645
679
  platformList = [...allPlatforms];
646
680
  } else if (opts.platform) {
647
- platformList = opts.platform.split(",").map((p) => p.trim());
681
+ platformList = opts.platform.split(",").map((p3) => p3.trim());
648
682
  } else {
649
- const defaultPlatform = await getFirstConnectedPlatform(id);
650
- if (!defaultPlatform) exitWithError("No connected platforms found. Specify --platform");
651
- platformList = [defaultPlatform];
683
+ const saved = getDefaultPlatforms();
684
+ if (saved?.length) {
685
+ const connected = await getAllConnectedPlatforms(id);
686
+ platformList = saved.filter((p3) => connected.includes(p3));
687
+ if (platformList.length === 0) platformList = [...connected];
688
+ } else {
689
+ const defaultPlatform = await getFirstConnectedPlatform(id);
690
+ if (!defaultPlatform) exitWithError("No connected platforms found. Specify --platform");
691
+ platformList = [defaultPlatform];
692
+ }
652
693
  }
653
- const posts = splitThreadText(text4);
694
+ const posts = splitThreadText(text5);
654
695
  const mediaIds = opts.media ? opts.media.split(",").map((m) => m.trim()) : [];
655
696
  const postsArray = posts.map((postText, index) => {
656
697
  const post = { text: postText };
@@ -675,25 +716,25 @@ function registerAliasCommands(program2) {
675
716
  if (opts.share) body.share = true;
676
717
  const scratchpad = opts.notes ?? opts.scratchpad;
677
718
  if (scratchpad) body.scratchpad_text = scratchpad;
678
- const spinner = spin("Creating draft\u2026");
679
- spinner.start();
719
+ const spinner3 = spin("Creating draft\u2026");
720
+ spinner3.start();
680
721
  const data = await apiRequest("POST", `/social-sets/${id}/drafts`, body);
681
- spinner.stop();
722
+ spinner3.stop();
682
723
  display(data, () => renderDraft(data, "Draft created"));
683
724
  });
684
725
  program2.command("update-draft").description('Update a draft \u2014 alias for "drafts update" with positional draft_id').argument("<draft_id>", "Draft ID").argument("[text]", "New draft text (or use --text/--file)").option("--social-set-id <id>", "Social set ID (uses default if omitted)").option("--text <text>", "New post content (overrides positional text)").option("-f, --file <path>", "Read content from file").option("--platform <platforms>", "Comma-separated platforms").option("--media <media_ids>", "Comma-separated media IDs").option("-a, --append", "Append to existing thread").option("--title <title>", "New draft title").option("--schedule <time>", '"now", "next-free-slot", or ISO datetime').option("--tags <tags>", "Comma-separated tag slugs").option("--share", "Generate a public share URL").option("--scratchpad <text>", "Internal notes/scratchpad").option("--notes <text>", "Internal notes/scratchpad (alias for --scratchpad)").action(
685
726
  async (draftId, positionalText, opts) => {
686
727
  const socialSetId = requireSocialSetId(opts.socialSetId ?? null);
687
- let text4;
728
+ let text5;
688
729
  if (opts.file) {
689
730
  const filePath = opts.file;
690
731
  if (!fs3.existsSync(filePath)) exitWithError(`File not found: ${filePath}`);
691
- text4 = fs3.readFileSync(filePath, "utf-8");
732
+ text5 = fs3.readFileSync(filePath, "utf-8");
692
733
  } else {
693
- text4 = opts.text ?? positionalText;
734
+ text5 = opts.text ?? positionalText;
694
735
  }
695
736
  const body = {};
696
- if (text4) {
737
+ if (text5) {
697
738
  const mediaIds = opts.media ? opts.media.split(",").map((m) => m.trim()) : [];
698
739
  const existing = await apiRequest(
699
740
  "GET",
@@ -701,7 +742,7 @@ function registerAliasCommands(program2) {
701
742
  );
702
743
  let platformList;
703
744
  if (opts.platform) {
704
- platformList = opts.platform.split(",").map((p) => p.trim());
745
+ platformList = opts.platform.split(",").map((p3) => p3.trim());
705
746
  } else {
706
747
  const existingPlatforms = existing.platforms;
707
748
  platformList = Object.entries(existingPlatforms ?? {}).filter(([, config]) => config.enabled).map(([platform]) => platform);
@@ -721,11 +762,11 @@ function registerAliasCommands(program2) {
721
762
  break;
722
763
  }
723
764
  }
724
- const newPost = { text: text4 };
765
+ const newPost = { text: text5 };
725
766
  if (mediaIds.length > 0) newPost.media_ids = mediaIds;
726
767
  postsArray = [...existingPosts, newPost];
727
768
  } else {
728
- const posts = splitThreadText(text4);
769
+ const posts = splitThreadText(text5);
729
770
  postsArray = posts.map((postText, index) => {
730
771
  const post = { text: postText };
731
772
  if (index === 0 && mediaIds.length > 0) post.media_ids = mediaIds;
@@ -733,8 +774,8 @@ function registerAliasCommands(program2) {
733
774
  });
734
775
  }
735
776
  const platformsObj = {};
736
- for (const p of platformList) {
737
- platformsObj[p] = { enabled: true, posts: postsArray };
777
+ for (const p3 of platformList) {
778
+ platformsObj[p3] = { enabled: true, posts: postsArray };
738
779
  }
739
780
  body.platforms = platformsObj;
740
781
  }
@@ -749,27 +790,97 @@ function registerAliasCommands(program2) {
749
790
  "At least one option is required (--text, --file, --title, --schedule, --share, --scratchpad/--notes, or --tags)"
750
791
  );
751
792
  }
752
- const spinner = spin("Updating draft\u2026");
753
- spinner.start();
793
+ const spinner3 = spin("Updating draft\u2026");
794
+ spinner3.start();
754
795
  const data = await apiRequest(
755
796
  "PATCH",
756
797
  `/social-sets/${socialSetId}/drafts/${draftId}`,
757
798
  body
758
799
  );
759
- spinner.stop();
800
+ spinner3.stop();
760
801
  display(data, () => renderDraft(data, "Draft updated"));
761
802
  }
762
803
  );
804
+ program2.command("rm").description("Delete a draft \u2014 provide draft_id or pick interactively").argument("[draft_id]", "Draft ID to delete (omit for interactive picker)").option("--social-set-id <id>", "Social set ID (uses default if omitted)").option("--status <status>", "Filter drafts by status in picker (default: draft)").option("--limit <n>", "Max drafts to show in picker (default: 20)").action(async (draftId, opts) => {
805
+ const socialSetId = requireSocialSetId(opts.socialSetId ?? null);
806
+ if (draftId) {
807
+ const spinner3 = spin("Deleting draft\u2026");
808
+ spinner3.start();
809
+ await apiRequest("DELETE", `/social-sets/${socialSetId}/drafts/${draftId}`);
810
+ spinner3.succeed("Draft deleted");
811
+ display({ success: true, message: "Draft deleted" }, () => {
812
+ });
813
+ return;
814
+ }
815
+ const fetchSpinner = p.spinner();
816
+ fetchSpinner.start("Loading drafts\u2026");
817
+ const params = new URLSearchParams();
818
+ params.set("limit", String(opts.limit ?? "20"));
819
+ params.set("status", String(opts.status ?? "draft"));
820
+ const data = await apiRequest(
821
+ "GET",
822
+ `/social-sets/${socialSetId}/drafts?${params}`
823
+ );
824
+ const results = data.results ?? [];
825
+ if (results.length === 0) {
826
+ fetchSpinner.stop("No drafts found.");
827
+ p.outro("Nothing to delete.");
828
+ return;
829
+ }
830
+ const fullDrafts = await Promise.all(
831
+ results.map(
832
+ (draft) => apiRequest("GET", `/social-sets/${socialSetId}/drafts/${draft.id}`).catch(() => draft)
833
+ )
834
+ );
835
+ fetchSpinner.stop(`${fullDrafts.length} draft${fullDrafts.length !== 1 ? "s" : ""} loaded`);
836
+ const options = fullDrafts.map((draft) => {
837
+ const id = String(draft.id ?? "");
838
+ const shortId = id.slice(0, 8);
839
+ const platforms = draft.platforms ? Object.entries(draft.platforms).filter(([, v]) => v?.enabled !== false).map(([k]) => k).join(" \xB7 ") : "";
840
+ const preview = firstPostText(draft, 60);
841
+ return {
842
+ value: id,
843
+ label: preview || pc4.dim("(no text)"),
844
+ hint: platforms ? `${shortId} \xB7 ${platforms}` : shortId
845
+ };
846
+ });
847
+ const selected = await p.multiselect({
848
+ message: `Pick drafts to delete ${pc4.dim(`(${results.length} loaded)`)}`,
849
+ options,
850
+ required: true
851
+ });
852
+ if (p.isCancel(selected)) {
853
+ p.cancel("Cancelled.");
854
+ process.exit(0);
855
+ }
856
+ const ids = selected;
857
+ const confirm3 = await p.confirm({
858
+ message: `Delete ${ids.length} draft${ids.length !== 1 ? "s" : ""}?`,
859
+ initialValue: false
860
+ });
861
+ if (p.isCancel(confirm3) || !confirm3) {
862
+ p.cancel("Cancelled.");
863
+ process.exit(0);
864
+ }
865
+ const deleteSpinner = p.spinner();
866
+ deleteSpinner.start(`Deleting ${ids.length} draft${ids.length !== 1 ? "s" : ""}\u2026`);
867
+ for (const id of ids) {
868
+ await apiRequest("DELETE", `/social-sets/${socialSetId}/drafts/${id}`);
869
+ }
870
+ deleteSpinner.stop(
871
+ `${pc4.green("\u2713")} Deleted ${ids.length} draft${ids.length !== 1 ? "s" : ""}`
872
+ );
873
+ });
763
874
  }
764
875
 
765
876
  // src/commands/config.ts
766
877
  import * as clack2 from "@clack/prompts";
767
- import pc4 from "picocolors";
878
+ import pc5 from "picocolors";
768
879
  function renderConfigShow(data) {
769
880
  if (!data.configured) {
770
881
  console.log("");
771
- console.log(` ${pc4.yellow("\u26A0")} Not configured`);
772
- console.log(pc4.dim(` Run: typefully setup \xB7 Get key at: ${API_KEY_URL}`));
882
+ console.log(` ${pc5.yellow("\u26A0")} Not configured`);
883
+ console.log(pc5.dim(` Run: typefully setup \xB7 Get key at: ${API_KEY_URL}`));
773
884
  console.log("");
774
885
  return;
775
886
  }
@@ -777,12 +888,18 @@ function renderConfigShow(data) {
777
888
  const source = String(data.active_source ?? "");
778
889
  const defaultSet = data.default_social_set;
779
890
  console.log("");
780
- console.log(` ${pc4.green("\u2713")} Configured`);
781
- console.log(pc4.dim(` API Key: ${keyPreview} \xB7 from ${source}`));
891
+ console.log(` ${pc5.green("\u2713")} Configured`);
892
+ console.log(pc5.dim(` API Key: ${keyPreview} \xB7 from ${source}`));
782
893
  if (defaultSet) {
783
- console.log(pc4.dim(` Default social set: ${defaultSet.id} \xB7 from ${defaultSet.source}`));
894
+ console.log(pc5.dim(` Default social set: ${defaultSet.id} \xB7 from ${defaultSet.source}`));
784
895
  } else {
785
- console.log(pc4.dim(" Default social set: not set \xB7 Run: typefully config set-default"));
896
+ console.log(pc5.dim(" Default social set: not set \xB7 Run: typefully config set-default"));
897
+ }
898
+ const defaultPlatforms = getDefaultPlatforms();
899
+ if (defaultPlatforms) {
900
+ console.log(pc5.dim(` Default platforms: ${defaultPlatforms.join(", ")}`));
901
+ } else {
902
+ console.log(pc5.dim(" Default platforms: not set \xB7 Run: typefully config set-platforms"));
786
903
  }
787
904
  console.log("");
788
905
  }
@@ -825,10 +942,10 @@ function registerConfigCommand(program2) {
825
942
  let socialSetId = socialSetIdArg;
826
943
  let location = opts.scope ?? opts.location;
827
944
  if (!socialSetId) {
828
- const spinner = spin("Fetching social sets\u2026");
829
- spinner.start();
945
+ const spinner3 = spin("Fetching social sets\u2026");
946
+ spinner3.start();
830
947
  const socialSets = await apiRequest("GET", "/social-sets?limit=50");
831
- spinner.stop();
948
+ spinner3.stop();
832
949
  const results = socialSets.results;
833
950
  if (!results || results.length === 0) {
834
951
  exitWithError("No social sets found. Create one at typefully.com first.");
@@ -837,10 +954,10 @@ function registerConfigCommand(program2) {
837
954
  if (formatted.length === 1) {
838
955
  socialSetId = formatted[0]?.set.id;
839
956
  console.error(
840
- pc4.green(`\u2713 Auto-selecting: ${pc4.bold(String(formatted[0]?.set.name || "Unnamed"))}`)
957
+ pc5.green(`\u2713 Auto-selecting: ${pc5.bold(String(formatted[0]?.set.name || "Unnamed"))}`)
841
958
  );
842
959
  } else {
843
- console.error(pc4.bold("Available social sets:"));
960
+ console.error(pc5.bold("Available social sets:"));
844
961
  console.error("");
845
962
  for (const f of formatted) console.error(f.displayLine);
846
963
  console.error("");
@@ -868,12 +985,12 @@ function registerConfigCommand(program2) {
868
985
  options: [
869
986
  {
870
987
  value: "global",
871
- label: `Global ${pc4.dim("(~/.config/typefully/)")}`,
988
+ label: `Global ${pc5.dim("(~/.config/typefully/)")}`,
872
989
  hint: "available to all projects"
873
990
  },
874
991
  {
875
992
  value: "local",
876
- label: `Local ${pc4.dim("(./.typefully/)")}`,
993
+ label: `Local ${pc5.dim("(./.typefully/)")}`,
877
994
  hint: "only this project"
878
995
  }
879
996
  ]
@@ -894,19 +1011,178 @@ function registerConfigCommand(program2) {
894
1011
  };
895
1012
  display(result, () => {
896
1013
  console.log("");
897
- console.log(` ${pc4.green("\u2713")} Default social set saved: ${pc4.bold(String(socialSetId))}`);
898
- console.log(pc4.dim(` Config: ${configPath}`));
1014
+ console.log(` ${pc5.green("\u2713")} Default social set saved: ${pc5.bold(String(socialSetId))}`);
1015
+ console.log(pc5.dim(` Config: ${configPath}`));
899
1016
  console.log("");
900
1017
  });
901
1018
  });
1019
+ cmd.command("set-platforms").description("Set default platforms for new drafts").option("--platforms <platforms>", "Comma-separated platforms (skips interactive)").option("--location <location>", "Storage location: global or local").option("--scope <scope>", "Alias for --location").action(async (opts) => {
1020
+ let platformList;
1021
+ if (opts.platforms) {
1022
+ platformList = opts.platforms.split(",").map((p3) => p3.trim());
1023
+ } else {
1024
+ const selected = await clack2.multiselect({
1025
+ message: "Default platforms for new drafts",
1026
+ initialValues: ["x"],
1027
+ options: PLATFORMS.map((plat) => ({ value: plat, label: plat }))
1028
+ });
1029
+ if (clack2.isCancel(selected)) {
1030
+ clack2.cancel("Cancelled.");
1031
+ process.exit(0);
1032
+ }
1033
+ platformList = selected;
1034
+ }
1035
+ let location = opts.scope ?? opts.location;
1036
+ if (!location) {
1037
+ const choice = await clack2.select({
1038
+ message: "Where should this be stored?",
1039
+ options: [
1040
+ {
1041
+ value: "global",
1042
+ label: `Global ${pc5.dim("(~/.config/typefully/)")}`,
1043
+ hint: "all projects"
1044
+ },
1045
+ {
1046
+ value: "local",
1047
+ label: `Local ${pc5.dim("(./.typefully/)")}`,
1048
+ hint: "this project only"
1049
+ }
1050
+ ]
1051
+ });
1052
+ if (clack2.isCancel(choice)) {
1053
+ clack2.cancel("Cancelled.");
1054
+ process.exit(0);
1055
+ }
1056
+ location = choice;
1057
+ }
1058
+ const isLocal = location === "local";
1059
+ const configPath = isLocal ? getLocalConfigFile() : getGlobalConfigFile();
1060
+ const existingConfig = readConfigFile(configPath) ?? {};
1061
+ writeConfig(configPath, { ...existingConfig, defaultPlatforms: platformList });
1062
+ display(
1063
+ { success: true, default_platforms: platformList, config_path: configPath },
1064
+ () => {
1065
+ console.log("");
1066
+ console.log(
1067
+ ` ${pc5.green("\u2713")} Default platforms saved: ${pc5.bold(platformList.join(", "))}`
1068
+ );
1069
+ console.log(pc5.dim(` Config: ${configPath}`));
1070
+ console.log("");
1071
+ }
1072
+ );
1073
+ });
1074
+ }
1075
+
1076
+ // src/commands/interactive.ts
1077
+ import * as p2 from "@clack/prompts";
1078
+ async function createDraftDirect(text5) {
1079
+ const id = requireSocialSetId(null);
1080
+ let platformList;
1081
+ const saved = getDefaultPlatforms();
1082
+ if (saved?.length) {
1083
+ const connected = await getAllConnectedPlatforms(id);
1084
+ platformList = saved.filter((p3) => connected.includes(p3));
1085
+ if (platformList.length === 0) platformList = [...connected];
1086
+ } else {
1087
+ const platform = await getFirstConnectedPlatform(id);
1088
+ if (!platform) exitWithError("No connected platforms found. Run: typefully setup");
1089
+ platformList = [platform];
1090
+ }
1091
+ const posts = splitThreadText(text5);
1092
+ const postsArray = posts.map((postText) => ({ text: postText }));
1093
+ const platformsObj = {};
1094
+ for (const p3 of platformList) platformsObj[p3] = { enabled: true, posts: postsArray };
1095
+ const body = { platforms: platformsObj };
1096
+ const spinner3 = spin("Creating draft\u2026");
1097
+ spinner3.start();
1098
+ const data = await apiRequest("POST", `/social-sets/${id}/drafts`, body);
1099
+ spinner3.stop();
1100
+ display(data, () => renderDraft(data, "Draft created"));
1101
+ }
1102
+ async function createDraftInteractive() {
1103
+ const id = requireSocialSetId(null);
1104
+ const textResult = await p2.text({
1105
+ message: "What do you want to post?",
1106
+ placeholder: "Your post content\u2026 (use --- on a new line to split into a thread)",
1107
+ validate: (v) => !v.trim() ? "Post content is required." : void 0
1108
+ });
1109
+ if (p2.isCancel(textResult)) {
1110
+ p2.cancel("Cancelled.");
1111
+ process.exit(0);
1112
+ }
1113
+ const text5 = textResult;
1114
+ let availablePlatforms = [];
1115
+ try {
1116
+ availablePlatforms = await getAllConnectedPlatforms(id);
1117
+ } catch {
1118
+ }
1119
+ const savedPlatforms = getDefaultPlatforms();
1120
+ const defaultInitial = savedPlatforms?.length ? availablePlatforms.filter((p3) => savedPlatforms.includes(p3)) : [availablePlatforms[0]];
1121
+ let platformList;
1122
+ if (availablePlatforms.length > 1) {
1123
+ const selected = await p2.multiselect({
1124
+ message: "Post to",
1125
+ initialValues: defaultInitial.length ? defaultInitial : [availablePlatforms[0]],
1126
+ options: availablePlatforms.map((platform) => ({
1127
+ value: platform,
1128
+ label: platform
1129
+ }))
1130
+ });
1131
+ if (p2.isCancel(selected)) {
1132
+ p2.cancel("Cancelled.");
1133
+ process.exit(0);
1134
+ }
1135
+ platformList = selected;
1136
+ } else if (availablePlatforms.length === 1) {
1137
+ platformList = [availablePlatforms[0]];
1138
+ } else {
1139
+ const def = await getFirstConnectedPlatform(id);
1140
+ if (!def) {
1141
+ p2.cancel("No connected platforms found. Run: typefully setup");
1142
+ process.exit(1);
1143
+ }
1144
+ platformList = [def];
1145
+ }
1146
+ const schedule = await p2.select({
1147
+ message: "When?",
1148
+ options: [
1149
+ { value: "draft", label: "Save as draft" },
1150
+ { value: "next-free-slot", label: "Schedule: next free slot" },
1151
+ { value: "now", label: "Publish now" }
1152
+ ]
1153
+ });
1154
+ if (p2.isCancel(schedule)) {
1155
+ p2.cancel("Cancelled.");
1156
+ process.exit(0);
1157
+ }
1158
+ const posts = splitThreadText(text5);
1159
+ const postsArray = posts.map((postText) => ({ text: postText }));
1160
+ const platformsObj = {};
1161
+ for (const platform of platformList) {
1162
+ platformsObj[platform] = { enabled: true, posts: postsArray };
1163
+ }
1164
+ const body = { platforms: platformsObj };
1165
+ if (schedule !== "draft") body.publish_at = schedule;
1166
+ const s = p2.spinner();
1167
+ s.start("Creating draft\u2026");
1168
+ const data = await apiRequest("POST", `/social-sets/${id}/drafts`, body);
1169
+ s.stop("");
1170
+ display(data, () => renderDraft(data, "Draft created"));
1171
+ }
1172
+ async function runDraft(text5) {
1173
+ if (text5) {
1174
+ await createDraftDirect(text5);
1175
+ } else {
1176
+ await createDraftInteractive();
1177
+ }
902
1178
  }
903
1179
 
904
1180
  // src/commands/me.ts
905
- import pc5 from "picocolors";
1181
+ import pc6 from "picocolors";
906
1182
  var KNOWN_ORDER = ["id", "email", "username", "plan", "timezone", "locale", "avatar_url"];
907
1183
  function renderMe(data) {
908
1184
  console.log("");
909
- console.log(` ${pc5.bold(String(data.name ?? "Unknown"))}`);
1185
+ console.log(` ${pc6.bold(String(data.name ?? "Unknown"))}`);
910
1186
  console.log("");
911
1187
  const rendered = /* @__PURE__ */ new Set(["name"]);
912
1188
  for (const key of KNOWN_ORDER) {
@@ -914,25 +1190,25 @@ function renderMe(data) {
914
1190
  const val = data[key];
915
1191
  const label = key === "avatar_url" ? "Avatar" : key.replace(/_/g, " ");
916
1192
  const display_val = key === "username" ? `@${val}` : String(val);
917
- console.log(` ${pc5.dim(`${label}:`).padEnd(22)} ${display_val}`);
1193
+ console.log(` ${pc6.dim(`${label}:`).padEnd(22)} ${display_val}`);
918
1194
  rendered.add(key);
919
1195
  }
920
1196
  for (const [key, val] of Object.entries(data)) {
921
1197
  if (rendered.has(key) || val == null || val === "") continue;
922
1198
  if (typeof val === "object" && !Array.isArray(val)) {
923
1199
  const nested = val;
924
- console.log(` ${pc5.dim(`${key.replace(/_/g, " ")}:`)}`);
1200
+ console.log(` ${pc6.dim(`${key.replace(/_/g, " ")}:`)}`);
925
1201
  for (const [nk, nv] of Object.entries(nested)) {
926
1202
  if (nv == null || nv === "") continue;
927
- console.log(` ${pc5.dim(`${nk.replace(/_/g, " ")}:`).padEnd(22)} ${nv}`);
1203
+ console.log(` ${pc6.dim(`${nk.replace(/_/g, " ")}:`).padEnd(22)} ${nv}`);
928
1204
  }
929
1205
  } else if (Array.isArray(val)) {
930
1206
  if (val.length > 0) {
931
- console.log(` ${pc5.dim(`${key.replace(/_/g, " ")}:`)} ${val.join(", ")}`);
1207
+ console.log(` ${pc6.dim(`${key.replace(/_/g, " ")}:`)} ${val.join(", ")}`);
932
1208
  }
933
1209
  } else {
934
1210
  const label = key.replace(/_/g, " ");
935
- console.log(` ${pc5.dim(`${label}:`).padEnd(22)} ${val}`);
1211
+ console.log(` ${pc6.dim(`${label}:`).padEnd(22)} ${val}`);
936
1212
  }
937
1213
  rendered.add(key);
938
1214
  }
@@ -940,10 +1216,10 @@ function renderMe(data) {
940
1216
  }
941
1217
  function registerMeCommand(program2) {
942
1218
  program2.command("me").description("Get authenticated user info").action(async () => {
943
- const spinner = spin("Fetching user info\u2026");
944
- spinner.start();
1219
+ const spinner3 = spin("Fetching user info\u2026");
1220
+ spinner3.start();
945
1221
  const data = await apiRequest("GET", "/me");
946
- spinner.stop();
1222
+ spinner3.stop();
947
1223
  display(data, () => renderMe(data));
948
1224
  });
949
1225
  }
@@ -951,7 +1227,7 @@ function registerMeCommand(program2) {
951
1227
  // src/commands/media.ts
952
1228
  import fs4 from "fs";
953
1229
  import path3 from "path";
954
- import pc6 from "picocolors";
1230
+ import pc7 from "picocolors";
955
1231
  function registerMediaCommand(program2) {
956
1232
  const cmd = program2.command("media").description("Manage media uploads");
957
1233
  cmd.command("upload").description("Upload a media file").argument("<file_path>", "Path to file").argument("[social_set_id]", "Social set ID (uses default if omitted)").option("--social-set-id <id>", "Social set ID via flag").option("--no-wait", "Return immediately after upload").option("--timeout <seconds>", "Max wait for processing (default: 60)").action(
@@ -968,8 +1244,8 @@ function registerMediaCommand(program2) {
968
1244
  const n = Number.parseInt(raw, 10);
969
1245
  return Number.isFinite(n) && n >= 0 ? n : 2e3;
970
1246
  })();
971
- const spinner = spin(`Uploading ${pc6.bold(rawFilename)}\u2026`);
972
- spinner.start();
1247
+ const spinner3 = spin(`Uploading ${pc7.bold(rawFilename)}\u2026`);
1248
+ spinner3.start();
973
1249
  const presignedResponse = await apiRequest("POST", `/social-sets/${id}/media/upload`, {
974
1250
  file_name: filename
975
1251
  });
@@ -980,26 +1256,26 @@ function registerMediaCommand(program2) {
980
1256
  const fileBuffer = fs4.readFileSync(filePath);
981
1257
  const uploadResponse = await fetch(uploadUrl, { method: "PUT", body: fileBuffer });
982
1258
  if (!uploadResponse.ok) {
983
- spinner.fail("Upload failed");
1259
+ spinner3.fail("Upload failed");
984
1260
  exitWithError("Failed to upload file to S3", {
985
1261
  http_code: uploadResponse.status,
986
1262
  status_text: uploadResponse.statusText
987
1263
  });
988
1264
  }
989
1265
  if (opts.wait === false) {
990
- spinner.succeed("Uploaded");
1266
+ spinner3.succeed("Uploaded");
991
1267
  display(
992
1268
  { media_id: mediaId, message: "Upload complete. Use media status to check processing." },
993
1269
  () => {
994
1270
  console.log("");
995
- console.log(` ${pc6.green("\u2713")} Uploaded \xB7 ID: ${pc6.bold(mediaId)}`);
996
- console.log(pc6.dim(" Run: typefully media status <id> to check processing"));
1271
+ console.log(` ${pc7.green("\u2713")} Uploaded \xB7 ID: ${pc7.bold(mediaId)}`);
1272
+ console.log(pc7.dim(" Run: typefully media status <id> to check processing"));
997
1273
  console.log("");
998
1274
  }
999
1275
  );
1000
1276
  return;
1001
1277
  }
1002
- spinner.text = "Processing\u2026";
1278
+ spinner3.text = "Processing\u2026";
1003
1279
  const startTime = Date.now();
1004
1280
  while (Date.now() - startTime < timeout) {
1005
1281
  const statusResponse = await apiRequest(
@@ -1007,21 +1283,21 @@ function registerMediaCommand(program2) {
1007
1283
  `/social-sets/${id}/media/${mediaId}`
1008
1284
  );
1009
1285
  if (statusResponse.status === "ready") {
1010
- spinner.succeed("Media ready");
1286
+ spinner3.succeed("Media ready");
1011
1287
  display({ media_id: mediaId, status: "ready", message: "Media uploaded and ready" }, () => {
1012
1288
  console.log("");
1013
- console.log(` ${pc6.green("\u2713")} Media ready \xB7 ID: ${pc6.bold(mediaId)}`);
1289
+ console.log(` ${pc7.green("\u2713")} Media ready \xB7 ID: ${pc7.bold(mediaId)}`);
1014
1290
  console.log("");
1015
1291
  });
1016
1292
  return;
1017
1293
  }
1018
1294
  if (statusResponse.status === "error" || statusResponse.status === "failed") {
1019
- spinner.fail("Processing failed");
1295
+ spinner3.fail("Processing failed");
1020
1296
  exitWithError("Media processing failed", { status: statusResponse });
1021
1297
  }
1022
1298
  await sleep(pollIntervalMs);
1023
1299
  }
1024
- spinner.stop();
1300
+ spinner3.stop();
1025
1301
  const timeoutResult = {
1026
1302
  media_id: mediaId,
1027
1303
  status: "processing",
@@ -1030,8 +1306,8 @@ function registerMediaCommand(program2) {
1030
1306
  };
1031
1307
  display(timeoutResult, () => {
1032
1308
  console.log("");
1033
- console.log(` ${pc6.yellow("\u26A0")} Still processing \xB7 ID: ${pc6.bold(mediaId)}`);
1034
- console.log(pc6.dim(" Run: typefully media status <id> to check"));
1309
+ console.log(` ${pc7.yellow("\u26A0")} Still processing \xB7 ID: ${pc7.bold(mediaId)}`);
1310
+ console.log(pc7.dim(" Run: typefully media status <id> to check"));
1035
1311
  console.log("");
1036
1312
  });
1037
1313
  }
@@ -1040,16 +1316,16 @@ function registerMediaCommand(program2) {
1040
1316
  async (mediaId, socialSetId, opts) => {
1041
1317
  const flagId = opts.socialSetId;
1042
1318
  const id = requireSocialSetId(flagId ?? socialSetId ?? null);
1043
- const spinner = spin("Checking media status\u2026");
1044
- spinner.start();
1319
+ const spinner3 = spin("Checking media status\u2026");
1320
+ spinner3.start();
1045
1321
  const data = await apiRequest("GET", `/social-sets/${id}/media/${mediaId}`);
1046
- spinner.stop();
1322
+ spinner3.stop();
1047
1323
  display(data, () => {
1048
1324
  const d = data;
1049
1325
  const status = String(d.status ?? "unknown");
1050
- const icon = status === "ready" ? pc6.green("\u2713") : status === "error" || status === "failed" ? pc6.red("\u2717") : pc6.yellow("\u23F3");
1326
+ const icon = status === "ready" ? pc7.green("\u2713") : status === "error" || status === "failed" ? pc7.red("\u2717") : pc7.yellow("\u23F3");
1051
1327
  console.log("");
1052
- console.log(` ${icon} ${pc6.bold(mediaId)} \xB7 ${status}`);
1328
+ console.log(` ${icon} ${pc7.bold(mediaId)} \xB7 ${status}`);
1053
1329
  console.log("");
1054
1330
  });
1055
1331
  }
@@ -1060,7 +1336,7 @@ function registerMediaCommand(program2) {
1060
1336
  import fs5 from "fs";
1061
1337
  import path4 from "path";
1062
1338
  import * as clack3 from "@clack/prompts";
1063
- import pc7 from "picocolors";
1339
+ import pc8 from "picocolors";
1064
1340
  function registerSetupCommand(program2) {
1065
1341
  program2.command("setup").description("Interactive setup \u2014 saves API key and optional default social set").option("--key <api_key>", "Provide key non-interactively").option("--location <location>", "Config location: global or local").option("--scope <scope>", "Alias for --location: global or local").option("--default-social-set <id>", "Set default social set non-interactively").option("--no-default", "Skip setting default social set").action(async (opts) => {
1066
1342
  let apiKey = opts.key;
@@ -1069,9 +1345,9 @@ function registerSetupCommand(program2) {
1069
1345
  const noDefault = opts.default === false;
1070
1346
  const isNonInteractive = !!apiKey;
1071
1347
  if (!apiKey) {
1072
- clack3.intro(pc7.bold("Typefully CLI Setup"));
1073
- console.error(pc7.dim("Sign up free at typefully.com if you don't have an account."));
1074
- console.error(`${pc7.blue("\u2192")} Get your API key at: ${pc7.cyan(API_KEY_URL)}`);
1348
+ clack3.intro(pc8.bold("Typefully CLI Setup"));
1349
+ console.error(pc8.dim("Sign up free at typefully.com if you don't have an account."));
1350
+ console.error(`${pc8.blue("\u2192")} Get your API key at: ${pc8.cyan(API_KEY_URL)}`);
1075
1351
  const keyInput = await clack3.text({
1076
1352
  message: "Enter your Typefully API key",
1077
1353
  validate: (val = "") => {
@@ -1091,9 +1367,9 @@ function registerSetupCommand(program2) {
1091
1367
  options: [
1092
1368
  {
1093
1369
  value: "global",
1094
- label: `Global ${pc7.dim("(~/.config/typefully/)")} \u2014 Available to all projects`
1370
+ label: `Global ${pc8.dim("(~/.config/typefully/)")} \u2014 Available to all projects`
1095
1371
  },
1096
- { value: "local", label: `Local ${pc7.dim("(./.typefully/)")} \u2014 Only this project` }
1372
+ { value: "local", label: `Local ${pc8.dim("(./.typefully/)")} \u2014 Only this project` }
1097
1373
  ]
1098
1374
  });
1099
1375
  if (clack3.isCancel(locationChoice)) process.exit(0);
@@ -1114,7 +1390,7 @@ function registerSetupCommand(program2) {
1114
1390
  gitignorePath,
1115
1391
  "\n# Typefully config (contains API key)\n.typefully/\n"
1116
1392
  );
1117
- console.error(pc7.green("\u2713 Added .typefully/ to .gitignore"));
1393
+ console.error(pc8.green("\u2713 Added .typefully/ to .gitignore"));
1118
1394
  } else {
1119
1395
  const addToGitignore = await clack3.confirm({
1120
1396
  message: "Add .typefully/ to .gitignore?",
@@ -1125,16 +1401,16 @@ function registerSetupCommand(program2) {
1125
1401
  gitignorePath,
1126
1402
  "\n# Typefully config (contains API key)\n.typefully/\n"
1127
1403
  );
1128
- console.error(pc7.green("\u2713 Added .typefully/ to .gitignore"));
1404
+ console.error(pc8.green("\u2713 Added .typefully/ to .gitignore"));
1129
1405
  }
1130
1406
  }
1131
1407
  }
1132
1408
  } else if (isNonInteractive) {
1133
1409
  fs5.writeFileSync(gitignorePath, "# Typefully config (contains API key)\n.typefully/\n");
1134
- console.error(pc7.green("\u2713 Created .gitignore with .typefully/ entry"));
1410
+ console.error(pc8.green("\u2713 Created .gitignore with .typefully/ entry"));
1135
1411
  } else {
1136
1412
  console.error(
1137
- pc7.yellow("\u26A0 No .gitignore found. Your API key could be accidentally committed.")
1413
+ pc8.yellow("\u26A0 No .gitignore found. Your API key could be accidentally committed.")
1138
1414
  );
1139
1415
  const createGitignore = await clack3.confirm({
1140
1416
  message: "Create .gitignore with .typefully/ entry?",
@@ -1142,11 +1418,11 @@ function registerSetupCommand(program2) {
1142
1418
  });
1143
1419
  if (!clack3.isCancel(createGitignore) && createGitignore) {
1144
1420
  fs5.writeFileSync(gitignorePath, "# Typefully config (contains API key)\n.typefully/\n");
1145
- console.error(pc7.green("\u2713 Created .gitignore with .typefully/ entry"));
1421
+ console.error(pc8.green("\u2713 Created .gitignore with .typefully/ entry"));
1146
1422
  }
1147
1423
  }
1148
1424
  }
1149
- console.error(pc7.green(`\u2713 API key saved to ${pc7.dim(configPath)}`));
1425
+ console.error(pc8.green(`\u2713 API key saved to ${pc8.dim(configPath)}`));
1150
1426
  let defaultSocialSetId = null;
1151
1427
  if (defaultSocialSetArg) {
1152
1428
  const origKey = process.env.TYPEFULLY_API_KEY;
@@ -1165,9 +1441,9 @@ function registerSetupCommand(program2) {
1165
1441
  defaultSocialSetId = defaultSocialSetArg;
1166
1442
  const updatedConfig = readConfigFile(configPath) ?? {};
1167
1443
  writeConfig(configPath, { ...updatedConfig, defaultSocialSetId });
1168
- console.error(pc7.green(`\u2713 Default social set saved: ${defaultSocialSetId}`));
1444
+ console.error(pc8.green(`\u2713 Default social set saved: ${defaultSocialSetId}`));
1169
1445
  } else if (noDefault) {
1170
- console.error(pc7.dim("Skipping default social set configuration."));
1446
+ console.error(pc8.dim("Skipping default social set configuration."));
1171
1447
  } else {
1172
1448
  let socialSets = null;
1173
1449
  const origKey = process.env.TYPEFULLY_API_KEY;
@@ -1177,15 +1453,15 @@ function registerSetupCommand(program2) {
1177
1453
  exitOnError: false
1178
1454
  });
1179
1455
  } catch (err) {
1180
- console.error(pc7.yellow(`\u26A0 Could not fetch social sets: ${err.message}`));
1456
+ console.error(pc8.yellow(`\u26A0 Could not fetch social sets: ${err.message}`));
1181
1457
  }
1182
1458
  if (origKey) process.env.TYPEFULLY_API_KEY = origKey;
1183
1459
  else delete process.env.TYPEFULLY_API_KEY;
1184
1460
  if (socialSets) {
1185
1461
  const results = socialSets.results;
1186
1462
  if (!results || results.length === 0) {
1187
- console.error(pc7.yellow("\u26A0 No social sets found."));
1188
- console.error(pc7.dim("Connect a social account at typefully.com"));
1463
+ console.error(pc8.yellow("\u26A0 No social sets found."));
1464
+ console.error(pc8.dim("Connect a social account at typefully.com"));
1189
1465
  } else if (results.length === 1) {
1190
1466
  const firstSet = results[0];
1191
1467
  defaultSocialSetId = firstSet.id;
@@ -1193,19 +1469,19 @@ function registerSetupCommand(program2) {
1193
1469
  writeConfig(configPath, { ...updatedConfig, defaultSocialSetId });
1194
1470
  const name = String(firstSet.name || "Unnamed");
1195
1471
  const username = firstSet.username ? ` @${firstSet.username}` : "";
1196
- console.error(pc7.green(`\u2713 Default social set: ${pc7.bold(name)}${pc7.dim(username)}`));
1472
+ console.error(pc8.green(`\u2713 Default social set: ${pc8.bold(name)}${pc8.dim(username)}`));
1197
1473
  } else if (isNonInteractive) {
1198
1474
  console.error(
1199
- pc7.blue(
1475
+ pc8.blue(
1200
1476
  `\u2192 Found ${results.length} social sets. Use --default-social-set <id> to set one as default.`
1201
1477
  )
1202
1478
  );
1203
1479
  } else {
1204
1480
  const formatted = formatSocialSetsForDisplay(results);
1205
1481
  console.error("");
1206
- console.error(pc7.bold("Choose a default social set"));
1482
+ console.error(pc8.bold("Choose a default social set"));
1207
1483
  console.error(
1208
- pc7.dim("This will be used when you don't specify one. You can always override it.")
1484
+ pc8.dim("This will be used when you don't specify one. You can always override it.")
1209
1485
  );
1210
1486
  console.error("");
1211
1487
  for (const f of formatted) console.error(f.displayLine);
@@ -1219,7 +1495,7 @@ function registerSetupCommand(program2) {
1219
1495
  defaultSocialSetId = formatted[choiceNum - 1]?.set.id;
1220
1496
  const updatedConfig = readConfigFile(configPath) ?? {};
1221
1497
  writeConfig(configPath, { ...updatedConfig, defaultSocialSetId });
1222
- console.error(pc7.green("\u2713 Default social set saved"));
1498
+ console.error(pc8.green("\u2713 Default social set saved"));
1223
1499
  }
1224
1500
  }
1225
1501
  }
@@ -1236,36 +1512,36 @@ function registerSetupCommand(program2) {
1236
1512
  }
1237
1513
 
1238
1514
  // src/commands/social-sets.ts
1239
- import pc8 from "picocolors";
1515
+ import pc9 from "picocolors";
1240
1516
  var PLATFORM_ORDER = ["x", "linkedin", "threads", "bluesky", "mastodon"];
1241
1517
  function renderPlatforms(platforms, indent = " ") {
1242
- for (const p of PLATFORM_ORDER) {
1243
- const cfg = platforms[p];
1518
+ for (const p3 of PLATFORM_ORDER) {
1519
+ const cfg = platforms[p3];
1244
1520
  if (!cfg) continue;
1245
1521
  const isConnected = cfg.connected !== false;
1246
- const dot = isConnected ? pc8.green("\u25CF") : pc8.dim("\u25CB");
1247
- const handle = cfg.username ? pc8.dim(` @${cfg.username}`) : "";
1248
- const status = isConnected ? "" : pc8.dim(" (not connected)");
1249
- console.log(`${indent}${dot} ${p.padEnd(9)}${handle}${status}`);
1522
+ const dot = isConnected ? pc9.green("\u25CF") : pc9.dim("\u25CB");
1523
+ const handle = cfg.username ? pc9.dim(` @${cfg.username}`) : "";
1524
+ const status = isConnected ? "" : pc9.dim(" (not connected)");
1525
+ console.log(`${indent}${dot} ${p3.padEnd(9)}${handle}${status}`);
1250
1526
  }
1251
- for (const [p, cfg] of Object.entries(platforms)) {
1252
- if (PLATFORM_ORDER.includes(p)) continue;
1527
+ for (const [p3, cfg] of Object.entries(platforms)) {
1528
+ if (PLATFORM_ORDER.includes(p3)) continue;
1253
1529
  const isConnected = cfg.connected !== false;
1254
- const dot = isConnected ? pc8.green("\u25CF") : pc8.dim("\u25CB");
1255
- const handle = cfg.username ? pc8.dim(` @${cfg.username}`) : "";
1256
- console.log(`${indent}${dot} ${p.padEnd(9)}${handle}`);
1530
+ const dot = isConnected ? pc9.green("\u25CF") : pc9.dim("\u25CB");
1531
+ const handle = cfg.username ? pc9.dim(` @${cfg.username}`) : "";
1532
+ console.log(`${indent}${dot} ${p3.padEnd(9)}${handle}`);
1257
1533
  }
1258
1534
  }
1259
1535
  function renderSocialSetCard(set, index) {
1260
1536
  const team = set.team;
1261
1537
  const platforms = set.platforms;
1262
- const prefix = index != null ? `${pc8.dim(`${String(index + 1)}.`)} ` : " ";
1263
- const name = pc8.bold(String(set.name ?? "Unnamed"));
1264
- const username = set.username ? pc8.dim(` @${set.username}`) : "";
1265
- const teamLabel = team ? pc8.dim(` [${team.name}]`) : "";
1538
+ const prefix = index != null ? `${pc9.dim(`${String(index + 1)}.`)} ` : " ";
1539
+ const name = pc9.bold(String(set.name ?? "Unnamed"));
1540
+ const username = set.username ? pc9.dim(` @${set.username}`) : "";
1541
+ const teamLabel = team ? pc9.dim(` [${team.name}]`) : "";
1266
1542
  console.log(`${prefix}${name}${username}${teamLabel}`);
1267
1543
  console.log(
1268
- pc8.dim(` ID: ${set.id} \xB7 ${team ? `Team: ${team.name}${team.id ? ` (ID: ${team.id})` : ""}` : "Personal"}`)
1544
+ pc9.dim(` ID: ${set.id} \xB7 ${team ? `Team: ${team.name}${team.id ? ` (ID: ${team.id})` : ""}` : "Personal"}`)
1269
1545
  );
1270
1546
  if (platforms && Object.keys(platforms).length > 0) {
1271
1547
  console.log("");
@@ -1276,12 +1552,12 @@ function renderSocialSetsList(data) {
1276
1552
  const results = data.results ?? [];
1277
1553
  const total = data.total ?? results.length;
1278
1554
  if (results.length === 0) {
1279
- console.log(pc8.yellow("\n No social sets found."));
1280
- console.log(pc8.dim(" Connect a social account at typefully.com\n"));
1555
+ console.log(pc9.yellow("\n No social sets found."));
1556
+ console.log(pc9.dim(" Connect a social account at typefully.com\n"));
1281
1557
  return;
1282
1558
  }
1283
1559
  console.log("");
1284
- console.log(pc8.dim(` ${total} social set${total !== 1 ? "s" : ""}`));
1560
+ console.log(pc9.dim(` ${total} social set${total !== 1 ? "s" : ""}`));
1285
1561
  for (let i = 0; i < results.length; i++) {
1286
1562
  console.log("");
1287
1563
  renderSocialSetCard(results[i], i);
@@ -1296,38 +1572,38 @@ function renderSocialSet(data) {
1296
1572
  function registerSocialSetsCommand(program2) {
1297
1573
  const cmd = program2.command("social-sets").description("Manage social sets");
1298
1574
  cmd.command("list").description("List all social sets").action(async () => {
1299
- const spinner = spin("Fetching social sets\u2026");
1300
- spinner.start();
1575
+ const spinner3 = spin("Fetching social sets\u2026");
1576
+ spinner3.start();
1301
1577
  const data = await apiRequest("GET", "/social-sets?limit=50");
1302
- spinner.stop();
1578
+ spinner3.stop();
1303
1579
  display(data, () => renderSocialSetsList(data));
1304
1580
  });
1305
1581
  cmd.command("get").description("Get social set details").argument("[social_set_id]", "Social set ID (uses default if omitted)").action(async (socialSetId) => {
1306
1582
  const id = requireSocialSetId(socialSetId ?? null);
1307
- const spinner = spin("Fetching social set\u2026");
1308
- spinner.start();
1583
+ const spinner3 = spin("Fetching social set\u2026");
1584
+ spinner3.start();
1309
1585
  const data = await apiRequest("GET", `/social-sets/${id}`);
1310
- spinner.stop();
1586
+ spinner3.stop();
1311
1587
  display(data, () => renderSocialSet(data));
1312
1588
  });
1313
1589
  }
1314
1590
 
1315
1591
  // src/commands/tags.ts
1316
- import pc9 from "picocolors";
1592
+ import pc10 from "picocolors";
1317
1593
  function renderTagsList(data) {
1318
1594
  const results = data.results ?? [];
1319
1595
  if (results.length === 0) {
1320
- console.log(pc9.yellow("\n No tags found.\n"));
1596
+ console.log(pc10.yellow("\n No tags found.\n"));
1321
1597
  return;
1322
1598
  }
1323
1599
  console.log("");
1324
- console.log(pc9.dim(` ${results.length} tag${results.length !== 1 ? "s" : ""}`));
1600
+ console.log(pc10.dim(` ${results.length} tag${results.length !== 1 ? "s" : ""}`));
1325
1601
  console.log("");
1326
1602
  for (let i = 0; i < results.length; i++) {
1327
1603
  const tag = results[i];
1328
- const num = pc9.dim(`${String(i + 1)}.`.padStart(3));
1329
- const name = pc9.bold(String(tag.name ?? ""));
1330
- const slug = tag.slug ? pc9.dim(` (${tag.slug})`) : "";
1604
+ const num = pc10.dim(`${String(i + 1)}.`.padStart(3));
1605
+ const name = pc10.bold(String(tag.name ?? ""));
1606
+ const slug = tag.slug ? pc10.dim(` (${tag.slug})`) : "";
1331
1607
  console.log(` ${num} ${name}${slug}`);
1332
1608
  }
1333
1609
  console.log("");
@@ -1336,47 +1612,37 @@ function registerTagsCommand(program2) {
1336
1612
  const cmd = program2.command("tags").description("Manage tags");
1337
1613
  cmd.command("list").description("List all tags").argument("[social_set_id]", "Social set ID (uses default if omitted)").action(async (socialSetId) => {
1338
1614
  const id = requireSocialSetId(socialSetId ?? null);
1339
- const spinner = spin("Fetching tags\u2026");
1340
- spinner.start();
1615
+ const spinner3 = spin("Fetching tags\u2026");
1616
+ spinner3.start();
1341
1617
  const data = await apiRequest("GET", `/social-sets/${id}/tags?limit=50`);
1342
- spinner.stop();
1618
+ spinner3.stop();
1343
1619
  display(data, () => renderTagsList(data));
1344
1620
  });
1345
1621
  cmd.command("create").description("Create a new tag").argument("[social_set_id]", "Social set ID (uses default if omitted)").option("--name <name>", "Tag name (required)").action(async (socialSetId, opts) => {
1346
1622
  const id = requireSocialSetId(socialSetId ?? null);
1347
1623
  if (!opts.name) exitWithError("--name is required");
1348
- const spinner = spin("Creating tag\u2026");
1349
- spinner.start();
1624
+ const spinner3 = spin("Creating tag\u2026");
1625
+ spinner3.start();
1350
1626
  const data = await apiRequest("POST", `/social-sets/${id}/tags`, { name: opts.name });
1351
- spinner.stop();
1627
+ spinner3.stop();
1352
1628
  display(data, () => {
1353
1629
  const tag = data;
1354
1630
  console.log("");
1355
- console.log(` ${pc9.green("\u2713")} Tag created: ${pc9.bold(String(tag.name ?? opts.name))}`);
1356
- if (tag.slug) console.log(pc9.dim(` Slug: ${tag.slug}`));
1631
+ console.log(` ${pc10.green("\u2713")} Tag created: ${pc10.bold(String(tag.name ?? opts.name))}`);
1632
+ if (tag.slug) console.log(pc10.dim(` Slug: ${tag.slug}`));
1357
1633
  console.log("");
1358
1634
  });
1359
1635
  });
1360
1636
  }
1361
1637
 
1362
1638
  // src/utils/banner.ts
1363
- import pc10 from "picocolors";
1364
- var BANNER_WIDE = `
1365
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
1366
- \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D
1367
- \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D
1368
- \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D
1369
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551
1370
- \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D
1371
- `;
1372
- var BANNER_COMPACT = `
1373
- \u2580\u2588\u2580 \u2588\u2584\u2588 \u2588\u2580\u2588 \u2588\u2580\u2580 \u2588\u2580\u2580 \u2588 \u2588 \u2588 \u2588 \u2588\u2584\u2588
1374
- \u2588 \u2588 \u2588\u2580\u2580 \u2588\u2588\u2584 \u2588\u2580 \u2588\u2584\u2588 \u2588\u2584\u2584 \u2588\u2584\u2584 \u2588
1639
+ import pc11 from "picocolors";
1640
+ var BANNER = `
1641
+ \u281B\u28FF\u281B \u28FF\u28E4\u28FF \u28FF\u281B\u28FF \u28FF\u281B\u281B \u28FF\u281B\u281B \u28FF \u28FF \u28FF \u28FF \u28FF\u28E4\u28FF
1642
+ \u28FF \u28FF \u28FF\u281B\u281B \u28FF\u28FF\u28E4 \u28FF\u281B \u28FF\u28E4\u28FF \u28FF\u28E4\u28E4 \u28FF\u28E4\u28E4 \u28FF
1375
1643
  `;
1376
1644
  function showBanner() {
1377
- const cols = process.stdout.columns || 80;
1378
- const banner = cols >= 80 ? BANNER_WIDE : BANNER_COMPACT;
1379
- console.error(pc10.dim(banner));
1645
+ console.error(pc11.dim(BANNER));
1380
1646
  }
1381
1647
 
1382
1648
  // src/cli.ts
@@ -1384,7 +1650,9 @@ var require2 = createRequire(import.meta.url);
1384
1650
  var pkg = require2("../package.json");
1385
1651
  function createCli() {
1386
1652
  const program2 = new Command();
1387
- program2.name("typefully").description("Manage social media posts via the Typefully API").version(pkg.version, "-v, --version").option("-j, --json", "Output raw JSON instead of human-readable text").hook("preAction", (_thisCommand) => {
1653
+ program2.name("typefully").description("Manage social media posts via the Typefully API").version(pkg.version, "-v, --version").option("-j, --json", "Output raw JSON instead of human-readable text").argument("[text]", "Post text \u2014 creates a draft directly, or omit for interactive mode").action(async (text5) => {
1654
+ await runDraft(text5);
1655
+ }).hook("preAction", (_thisCommand) => {
1388
1656
  const jsonMode = !!program2.opts().json;
1389
1657
  setJsonMode(jsonMode);
1390
1658
  const isVersion = process.argv.includes("-v") || process.argv.includes("--version");