chirpie 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.js +241 -52
- package/package.json +2 -2
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
|
|
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
|
-
...
|
|
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
|
|
657
|
+
const givenConfigurations = parseAccountConfigurations(opts.config) ?? fileConfigurations;
|
|
505
658
|
const named = opts.account ?? fileAccountIds ?? [];
|
|
506
|
-
if (
|
|
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
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
1114
|
+
const result = await client.disconnectAccount(accountId);
|
|
937
1115
|
if (wantsJson(opts, cmd)) {
|
|
938
1116
|
json(result);
|
|
939
1117
|
} else {
|
|
940
|
-
success(`Disconnected account ${
|
|
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
|
|
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
|
-
|
|
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(
|
|
1393
|
-
|
|
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
|
|
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 ${
|
|
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.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Chirpie CLI: post to social media from the command line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
],
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chirpie/sdk": "^1.1.
|
|
47
|
+
"@chirpie/sdk": "^1.1.3",
|
|
48
48
|
"commander": "^13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|