chirpie 1.1.2 → 1.1.3

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 +4 -0
  2. package/dist/index.js +241 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -130,6 +130,10 @@ chirpie posts --group 550e8400-e29b-41d4-a716-446655440000
130
130
 
131
131
  One line is printed per account, with a group ID above them. An account the platform refused is shown with its error while the others stay published, and the command leaves with a non-zero status when any account failed. An override says only what differs, and naming media replaces the shared media for that account.
132
132
 
133
+ ## IDs
134
+
135
+ Listings print a short ID, the first eight characters of the full one, and every command that takes an account, post, key or comment ID accepts it: `chirpie accounts disconnect 90208e20`, `chirpie posts delete 2e7e8e95`, `-a 90208e20` when posting. A short ID has to name exactly one of your accounts, posts, keys or comments; when it matches none or several, the command stops and says so, and the full ID from `--json` always works.
136
+
133
137
  ## JSON output
134
138
 
135
139
  Every command accepts `--json` and prints the raw API response, which is what you want in a script:
package/dist/index.js CHANGED
@@ -278,6 +278,151 @@ function printFanOut(groupId, lines) {
278
278
  return failed.length;
279
279
  }
280
280
 
281
+ // src/lib/ids.ts
282
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
283
+ var MAX_SEARCHED = 1e3;
284
+ var IdResolutionError = class extends Error {
285
+ constructor(message) {
286
+ super(message);
287
+ this.name = "IdResolutionError";
288
+ }
289
+ };
290
+ function isFullUuid(value) {
291
+ return UUID.test(value);
292
+ }
293
+ var UUID_PREFIX = /^[0-9a-f-]+$/i;
294
+ function needsLookup(value) {
295
+ const trimmed = value.trim();
296
+ return UUID_PREFIX.test(trimmed) && !isFullUuid(trimmed);
297
+ }
298
+ async function resolveId(input, kind, pages) {
299
+ const wanted = input.trim();
300
+ if (!wanted) {
301
+ throw new IdResolutionError(`Pass the ${kind.noun} ID.`);
302
+ }
303
+ if (!needsLookup(wanted)) return wanted;
304
+ const needle = wanted.toLowerCase();
305
+ const matches = /* @__PURE__ */ new Set();
306
+ for await (const ids of pages()) {
307
+ for (const id of ids) {
308
+ if (id === wanted) return id;
309
+ if (id.toLowerCase().startsWith(needle)) matches.add(id);
310
+ }
311
+ }
312
+ if (matches.size === 1) return [...matches][0];
313
+ if (matches.size > 1) {
314
+ const list = [...matches].sort().join(", ");
315
+ throw new IdResolutionError(
316
+ `"${wanted}" matches ${matches.size} ${kind.noun}s (${list}). Use more characters of the ID.`
317
+ );
318
+ }
319
+ throw new IdResolutionError(
320
+ `No ${kind.noun} ID starts with "${wanted}". Run \`${kind.listCommand}\` to see them, or pass the full ID.`
321
+ );
322
+ }
323
+ function resolveIdIn(input, kind, ids) {
324
+ return resolveId(input, kind, async function* () {
325
+ yield ids;
326
+ });
327
+ }
328
+ var ACCOUNT = { noun: "account", listCommand: "chirpie accounts" };
329
+ var POST = { noun: "post", listCommand: "chirpie posts" };
330
+ var KEY = { noun: "API key", listCommand: "chirpie keys" };
331
+ function accountIdResolver(client) {
332
+ let listing = null;
333
+ return async (input) => {
334
+ if (!needsLookup(input) && input.trim()) return input.trim();
335
+ listing ??= client.listAccounts().then((accounts) => accounts.map((a) => a.id));
336
+ return resolveIdIn(input, ACCOUNT, await listing);
337
+ };
338
+ }
339
+ function resolveAccountId(client, input) {
340
+ return accountIdResolver(client)(input);
341
+ }
342
+ async function resolveAccountKeys(resolve2, byAccount) {
343
+ if (!byAccount) return void 0;
344
+ const out = {};
345
+ for (const [key, value] of Object.entries(byAccount)) {
346
+ const id = await resolve2(key);
347
+ if (id in out) {
348
+ throw new IdResolutionError(
349
+ `--config names account ${id} twice. Give each account one entry.`
350
+ );
351
+ }
352
+ out[id] = value;
353
+ }
354
+ return out;
355
+ }
356
+ function resolvePostId(client, input) {
357
+ const pageSize = 100;
358
+ return resolveId(
359
+ input,
360
+ POST,
361
+ async function* () {
362
+ for (const status of [void 0, "draft"]) {
363
+ for (let offset = 0; offset < MAX_SEARCHED; offset += pageSize) {
364
+ const page = await client.listPosts({
365
+ ...status ? { status } : {},
366
+ limit: pageSize,
367
+ offset,
368
+ include_hidden: true
369
+ });
370
+ yield page.map((p) => p.id);
371
+ if (page.length < pageSize) break;
372
+ }
373
+ }
374
+ }
375
+ );
376
+ }
377
+ async function resolveKeyId(client, input) {
378
+ if (!needsLookup(input) && input.trim()) return input.trim();
379
+ const keys = await client.listKeys();
380
+ return resolveIdIn(
381
+ input,
382
+ KEY,
383
+ keys.map((k) => k.id)
384
+ );
385
+ }
386
+ function resolveCommentId(client, postId, input) {
387
+ const pageSize = 100;
388
+ return resolveId(
389
+ input,
390
+ {
391
+ noun: "comment",
392
+ listCommand: `chirpie comments list ${postId}`
393
+ },
394
+ async function* () {
395
+ let cursor;
396
+ for (let read = 0; read < MAX_SEARCHED; read += pageSize) {
397
+ const page = await client.listComments(postId, {
398
+ limit: pageSize,
399
+ ...cursor ? { cursor } : {},
400
+ sync: false,
401
+ include_hidden: true,
402
+ include_deleted: true
403
+ });
404
+ yield page.comments.map((c) => c.id);
405
+ if (!page.next_cursor) break;
406
+ cursor = page.next_cursor;
407
+ }
408
+ }
409
+ );
410
+ }
411
+ async function resolveCommentIds(client, postInput, commentInput) {
412
+ const postId = await resolvePostId(client, postInput);
413
+ const commentId = await resolveCommentId(client, postId, commentInput);
414
+ return { postId, commentId };
415
+ }
416
+ async function resolveNamedAccounts(client, named, configurations) {
417
+ const resolve2 = accountIdResolver(client);
418
+ const accountIds = [];
419
+ for (const input of named) accountIds.push(await resolve2(input));
420
+ return {
421
+ accountIds,
422
+ configurations: await resolveAccountKeys(resolve2, configurations)
423
+ };
424
+ }
425
+
281
426
  // src/lib/placement.ts
282
427
  var INSTAGRAM_PLACEMENT_HELP = 'Where the post goes on Instagram: feed (the default), story or reel. A story takes exactly one image or video, no caption (pass "" as the text) and no first comment. A reel takes exactly one video. Only pass this when the post is going to an Instagram account';
283
428
  var FACEBOOK_PLACEMENT_HELP = "Where the post goes on a Facebook Page: feed (the default) or story. A story takes exactly one image or video, no text and no first comment. Only pass this when the post is going to a Facebook Page";
@@ -329,7 +474,19 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
329
474
  error("--config names accounts, so pass --account <id> for each of them");
330
475
  process.exit(1);
331
476
  }
332
- const accountIds = named.length > 0 ? named : [await resolveSingleAccountId(client)];
477
+ const givenConfigurations = parseAccountConfigurations(opts.config);
478
+ let resolved;
479
+ try {
480
+ resolved = await resolveNamedAccounts(
481
+ client,
482
+ named,
483
+ givenConfigurations
484
+ );
485
+ } catch (err) {
486
+ error(err instanceof Error ? err.message : "Failed to find the accounts");
487
+ process.exit(1);
488
+ }
489
+ const accountIds = named.length > 0 ? resolved.accountIds : [await resolveSingleAccountId(client)];
333
490
  const fanOut = named.length > 1 || Boolean(opts.config);
334
491
  const configuration = placementConfiguration(opts);
335
492
  try {
@@ -341,11 +498,7 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
341
498
  ...media ? { media } : {},
342
499
  ...opts.firstComment ? { first_comment: opts.firstComment } : {},
343
500
  ...configuration ? { configuration } : {},
344
- ...opts.config ? {
345
- account_configurations: parseAccountConfigurations(
346
- opts.config
347
- )
348
- } : {},
501
+ ...resolved.configurations ? { account_configurations: resolved.configurations } : {},
349
502
  schedule_at: opts.schedule,
350
503
  ...opts.timezone ? { timezone: opts.timezone } : {},
351
504
  ...opts.draft ? { draft: true } : {}
@@ -501,13 +654,21 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
501
654
  }
502
655
  }
503
656
  }
504
- const configurations = parseAccountConfigurations(opts.config) ?? fileConfigurations;
657
+ const givenConfigurations = parseAccountConfigurations(opts.config) ?? fileConfigurations;
505
658
  const named = opts.account ?? fileAccountIds ?? [];
506
- if (configurations && named.length === 0) {
659
+ if (givenConfigurations && named.length === 0) {
507
660
  error("Per-account posts name accounts, so pass --account <id> for each of them");
508
661
  process.exit(1);
509
662
  }
510
- const accountIds = named.length > 0 ? named : [await resolveSingleAccountId(client)];
663
+ let resolved;
664
+ try {
665
+ resolved = await resolveNamedAccounts(client, named, givenConfigurations);
666
+ } catch (err) {
667
+ error(err instanceof Error ? err.message : "Failed to find the accounts");
668
+ process.exit(1);
669
+ }
670
+ const configurations = resolved.configurations;
671
+ const accountIds = named.length > 0 ? resolved.accountIds : [await resolveSingleAccountId(client)];
511
672
  const fanOut = named.length > 1 || Boolean(configurations);
512
673
  try {
513
674
  if (fanOut) {
@@ -569,15 +730,16 @@ import { postFailureReason } from "@chirpie/sdk";
569
730
  var postsCommand = new Command5("posts").description("List, get, update, delete, hide or unhide posts").option(
570
731
  "--status <status>",
571
732
  "Filter by status: draft, scheduled, publishing, published, failed, deleted"
572
- ).option("--account <id>", "Filter by account").option(
733
+ ).option("--account <id>", "Filter by account. The short ID from `chirpie accounts` works").option(
573
734
  "--group <id>",
574
735
  "Only the posts of one fan-out, from the group ID a multi-account post returned"
575
736
  ).option("--limit <n>", "Limit results", "20").option("--include-hidden", "Include posts you have hidden").option("--json", "Output raw JSON").action(async (opts, cmd) => {
576
737
  const client = getClient();
577
738
  try {
739
+ const accountId = opts.account ? await resolveAccountId(client, opts.account) : void 0;
578
740
  const posts = await client.listPosts({
579
741
  status: opts.status,
580
- account_id: opts.account,
742
+ account_id: accountId,
581
743
  group_id: opts.group,
582
744
  limit: parseInt(opts.limit),
583
745
  include_hidden: opts.includeHidden
@@ -606,7 +768,8 @@ var postsCommand = new Command5("posts").description("List, get, update, delete,
606
768
  postsCommand.command("get <id>").description("Get a post by ID").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
607
769
  const client = getClient();
608
770
  try {
609
- const post = await client.getPost(id);
771
+ const postId = await resolvePostId(client, id);
772
+ const post = await client.getPost(postId);
610
773
  if (wantsJson(opts, cmd)) {
611
774
  json(post);
612
775
  } else {
@@ -673,8 +836,9 @@ postsCommand.command("update <id>").description(
673
836
  const configuration = opts.clearPlacement ? {} : placementConfiguration(opts);
674
837
  const client = getClient();
675
838
  try {
839
+ const postId = await resolvePostId(client, id);
676
840
  const media = await resolveMediaArgs(client, opts.media, opts.alt);
677
- const post = await client.updatePost(id, {
841
+ const post = await client.updatePost(postId, {
678
842
  ...opts.text !== void 0 ? { text: opts.text } : {},
679
843
  ...opts.clearMedia ? { media: [] } : {},
680
844
  ...media ? { media } : {},
@@ -705,14 +869,15 @@ postsCommand.command("update <id>").description(
705
869
  postsCommand.command("publish <id>").description("Publish a draft now. Every rule and the monthly quota apply").option("-t, --text <text>", "Final post text, saved before publishing").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
706
870
  const client = getClient();
707
871
  try {
708
- const post = await client.updatePost(id, {
872
+ const postId = await resolvePostId(client, id);
873
+ const post = await client.updatePost(postId, {
709
874
  publish: true,
710
875
  ...opts.text !== void 0 ? { text: opts.text } : {}
711
876
  });
712
877
  if (wantsJson(opts, cmd)) {
713
878
  json(post);
714
879
  } else {
715
- success(`Draft ${id} published (${post.id})`);
880
+ success(`Draft ${postId} published (${post.id})`);
716
881
  }
717
882
  } catch (err) {
718
883
  apiError(err, "Failed to publish draft");
@@ -724,7 +889,8 @@ postsCommand.command("delete <id>").description(
724
889
  ).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
725
890
  const client = getClient();
726
891
  try {
727
- const result = await client.deletePost(id);
892
+ const postId = await resolvePostId(client, id);
893
+ const result = await client.deletePost(postId);
728
894
  const cancelled = result.cancelled_ids ?? [];
729
895
  if (wantsJson(opts, cmd)) {
730
896
  json(result);
@@ -733,7 +899,7 @@ postsCommand.command("delete <id>").description(
733
899
  `Scheduled thread canceled (${cancelled.length} posts): ${cancelled.join(", ")}`
734
900
  );
735
901
  } else {
736
- success(`Post ${id} deleted`);
902
+ success(`Post ${postId} deleted`);
737
903
  }
738
904
  } catch (err) {
739
905
  error(err instanceof Error ? err.message : "Failed to delete post");
@@ -748,7 +914,8 @@ postsCommand.command("first-comment <id>").description(
748
914
  ).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
749
915
  const client = getClient();
750
916
  try {
751
- const post = await client.retryFirstComment(id, idempotency(opts));
917
+ const postId = await resolvePostId(client, id);
918
+ const post = await client.retryFirstComment(postId, idempotency(opts));
752
919
  if (wantsJson(opts, cmd)) {
753
920
  json(post);
754
921
  } else {
@@ -764,13 +931,14 @@ postsCommand.command("hide <id>").description(
764
931
  ).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
765
932
  const client = getClient();
766
933
  try {
767
- const result = await client.hidePost(id);
934
+ const postId = await resolvePostId(client, id);
935
+ const result = await client.hidePost(postId);
768
936
  if (wantsJson(opts, cmd)) {
769
937
  json(result);
770
938
  } else {
771
939
  const n = result.hidden_ids.length;
772
940
  success(
773
- n > 1 ? `Hidden from Chirpie (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${id} hidden from Chirpie. It is unchanged on the platform.`
941
+ n > 1 ? `Hidden from Chirpie (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${postId} hidden from Chirpie. It is unchanged on the platform.`
774
942
  );
775
943
  }
776
944
  } catch (err) {
@@ -781,13 +949,14 @@ postsCommand.command("hide <id>").description(
781
949
  postsCommand.command("unhide <id>").description("Put a hidden post back in your Chirpie listings").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
782
950
  const client = getClient();
783
951
  try {
784
- const result = await client.unhidePost(id);
952
+ const postId = await resolvePostId(client, id);
953
+ const result = await client.unhidePost(postId);
785
954
  if (wantsJson(opts, cmd)) {
786
955
  json(result);
787
956
  } else {
788
957
  const n = result.hidden_ids.length;
789
958
  success(
790
- n > 1 ? `Showing again (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${id} is showing again`
959
+ n > 1 ? `Showing again (${n} posts): ${result.hidden_ids.join(", ")}` : `Post ${postId} is showing again`
791
960
  );
792
961
  }
793
962
  } catch (err) {
@@ -887,10 +1056,11 @@ function inactiveNote(reason) {
887
1056
  }
888
1057
  return "";
889
1058
  }
890
- accountsCommand.command("activate").argument("<id>", "Account ID (from `chirpie accounts`)").description("Activate a connected account so it can publish").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
1059
+ accountsCommand.command("activate").argument("<id>", "Account ID, full or the short ID from `chirpie accounts`").description("Activate a connected account so it can publish").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
891
1060
  const client = getClient();
892
1061
  try {
893
- const account = await client.activateAccount(id);
1062
+ const accountId = await resolveAccountId(client, id);
1063
+ const account = await client.activateAccount(accountId);
894
1064
  if (wantsJson(opts, cmd)) {
895
1065
  json(account);
896
1066
  } else {
@@ -901,12 +1071,13 @@ accountsCommand.command("activate").argument("<id>", "Account ID (from `chirpie
901
1071
  process.exit(1);
902
1072
  }
903
1073
  });
904
- accountsCommand.command("deactivate").argument("<id>", "Account ID (from `chirpie accounts`)").description(
1074
+ accountsCommand.command("deactivate").argument("<id>", "Account ID, full or the short ID from `chirpie accounts`").description(
905
1075
  "Deactivate a connected account (stays connected, frees a plan slot, cancels its scheduled posts)"
906
1076
  ).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
907
1077
  const client = getClient();
908
1078
  try {
909
- const account = await client.deactivateAccount(id);
1079
+ const accountId = await resolveAccountId(client, id);
1080
+ const account = await client.deactivateAccount(accountId);
910
1081
  if (wantsJson(opts, cmd)) {
911
1082
  json(account);
912
1083
  } else {
@@ -924,20 +1095,27 @@ accountsCommand.command("deactivate").argument("<id>", "Account ID (from `chirpi
924
1095
  process.exit(1);
925
1096
  }
926
1097
  });
927
- accountsCommand.command("disconnect").argument("<id>", "Account ID (from `chirpie accounts`)").description(
1098
+ accountsCommand.command("disconnect").argument("<id>", "Account ID, full or the short ID from `chirpie accounts`").description(
928
1099
  "Disconnect a connected account (stops publishing, frees a plan slot, cancels its scheduled posts)"
929
1100
  ).option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
930
- if (!opts.yes && !await confirm(`Disconnect account ${id}?`)) {
1101
+ const client = getClient();
1102
+ let accountId;
1103
+ try {
1104
+ accountId = await resolveAccountId(client, id);
1105
+ } catch (err) {
1106
+ error(err instanceof Error ? err.message : "Failed to find account");
1107
+ process.exit(1);
1108
+ }
1109
+ if (!opts.yes && !await confirm(`Disconnect account ${accountId}?`)) {
931
1110
  error("Cancelled");
932
1111
  process.exit(1);
933
1112
  }
934
- const client = getClient();
935
1113
  try {
936
- const result = await client.disconnectAccount(id);
1114
+ const result = await client.disconnectAccount(accountId);
937
1115
  if (wantsJson(opts, cmd)) {
938
1116
  json(result);
939
1117
  } else {
940
- success(`Disconnected account ${id}`);
1118
+ success(`Disconnected account ${accountId}`);
941
1119
  if (result.canceled_posts > 0) {
942
1120
  success(
943
1121
  `Cancelled ${result.canceled_posts} scheduled post${result.canceled_posts === 1 ? "" : "s"} and returned them to your monthly quota. They are not restored if you connect the account again.`
@@ -1270,13 +1448,13 @@ function commentState(comment) {
1270
1448
  return "";
1271
1449
  }
1272
1450
  var commentsCommand = new Command7("comments").description("List, reply to, hide, or delete the comments on a post").option("--json", "Output raw JSON");
1273
- commentsCommand.command("list").description("List the comments on a post you published").argument("<post_id>", "Post ID").option("--limit <n>", "Comments to show (default 25, max 100)").option("--cursor <cursor>", "The next_cursor from the previous page").option("--since <datetime>", "Only comments posted after this ISO 8601 time").option("--no-sync", "Show the stored comments without refreshing them").option("--include-hidden", "Include comments you have hidden").option(
1451
+ commentsCommand.command("list").description("List the comments on a post you published").argument("<post_id>", "Post ID, full or the short ID from `chirpie posts`").option("--limit <n>", "Comments to show (default 25, max 100)").option("--cursor <cursor>", "The next_cursor from the previous page").option("--since <datetime>", "Only comments posted after this ISO 8601 time").option("--no-sync", "Show the stored comments without refreshing them").option("--include-hidden", "Include comments you have hidden").option(
1274
1452
  "--include-deleted",
1275
1453
  "Include deleted comments, with the last text Chirpie stored for them"
1276
1454
  ).addOption(new Option("--hidden").hideHelp()).option("--json", "Output raw JSON").action(async (postId, opts, cmd) => {
1277
1455
  const client = getClient();
1278
1456
  try {
1279
- const result = await client.listComments(postId, {
1457
+ const result = await client.listComments(await resolvePostId(client, postId), {
1280
1458
  limit: opts.limit ? parseInt(opts.limit, 10) : void 0,
1281
1459
  cursor: opts.cursor,
1282
1460
  since: opts.since,
@@ -1312,54 +1490,63 @@ commentsCommand.command("list").description("List the comments on a post you pub
1312
1490
  process.exit(1);
1313
1491
  }
1314
1492
  });
1315
- commentsCommand.command("reply").description("Reply to a comment (counts as one post against your quota)").argument("<post_id>", "Post ID").argument("<comment_id>", "Comment ID from `chirpie comments list`").requiredOption("--text <text>", "The reply text").option(
1493
+ commentsCommand.command("reply").description("Reply to a comment (counts as one post against your quota)").argument("<post_id>", "Post ID, full or the short ID from `chirpie posts`").argument("<comment_id>", "Comment ID, full or the short ID from `chirpie comments list`").requiredOption("--text <text>", "The reply text").option(
1316
1494
  "--idempotency-key <key>",
1317
1495
  "Make a retry safe. The same key with the same reply replays the first answer instead of sending it again, for 24 hours"
1318
1496
  ).option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
1319
1497
  const client = getClient();
1320
1498
  try {
1499
+ const ids = await resolveCommentIds(client, postId, commentId);
1321
1500
  const reply = await client.replyToComment(
1322
- postId,
1323
- commentId,
1501
+ ids.postId,
1502
+ ids.commentId,
1324
1503
  opts.text,
1325
1504
  idempotency(opts)
1326
1505
  );
1327
1506
  if (wantsJson(opts, cmd)) {
1328
1507
  json(reply);
1329
1508
  } else {
1330
- success(`Replied to ${commentId}`);
1509
+ success(`Replied to ${ids.commentId}`);
1331
1510
  }
1332
1511
  } catch (err) {
1333
1512
  apiError(err, "Failed to reply to comment");
1334
1513
  process.exit(1);
1335
1514
  }
1336
1515
  });
1337
- commentsCommand.command("hide").description("Hide a comment, or show it again with --unhide").argument("<post_id>", "Post ID").argument("<comment_id>", "Comment ID from `chirpie comments list`").option("--unhide", "Show a comment you hid earlier").option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
1516
+ commentsCommand.command("hide").description("Hide a comment, or show it again with --unhide").argument("<post_id>", "Post ID, full or the short ID from `chirpie posts`").argument("<comment_id>", "Comment ID, full or the short ID from `chirpie comments list`").option("--unhide", "Show a comment you hid earlier").option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
1338
1517
  const client = getClient();
1339
1518
  try {
1340
- const updated = opts.unhide ? await client.unhideComment(postId, commentId) : await client.hideComment(postId, commentId);
1519
+ const ids = await resolveCommentIds(client, postId, commentId);
1520
+ const updated = opts.unhide ? await client.unhideComment(ids.postId, ids.commentId) : await client.hideComment(ids.postId, ids.commentId);
1341
1521
  if (wantsJson(opts, cmd)) {
1342
1522
  json(updated);
1343
1523
  } else {
1344
- success(`Comment ${commentId} ${opts.unhide ? "shown" : "hidden"}`);
1524
+ success(`Comment ${ids.commentId} ${opts.unhide ? "shown" : "hidden"}`);
1345
1525
  }
1346
1526
  } catch (err) {
1347
1527
  apiError(err, "Failed to hide comment");
1348
1528
  process.exit(1);
1349
1529
  }
1350
1530
  });
1351
- commentsCommand.command("delete").description("Delete a comment from the platform").argument("<post_id>", "Post ID").argument("<comment_id>", "Comment ID from `chirpie comments list`").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
1352
- if (!opts.yes && !await confirm2(`Delete comment ${commentId}?`)) {
1531
+ commentsCommand.command("delete").description("Delete a comment from the platform").argument("<post_id>", "Post ID, full or the short ID from `chirpie posts`").argument("<comment_id>", "Comment ID, full or the short ID from `chirpie comments list`").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
1532
+ const client = getClient();
1533
+ let ids;
1534
+ try {
1535
+ ids = await resolveCommentIds(client, postId, commentId);
1536
+ } catch (err) {
1537
+ apiError(err, "Failed to find the comment");
1538
+ process.exit(1);
1539
+ }
1540
+ if (!opts.yes && !await confirm2(`Delete comment ${ids.commentId}?`)) {
1353
1541
  error("Cancelled");
1354
1542
  process.exit(1);
1355
1543
  }
1356
- const client = getClient();
1357
1544
  try {
1358
- const result = await client.deleteComment(postId, commentId);
1545
+ const result = await client.deleteComment(ids.postId, ids.commentId);
1359
1546
  if (wantsJson(opts, cmd)) {
1360
1547
  json(result);
1361
1548
  } else {
1362
- success(`Comment ${commentId} deleted`);
1549
+ success(`Comment ${ids.commentId} deleted`);
1363
1550
  }
1364
1551
  } catch (err) {
1365
1552
  apiError(err, "Failed to delete comment");
@@ -1383,15 +1570,16 @@ async function confirm2(question) {
1383
1570
 
1384
1571
  // src/commands/analytics.ts
1385
1572
  import { Command as Command8 } from "commander";
1386
- var analyticsCommand = new Command8("analytics").description("Get analytics for a post").argument("<post_id>", "Post ID").option(
1573
+ var analyticsCommand = new Command8("analytics").description("Get analytics for a post").argument("<post_id>", "Post ID, full or the short ID from `chirpie posts`").option(
1387
1574
  "--refresh",
1388
1575
  "Ask the platform now instead of reading the stored numbers. Allowed once per post every 5 minutes"
1389
1576
  ).option("--json", "Output raw JSON").action(async (postId, opts) => {
1390
1577
  const client = getClient();
1391
1578
  try {
1392
- const analytics = await client.getPostAnalytics(postId, {
1393
- refresh: Boolean(opts.refresh)
1394
- });
1579
+ const analytics = await client.getPostAnalytics(
1580
+ await resolvePostId(client, postId),
1581
+ { refresh: Boolean(opts.refresh) }
1582
+ );
1395
1583
  if (opts.json) {
1396
1584
  json(analytics);
1397
1585
  } else {
@@ -1482,11 +1670,12 @@ keysCommand.command("create").description("Create a new API key").option("-n, --
1482
1670
  keysCommand.command("revoke <id>").description("Revoke an API key").option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
1483
1671
  const client = getClient();
1484
1672
  try {
1485
- await client.revokeKey(id);
1673
+ const keyId = await resolveKeyId(client, id);
1674
+ await client.revokeKey(keyId);
1486
1675
  if (wantsJson(opts, cmd)) {
1487
- json({ id, revoked: true });
1676
+ json({ id: keyId, revoked: true });
1488
1677
  } else {
1489
- success(`Key ${id} revoked`);
1678
+ success(`Key ${keyId} revoked`);
1490
1679
  }
1491
1680
  } catch (err) {
1492
1681
  error(err instanceof Error ? err.message : "Failed to revoke key");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chirpie",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Chirpie CLI: post to social media from the command line",
5
5
  "repository": {
6
6
  "type": "git",