chirpie 1.0.47 → 1.0.49
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 +21 -5
- package/dist/index.js +113 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,12 +30,13 @@ export CHIRPIE_API_KEY=chirpie_sk_...
|
|
|
30
30
|
| Command | What it does |
|
|
31
31
|
|---------|--------------|
|
|
32
32
|
| `chirpie login` / `logout` / `auth` / `whoami` | Manage credentials |
|
|
33
|
-
| `chirpie post <text>` | Create a post. `-a` account (repeat it for several accounts), `-m` media files or URLs, `--alt` descriptions, `--config` per-account overrides, `-s` schedule, `--draft` save without sending |
|
|
34
|
-
| `chirpie thread <post> <post> ...` | Create a 2 to 25 post thread, or 1 to 25 with `--draft`. `-a` is repeatable, `--config` takes per-account posts, `-f` takes a JSON payload file |
|
|
33
|
+
| `chirpie post <text>` | Create a post. `-a` account (repeat it for several accounts), `-m` media files or URLs, `--alt` descriptions, `--config` per-account overrides, `--first-comment` a comment published under the post as soon as it goes out, `-s` schedule, `--timezone` the IANA zone a `-s` with no offset is read in, `--idempotency-key` to make a retry safe, `--draft` save without sending |
|
|
34
|
+
| `chirpie thread <post> <post> ...` | Create a 2 to 25 post thread, or 1 to 25 with `--draft`. `-a` is repeatable, `--config` takes per-account posts, `--first-comment` posts a comment under the last part, `--timezone` and `--idempotency-key` work as they do on a post, `-f` takes a JSON payload file |
|
|
35
35
|
| `chirpie posts` | List posts. `--status`, `--account`, `--group`, `--limit`, `--include-hidden` |
|
|
36
36
|
| `chirpie posts get <id>` | Fetch one post |
|
|
37
|
-
| `chirpie posts update <id>` | Edit a post that has not published yet, or finish a draft. `--text`, `--media`, `--alt`, `--clear-media`, `--schedule-at`, `--keep-draft`. Leaving `--schedule-at` out keeps the time it already has |
|
|
37
|
+
| `chirpie posts update <id>` | Edit a post that has not published yet, or finish a draft. `--text`, `--media`, `--alt`, `--clear-media`, `--first-comment` (`""` removes it), `--schedule-at`, `--timezone`, `--keep-draft`. Leaving `--schedule-at` out keeps the time it already has |
|
|
38
38
|
| `chirpie posts publish <id>` | Send a saved draft now. `-t` sets the final text first |
|
|
39
|
+
| `chirpie posts first-comment <id>` | Post a first comment that failed, again. It re-sends the text the post already carries. `--idempotency-key` makes a retry safe |
|
|
39
40
|
| `chirpie posts delete <id>` | Take a post down from the platform. Chirpie keeps it, marked deleted. Deleting any post of a scheduled thread cancels the thread. Instagram and TikTok offer no delete API and refuse with `delete_unsupported` |
|
|
40
41
|
| `chirpie posts hide <id>` / `unhide <id>` | Hide a post from your Chirpie listings, or show it again. Nothing reaches the platform |
|
|
41
42
|
| `chirpie accounts` | List connected accounts, active and inactive |
|
|
@@ -48,8 +49,8 @@ export CHIRPIE_API_KEY=chirpie_sk_...
|
|
|
48
49
|
| `chirpie accounts connect-linkedin --pages` | Connect the LinkedIn Pages you administer, each as its own account (coming soon) |
|
|
49
50
|
| `chirpie accounts connect-telegram` | Connect a Telegram bot to a channel or group |
|
|
50
51
|
| `chirpie accounts x-keys set` / `status` / `remove` | Use your own X developer app |
|
|
51
|
-
| `chirpie analytics <post_id>` | Engagement metrics for a published post |
|
|
52
|
-
| `chirpie keys` / `keys create` / `keys revoke <id>` | Manage API keys |
|
|
52
|
+
| `chirpie analytics <post_id>` | Engagement metrics for a published post. `--refresh` asks the platform now instead of reading the stored snapshot, once per post every 30 minutes |
|
|
53
|
+
| `chirpie keys` / `keys create` / `keys revoke <id>` | Manage API keys. `keys create --scope posts:write --scope media:write` mints a key that can do less than yours; `chirpie keys` prints each key's scopes |
|
|
53
54
|
|
|
54
55
|
Run `chirpie <command> --help` for the full option list. Threads, Instagram, Facebook, Pinterest, TikTok, YouTube and Google Business Profile are coming soon. Accounts already connected keep posting and scheduling as normal.
|
|
55
56
|
|
|
@@ -65,11 +66,26 @@ chirpie post "Look at this" -a 550e8400-e29b-41d4-a716-446655440000 -m ./shot.pn
|
|
|
65
66
|
# Schedule. Any ISO 8601 timestamp that carries a timezone.
|
|
66
67
|
chirpie post "Later" -s 2026-04-01T14:00:00Z
|
|
67
68
|
|
|
69
|
+
# Or a local time, with the zone named separately. Daylight saving is worked
|
|
70
|
+
# out for the date, which an offset computed today gets wrong.
|
|
71
|
+
chirpie post "Half nine, my time" -s 2026-11-01T09:30 --timezone America/New_York
|
|
72
|
+
|
|
73
|
+
# Make a retry safe: the same key replays the first answer for 24 hours.
|
|
74
|
+
chirpie post "Shipped" --idempotency-key release-2026-11-01
|
|
75
|
+
|
|
68
76
|
# A thread
|
|
69
77
|
chirpie thread "One" "Two" "Three" -a 550e8400-e29b-41d4-a716-446655440000
|
|
70
78
|
|
|
71
79
|
# What is queued
|
|
72
80
|
chirpie posts --status scheduled
|
|
81
|
+
|
|
82
|
+
# Put the link in the first comment. X, Threads, Instagram and Facebook only,
|
|
83
|
+
# and it counts as one post against your monthly quota.
|
|
84
|
+
chirpie post "We rebuilt scheduling this week." \
|
|
85
|
+
--first-comment "Full write-up: https://example.com/blog/scheduling"
|
|
86
|
+
|
|
87
|
+
# A first comment never fails its post. Send one that did not go out again.
|
|
88
|
+
chirpie posts first-comment "$POST_ID"
|
|
73
89
|
```
|
|
74
90
|
|
|
75
91
|
## Drafts
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,15 @@ function getClient() {
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// src/lib/flags.ts
|
|
172
|
+
function wantsJson(opts, cmd) {
|
|
173
|
+
if (opts?.json) return true;
|
|
174
|
+
return Boolean(cmd?.optsWithGlobals?.().json);
|
|
175
|
+
}
|
|
176
|
+
function idempotency(opts) {
|
|
177
|
+
return opts?.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : void 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
171
180
|
// src/lib/media.ts
|
|
172
181
|
import { readFile } from "fs/promises";
|
|
173
182
|
import { basename, resolve } from "path";
|
|
@@ -274,12 +283,18 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
274
283
|
"-a, --account <id>",
|
|
275
284
|
"Account ID. Repeat it to publish the same post to several accounts at once (up to 25)",
|
|
276
285
|
collect
|
|
277
|
-
).option("-m, --media <files...>", MEDIA_OPTION_HELP).option("--alt <text...>", ALT_OPTION_HELP).option(
|
|
286
|
+
).option("-m, --media <files...>", MEDIA_OPTION_HELP).option("--first-comment <text>", "Post this as a comment under the post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota").option("--alt <text...>", ALT_OPTION_HELP).option(
|
|
278
287
|
"--config <json-or-file>",
|
|
279
|
-
'Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "text": "...", "media_urls": ["..."] } }. Every key must be an account named with --account'
|
|
280
|
-
).option("-s, --schedule <datetime>", "Schedule for later.
|
|
288
|
+
'Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "text": "...", "media_urls": ["..."], "first_comment": "..." } }. Every key must be an account named with --account, and "first_comment": "" publishes that account without the first comment the call is sending'
|
|
289
|
+
).option("-s, --schedule <datetime>", "Schedule for later. An ISO 8601 timestamp carrying a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00), or a local time with no offset (2026-11-01T09:30) read in --timezone or the timezone saved on your account. Posts publish within ~5 minutes of the scheduled time.").option(
|
|
281
290
|
"--draft",
|
|
282
291
|
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
292
|
+
).option(
|
|
293
|
+
"--timezone <iana>",
|
|
294
|
+
"The IANA timezone a --schedule with no offset is read in, such as America/New_York. Daylight saving is worked out for the date you name"
|
|
295
|
+
).option(
|
|
296
|
+
"--idempotency-key <key>",
|
|
297
|
+
"Make a retry safe. The same key with the same request replays the first answer instead of sending it again, for 24 hours. One is generated per call when you do not pass one"
|
|
283
298
|
).option("--json", "Output raw JSON").action(async (text, opts) => {
|
|
284
299
|
const client = getClient();
|
|
285
300
|
const named = opts.account ?? [];
|
|
@@ -296,14 +311,16 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
296
311
|
account_ids: accountIds,
|
|
297
312
|
text,
|
|
298
313
|
...media ? { media } : {},
|
|
314
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
299
315
|
...opts.config ? {
|
|
300
316
|
account_configurations: parseAccountConfigurations(
|
|
301
317
|
opts.config
|
|
302
318
|
)
|
|
303
319
|
} : {},
|
|
304
320
|
schedule_at: opts.schedule,
|
|
321
|
+
...opts.timezone ? { timezone: opts.timezone } : {},
|
|
305
322
|
...opts.draft ? { draft: true } : {}
|
|
306
|
-
});
|
|
323
|
+
}, idempotency(opts));
|
|
307
324
|
if (opts.json) {
|
|
308
325
|
json(result);
|
|
309
326
|
if (result.results.some((r) => !r.success)) process.exit(1);
|
|
@@ -329,9 +346,11 @@ var postCommand = new Command3("post").description("Create a post").argument("<t
|
|
|
329
346
|
account_id: accountIds[0],
|
|
330
347
|
text,
|
|
331
348
|
...media ? { media } : {},
|
|
349
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
332
350
|
schedule_at: opts.schedule,
|
|
351
|
+
...opts.timezone ? { timezone: opts.timezone } : {},
|
|
333
352
|
...opts.draft ? { draft: true } : {}
|
|
334
|
-
});
|
|
353
|
+
}, idempotency(opts));
|
|
335
354
|
if (opts.json) {
|
|
336
355
|
json(post);
|
|
337
356
|
} else if (post.status === "draft") {
|
|
@@ -366,7 +385,7 @@ function parseAltSpec(spec) {
|
|
|
366
385
|
alts: m[2].split(",").map((a) => a.trim())
|
|
367
386
|
};
|
|
368
387
|
}
|
|
369
|
-
var threadCommand = new Command4("thread").description("Create a thread").argument("[posts...]", "Thread posts (each argument is one post). Omit when using --file.").option(
|
|
388
|
+
var threadCommand = new Command4("thread").description("Create a thread").argument("[posts...]", "Thread posts (each argument is one post). Omit when using --file.").option("--first-comment <text>", "Post this as a comment under the thread's last post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota").option(
|
|
370
389
|
"-a, --account <id>",
|
|
371
390
|
"Account ID. Repeat it to publish the same thread to several accounts at once (up to 25)",
|
|
372
391
|
collect
|
|
@@ -381,10 +400,16 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
381
400
|
"JSON file with a full thread payload: { posts: [{ text, media_urls? }, ...], schedule_at?: string }. It may also carry account_ids and account_configurations."
|
|
382
401
|
).option(
|
|
383
402
|
"--config <json-or-file>",
|
|
384
|
-
`Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "posts": [{ "text": "one" }, { "text": "two" }] } }. An account's posts replace the whole thread for that account`
|
|
385
|
-
).option("-s, --schedule <datetime>", "Schedule for later.
|
|
403
|
+
`Per-account overrides as JSON, or a path to a JSON file holding them: { "<account id>": { "posts": [{ "text": "one" }, { "text": "two" }], "first_comment": "..." } }. An account's posts replace the whole thread for that account, and "first_comment": "" publishes that account without the first comment the call is sending`
|
|
404
|
+
).option("-s, --schedule <datetime>", "Schedule for later. An ISO 8601 timestamp carrying a timezone (2026-04-01T14:00:00Z or 2026-04-01T16:00:00+02:00), or a local time with no offset (2026-11-01T09:30) read in --timezone or the timezone saved on your account. Threads publish within ~5 minutes of the scheduled time.").option(
|
|
386
405
|
"--draft",
|
|
387
406
|
"Save without publishing. Nothing is sent, nothing counts against your quota, and any problems are listed back"
|
|
407
|
+
).option(
|
|
408
|
+
"--timezone <iana>",
|
|
409
|
+
"The IANA timezone a --schedule with no offset is read in, such as America/New_York. Daylight saving is worked out for the date you name"
|
|
410
|
+
).option(
|
|
411
|
+
"--idempotency-key <key>",
|
|
412
|
+
"Make a retry safe. The same key with the same request replays the first answer instead of sending it again, for 24 hours. One is generated per call when you do not pass one"
|
|
388
413
|
).option("--json", "Output raw JSON").action(async (posts, opts) => {
|
|
389
414
|
const client = getClient();
|
|
390
415
|
let postPayload;
|
|
@@ -459,10 +484,12 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
459
484
|
const result = await client.createThread({
|
|
460
485
|
account_ids: accountIds,
|
|
461
486
|
posts: postPayload,
|
|
487
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
462
488
|
...configurations ? { account_configurations: configurations } : {},
|
|
463
489
|
schedule_at: scheduleAt,
|
|
490
|
+
...opts.timezone ? { timezone: opts.timezone } : {},
|
|
464
491
|
...opts.draft ? { draft: true } : {}
|
|
465
|
-
});
|
|
492
|
+
}, idempotency(opts));
|
|
466
493
|
if (opts.json) {
|
|
467
494
|
json(result);
|
|
468
495
|
if (result.results.some((r) => !r.success)) process.exit(1);
|
|
@@ -487,9 +514,11 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
487
514
|
const thread = await client.createThread({
|
|
488
515
|
account_id: accountIds[0],
|
|
489
516
|
posts: postPayload,
|
|
517
|
+
...opts.firstComment ? { first_comment: opts.firstComment } : {},
|
|
490
518
|
schedule_at: scheduleAt,
|
|
519
|
+
...opts.timezone ? { timezone: opts.timezone } : {},
|
|
491
520
|
...opts.draft ? { draft: true } : {}
|
|
492
|
-
});
|
|
521
|
+
}, idempotency(opts));
|
|
493
522
|
if (opts.json) {
|
|
494
523
|
json(thread);
|
|
495
524
|
} else if (thread.status === "draft") {
|
|
@@ -507,14 +536,6 @@ var threadCommand = new Command4("thread").description("Create a thread").argume
|
|
|
507
536
|
// src/commands/posts.ts
|
|
508
537
|
import { Command as Command5 } from "commander";
|
|
509
538
|
import { postFailureReason } from "@chirpie/sdk";
|
|
510
|
-
|
|
511
|
-
// src/lib/flags.ts
|
|
512
|
-
function wantsJson(opts, cmd) {
|
|
513
|
-
if (opts?.json) return true;
|
|
514
|
-
return Boolean(cmd?.optsWithGlobals?.().json);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// src/commands/posts.ts
|
|
518
539
|
var postsCommand = new Command5("posts").description("List, get, update, delete, hide or unhide posts").option(
|
|
519
540
|
"--status <status>",
|
|
520
541
|
"Filter by status: draft, scheduled, publishing, published, failed, deleted"
|
|
@@ -583,7 +604,13 @@ postsCommand.command("update <id>").description(
|
|
|
583
604
|
"Replacement image or video files on this machine, or public URLs, following the same per-platform rules as creating a post. Pass --clear-media to remove the media instead."
|
|
584
605
|
).option("--alt <text...>", ALT_OPTION_HELP).option("--clear-media", "Remove the media from the post").option(
|
|
585
606
|
"-s, --schedule-at <datetime>",
|
|
586
|
-
"New publish time.
|
|
607
|
+
"New publish time. An ISO 8601 timestamp carrying a timezone (2027-04-01T14:00:00Z or 2027-04-01T16:00:00+02:00), or a local time with no offset (2027-04-01T09:30) read in --timezone or the timezone saved on your account. Omit it to keep the time the post already has. Rescheduling one part of a thread moves the whole thread. On a draft this queues it, unless you also pass --keep-draft."
|
|
608
|
+
).option(
|
|
609
|
+
"--timezone <iana>",
|
|
610
|
+
"The IANA timezone a --schedule-at with no offset is read in, such as America/New_York"
|
|
611
|
+
).option(
|
|
612
|
+
"--first-comment <text>",
|
|
613
|
+
'The comment to post under this one once it publishes. Pass "" to remove the one it has'
|
|
587
614
|
).option(
|
|
588
615
|
"--keep-draft",
|
|
589
616
|
"Keep a draft a draft, so --schedule-at only changes the time it remembers"
|
|
@@ -592,9 +619,9 @@ postsCommand.command("update <id>").description(
|
|
|
592
619
|
error("Pass either --media or --clear-media, not both");
|
|
593
620
|
process.exit(1);
|
|
594
621
|
}
|
|
595
|
-
if (!opts.text && !opts.media && !opts.clearMedia && !opts.scheduleAt && !opts.keepDraft) {
|
|
622
|
+
if (!opts.text && !opts.media && !opts.clearMedia && opts.firstComment === void 0 && !opts.scheduleAt && !opts.keepDraft) {
|
|
596
623
|
error(
|
|
597
|
-
"Nothing to change. Pass --text, --media, --clear-media, --schedule-at or --keep-draft"
|
|
624
|
+
"Nothing to change. Pass --text, --media, --clear-media, --first-comment, --schedule-at or --keep-draft"
|
|
598
625
|
);
|
|
599
626
|
process.exit(1);
|
|
600
627
|
}
|
|
@@ -605,7 +632,9 @@ postsCommand.command("update <id>").description(
|
|
|
605
632
|
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
606
633
|
...opts.clearMedia ? { media: [] } : {},
|
|
607
634
|
...media ? { media } : {},
|
|
635
|
+
...opts.firstComment !== void 0 ? { first_comment: opts.firstComment } : {},
|
|
608
636
|
...opts.scheduleAt ? { schedule_at: opts.scheduleAt } : {},
|
|
637
|
+
...opts.timezone ? { timezone: opts.timezone } : {},
|
|
609
638
|
...opts.keepDraft ? { draft: true } : {}
|
|
610
639
|
});
|
|
611
640
|
if (wantsJson(opts, cmd)) {
|
|
@@ -664,6 +693,25 @@ postsCommand.command("delete <id>").description(
|
|
|
664
693
|
process.exit(1);
|
|
665
694
|
}
|
|
666
695
|
});
|
|
696
|
+
postsCommand.command("first-comment <id>").description(
|
|
697
|
+
"Post a first comment that failed, again. Re-sends the text the post already carries"
|
|
698
|
+
).option(
|
|
699
|
+
"--idempotency-key <key>",
|
|
700
|
+
"Make a retry safe. The same key replays the first answer instead of sending the comment again, for 24 hours"
|
|
701
|
+
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
702
|
+
const client = getClient();
|
|
703
|
+
try {
|
|
704
|
+
const post = await client.retryFirstComment(id, idempotency(opts));
|
|
705
|
+
if (wantsJson(opts, cmd)) {
|
|
706
|
+
json(post);
|
|
707
|
+
} else {
|
|
708
|
+
success(`First comment posted on ${post.id}`);
|
|
709
|
+
}
|
|
710
|
+
} catch (err) {
|
|
711
|
+
apiError(err, "Failed to post the first comment");
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
667
715
|
postsCommand.command("hide <id>").description(
|
|
668
716
|
"Hide a post from your Chirpie listings (nothing reaches the platform)"
|
|
669
717
|
).option("--json", "Output raw JSON").action(async (id, opts, cmd) => {
|
|
@@ -1196,10 +1244,18 @@ commentsCommand.command("list").description("List the comments on a post you pub
|
|
|
1196
1244
|
process.exit(1);
|
|
1197
1245
|
}
|
|
1198
1246
|
});
|
|
1199
|
-
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(
|
|
1247
|
+
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(
|
|
1248
|
+
"--idempotency-key <key>",
|
|
1249
|
+
"Make a retry safe. The same key with the same reply replays the first answer instead of sending it again, for 24 hours"
|
|
1250
|
+
).option("--json", "Output raw JSON").action(async (postId, commentId, opts, cmd) => {
|
|
1200
1251
|
const client = getClient();
|
|
1201
1252
|
try {
|
|
1202
|
-
const reply = await client.replyToComment(
|
|
1253
|
+
const reply = await client.replyToComment(
|
|
1254
|
+
postId,
|
|
1255
|
+
commentId,
|
|
1256
|
+
opts.text,
|
|
1257
|
+
idempotency(opts)
|
|
1258
|
+
);
|
|
1203
1259
|
if (wantsJson(opts, cmd)) {
|
|
1204
1260
|
json(reply);
|
|
1205
1261
|
} else {
|
|
@@ -1259,10 +1315,15 @@ async function confirm2(question) {
|
|
|
1259
1315
|
|
|
1260
1316
|
// src/commands/analytics.ts
|
|
1261
1317
|
import { Command as Command8 } from "commander";
|
|
1262
|
-
var analyticsCommand = new Command8("analytics").description("Get analytics for a post").argument("<post_id>", "Post ID").option(
|
|
1318
|
+
var analyticsCommand = new Command8("analytics").description("Get analytics for a post").argument("<post_id>", "Post ID").option(
|
|
1319
|
+
"--refresh",
|
|
1320
|
+
"Ask the platform now instead of reading the stored numbers. Allowed once per post every 30 minutes"
|
|
1321
|
+
).option("--json", "Output raw JSON").action(async (postId, opts) => {
|
|
1263
1322
|
const client = getClient();
|
|
1264
1323
|
try {
|
|
1265
|
-
const analytics = await client.getPostAnalytics(postId
|
|
1324
|
+
const analytics = await client.getPostAnalytics(postId, {
|
|
1325
|
+
refresh: Boolean(opts.refresh)
|
|
1326
|
+
});
|
|
1266
1327
|
if (opts.json) {
|
|
1267
1328
|
json(analytics);
|
|
1268
1329
|
} else {
|
|
@@ -1284,6 +1345,7 @@ var analyticsCommand = new Command8("analytics").description("Get analytics for
|
|
|
1284
1345
|
|
|
1285
1346
|
// src/commands/keys.ts
|
|
1286
1347
|
import { Command as Command9 } from "commander";
|
|
1348
|
+
import { API_SCOPES } from "@chirpie/sdk";
|
|
1287
1349
|
var keysCommand = new Command9("keys").description("Manage API keys").option("--json", "Output raw JSON").action(async (opts, cmd) => {
|
|
1288
1350
|
const client = getClient();
|
|
1289
1351
|
try {
|
|
@@ -1296,6 +1358,9 @@ var keysCommand = new Command9("keys").description("Manage API keys").option("--
|
|
|
1296
1358
|
id: k.id.slice(0, 8),
|
|
1297
1359
|
name: k.name,
|
|
1298
1360
|
prefix: k.key_prefix,
|
|
1361
|
+
// Null scopes mean full access, which is not the same as every
|
|
1362
|
+
// scope listed: it also covers anything added later.
|
|
1363
|
+
scopes: k.scopes === null ? "all" : k.scopes.join(" "),
|
|
1299
1364
|
// GET /v1/keys only ever returns live keys, so there is no
|
|
1300
1365
|
// revoked column to show.
|
|
1301
1366
|
expires: k.expires_at?.slice(0, 10) ?? "never",
|
|
@@ -1308,10 +1373,27 @@ var keysCommand = new Command9("keys").description("Manage API keys").option("--
|
|
|
1308
1373
|
process.exit(1);
|
|
1309
1374
|
}
|
|
1310
1375
|
});
|
|
1311
|
-
keysCommand.command("create").description("Create a new API key").option("-n, --name <name>", "Key name").option(
|
|
1376
|
+
keysCommand.command("create").description("Create a new API key").option("-n, --name <name>", "Key name").option(
|
|
1377
|
+
"--scope <scope>",
|
|
1378
|
+
`Limit what the new key can do. Repeat it for each one: ${API_SCOPES.join(", ")}. Leave it out and the key can do everything this one can`,
|
|
1379
|
+
collect
|
|
1380
|
+
).option("--json", "Output raw JSON").action(async (opts, cmd) => {
|
|
1312
1381
|
const client = getClient();
|
|
1382
|
+
const scopes = opts.scope;
|
|
1383
|
+
const unknown = (scopes ?? []).filter(
|
|
1384
|
+
(scope) => !API_SCOPES.includes(scope)
|
|
1385
|
+
);
|
|
1386
|
+
if (unknown.length > 0) {
|
|
1387
|
+
error(
|
|
1388
|
+
`Not a scope: ${unknown.join(", ")}. Allowed values: ${API_SCOPES.join(", ")}`
|
|
1389
|
+
);
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1313
1392
|
try {
|
|
1314
|
-
const key = await client.createKey(
|
|
1393
|
+
const key = await client.createKey({
|
|
1394
|
+
...opts.name ? { name: opts.name } : {},
|
|
1395
|
+
...scopes ? { scopes } : {}
|
|
1396
|
+
});
|
|
1315
1397
|
if (wantsJson(opts, cmd)) {
|
|
1316
1398
|
json(key);
|
|
1317
1399
|
} else {
|
|
@@ -1319,6 +1401,10 @@ keysCommand.command("create").description("Create a new API key").option("-n, --
|
|
|
1319
1401
|
console.log(`
|
|
1320
1402
|
${key.key}
|
|
1321
1403
|
`);
|
|
1404
|
+
console.log(
|
|
1405
|
+
` Scopes: ${key.scopes === null ? "all" : key.scopes.join(" ")}
|
|
1406
|
+
`
|
|
1407
|
+
);
|
|
1322
1408
|
}
|
|
1323
1409
|
} catch (err) {
|
|
1324
1410
|
error(err instanceof Error ? err.message : "Failed to create key");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chirpie",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.49",
|
|
4
4
|
"description": "Chirpie CLI: post to social media from the command line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
],
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chirpie/sdk": "^1.0.
|
|
47
|
+
"@chirpie/sdk": "^1.0.46",
|
|
48
48
|
"commander": "^13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|