@rolino/cli 0.1.0 → 0.3.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/CHANGELOG.md +26 -0
- package/README.md +74 -40
- package/dist/bin.cjs +130 -27
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-YTEWMNSV.js → chunk-7BCIIFUB.js} +134 -29
- package/dist/chunk-7BCIIFUB.js.map +1 -0
- package/dist/index.cjs +130 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-YTEWMNSV.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @rolino/cli
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5267911: Add LinkedIn member publishing as a gated provider across public contracts, SDK, CLI, and MCP workflows.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [5267911]
|
|
12
|
+
- @rolino/contracts@0.3.0
|
|
13
|
+
- @rolino/sdk@0.3.0
|
|
14
|
+
- @rolino/local-auth@0.3.0
|
|
15
|
+
|
|
16
|
+
## 0.2.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 7d34580: Add account-specific TikTok delivery options, explicit settings review, safe partial draft updates, provider-keyed post settings, and streamed media uploads with server-verified metadata.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [7d34580]
|
|
25
|
+
- @rolino/contracts@0.2.0
|
|
26
|
+
- @rolino/sdk@0.2.0
|
|
27
|
+
- @rolino/local-auth@0.2.0
|
|
28
|
+
|
|
3
29
|
## 0.1.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ Network requests go through `@rolino/sdk`, which uses the Node.js native
|
|
|
6
6
|
`fetch` implementation; the CLI does not depend on Axios or another HTTP
|
|
7
7
|
client package.
|
|
8
8
|
|
|
9
|
-
## Installation
|
|
10
|
-
|
|
11
|
-
The supported installation keeps the CLI independent from MCP:
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
The supported installation keeps the CLI independent from MCP:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install --global @rolino/cli
|
|
@@ -36,19 +36,20 @@ rolino setup mcp --client claude-code [--scope user|project]
|
|
|
36
36
|
rolino whoami
|
|
37
37
|
rolino projects create --name NAME [--type PRODUCT|SERVICE|PERSONAL|COMPANY] [--website URL] [--description TEXT] [--idempotency-key KEY] --yes
|
|
38
38
|
rolino projects list [--limit 50] [--cursor ID]
|
|
39
|
-
rolino projects show PROJECT_ID
|
|
40
|
-
rolino media list --project PROJECT_ID [--type IMAGE|VIDEO] [--query TEXT] [--limit 50] [--cursor ID]
|
|
41
|
-
rolino media upload FILE_PATH --project PROJECT_ID --yes
|
|
42
|
-
rolino posts create --project PROJECT_ID --caption TEXT [--platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY] [--media ASSET_ID] --yes
|
|
43
|
-
rolino posts update POST_ID --project PROJECT_ID --expected-version VERSION --caption TEXT [--platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY] [--media ASSET_ID] --yes
|
|
39
|
+
rolino projects show PROJECT_ID
|
|
40
|
+
rolino media list --project PROJECT_ID [--type IMAGE|VIDEO] [--query TEXT] [--limit 50] [--cursor ID]
|
|
41
|
+
rolino media upload FILE_PATH --project PROJECT_ID --yes
|
|
42
|
+
rolino posts create --project PROJECT_ID --caption TEXT [--platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY|LINKEDIN] [--media ASSET_ID] --yes
|
|
43
|
+
rolino posts update POST_ID --project PROJECT_ID --expected-version VERSION [--caption TEXT] [--platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY|LINKEDIN] [--media ASSET_ID] [--clear-platforms] [--clear-media] --yes
|
|
44
44
|
rolino posts list --project PROJECT_ID [--limit 50] [--cursor ID] [--status STATUS]
|
|
45
45
|
rolino posts show POST_ID --project PROJECT_ID
|
|
46
46
|
rolino posts readiness POST_ID --project PROJECT_ID
|
|
47
47
|
rolino posts schedule preview POST_ID --project PROJECT_ID --expected-version VERSION --at ISO_DATE --timezone IANA_TIMEZONE
|
|
48
48
|
rolino posts schedule execute POST_ID --project PROJECT_ID --expected-version VERSION --at ISO_DATE --timezone IANA_TIMEZONE --confirmation-token TOKEN [--idempotency-key KEY]
|
|
49
|
-
rolino posts publish preview POST_ID --project PROJECT_ID --expected-version VERSION --platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY
|
|
50
|
-
rolino posts publish execute POST_ID --project PROJECT_ID --expected-version VERSION --platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY --confirmation-token TOKEN [--idempotency-key KEY]
|
|
49
|
+
rolino posts publish preview POST_ID --project PROJECT_ID --expected-version VERSION --platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY|LINKEDIN
|
|
50
|
+
rolino posts publish execute POST_ID --project PROJECT_ID --expected-version VERSION --platform INSTAGRAM|TIKTOK|YOUTUBE|BLUESKY|LINKEDIN --confirmation-token TOKEN [--idempotency-key KEY]
|
|
51
51
|
rolino integrations health --project PROJECT_ID
|
|
52
|
+
rolino integrations delivery-options --project PROJECT_ID --provider TIKTOK|LINKEDIN
|
|
52
53
|
rolino calendar list --project PROJECT_ID [--from ISO_DATE --to ISO_DATE] [--limit 50] [--cursor CURSOR]
|
|
53
54
|
rolino doctor
|
|
54
55
|
```
|
|
@@ -78,17 +79,21 @@ directory. Existing file-backed configuration is backed up with a
|
|
|
78
79
|
`.rolino-backup` suffix. Use `--dry-run` to preview in scripts and `--yes` to
|
|
79
80
|
provide explicit non-interactive consent; `--agent` never implies consent.
|
|
80
81
|
|
|
81
|
-
Draft creation and update never schedule or publish.
|
|
82
|
+
Draft creation and update never schedule or publish. Updates change only the
|
|
83
|
+
fields supplied; omitted media, destinations, captions, and provider settings
|
|
84
|
+
are preserved. Both require interactive
|
|
82
85
|
confirmation or `--yes`; non-interactive and agent-mode calls fail before an
|
|
83
86
|
HTTP request unless `--yes` is present. Retries use `--idempotency-key` (the
|
|
84
87
|
request ID by default), and updates require the current `--expected-version`
|
|
85
|
-
shown by `posts show` so stale automation cannot overwrite a newer edit.
|
|
86
|
-
|
|
87
|
-
Media uploads use the deployment's existing local, S3-compatible, or
|
|
88
|
-
Cloudflare R2 storage. The CLI validates the local file, requests a short-lived
|
|
89
|
-
signed upload,
|
|
90
|
-
asset only after the stored size and content type match.
|
|
91
|
-
|
|
88
|
+
shown by `posts show` so stale automation cannot overwrite a newer edit.
|
|
89
|
+
|
|
90
|
+
Media uploads use the deployment's existing local, S3-compatible, or
|
|
91
|
+
Cloudflare R2 storage. The CLI validates the local file, requests a short-lived
|
|
92
|
+
signed upload, streams the file directly to storage, and registers the reusable
|
|
93
|
+
asset only after the stored size and content type match. Rolino derives image
|
|
94
|
+
dimensions and video duration from bounded reads of the stored object rather
|
|
95
|
+
than trusting caller metadata. Uploading requires
|
|
96
|
+
interactive confirmation or `--yes` and never creates or schedules a post.
|
|
92
97
|
|
|
93
98
|
YouTube drafts require exactly one stored video and explicit settings. Supply
|
|
94
99
|
`--youtube-title`, `--youtube-category-id`, `--youtube-privacy`, and
|
|
@@ -101,19 +106,47 @@ description; otherwise Rolino uses the shared caption. For example:
|
|
|
101
106
|
rolino posts create --project PROJECT_ID --caption "Launch description" --platform YOUTUBE --media VIDEO_ASSET_ID --youtube-title "Launch video" --youtube-category-id 22 --youtube-privacy PRIVATE --youtube-made-for-kids no --youtube-tag launch --yes
|
|
102
107
|
```
|
|
103
108
|
|
|
104
|
-
Google may restrict uploads from unaudited API projects to Private. Rolino does
|
|
105
|
-
not infer audit approval from configuration. A scheduled YouTube post must
|
|
106
|
-
target Public; confirmed scheduling starts its private resumable upload
|
|
107
|
-
immediately so processing can finish before the target time.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
Google may restrict uploads from unaudited API projects to Private. Rolino does
|
|
110
|
+
not infer audit approval from configuration. A scheduled YouTube post must
|
|
111
|
+
target Public; confirmed scheduling starts its private resumable upload
|
|
112
|
+
immediately so processing can finish before the target time.
|
|
113
|
+
|
|
114
|
+
TikTok drafts require account-specific choices. Refresh them immediately before
|
|
115
|
+
preparing the draft:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
rolino integrations delivery-options --project PROJECT_ID --provider TIKTOK
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For direct publishing, pass the exact returned visibility and explicit yes/no
|
|
122
|
+
choices for comments, duet, stitch, commercial content, own-brand promotion,
|
|
123
|
+
third-party promotion, and AI-generated content. Use `--tiktok-mode inbox` for
|
|
124
|
+
a mobile inbox handoff. Add `--tiktok-settings-reviewed` only after reviewing
|
|
125
|
+
those fresh choices; it is deliberately separate from `--yes`, which confirms
|
|
126
|
+
the Rolino draft write. Editing TikTok caption, media, destinations, or its
|
|
127
|
+
override clears the prior settings review.
|
|
128
|
+
|
|
129
|
+
Bluesky drafts can be text-only or include up to four stored JPEG, PNG, or WebP
|
|
130
|
+
images. Use `--bluesky-caption` for a destination-specific override of up to
|
|
131
|
+
300 graphemes:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
rolino posts create --project PROJECT_ID --caption "Shared launch" --platform BLUESKY --bluesky-caption "Launch day 🦋" --yes
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
LinkedIn drafts can be text-only or include up to ten stored JPEG/PNG images.
|
|
138
|
+
Refresh the connected member and server policy before preparing the draft, and
|
|
139
|
+
use `--linkedin-caption` for a destination override:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
rolino integrations delivery-options --project PROJECT_ID --provider LINKEDIN
|
|
143
|
+
rolino posts create --project PROJECT_ID --caption "Shared launch" --platform LINKEDIN --linkedin-caption "Launch day for our LinkedIn network" --media IMAGE_ASSET_ID --yes
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The connected OAuth member is the author; the developer app's associated
|
|
147
|
+
Company Page is not a publishing target. If final post creation has an unknown
|
|
148
|
+
outcome, inspect that member's LinkedIn feed before retrying.
|
|
149
|
+
|
|
117
150
|
Project creation also requires interactive confirmation or `--yes`. It
|
|
118
151
|
atomically saves a project and its paired brand, but does not start research,
|
|
119
152
|
connect accounts, schedule posts, or publish. A fresh CLI invocation generates
|
|
@@ -147,13 +180,14 @@ arguments. YouTube remains `PREPARING` while its resumable upload and provider
|
|
|
147
180
|
processing are incomplete; only provider-confirmed visibility becomes
|
|
148
181
|
published.
|
|
149
182
|
|
|
150
|
-
The CLI cannot enable YouTube or
|
|
151
|
-
Those are server-side deployment controls. Self-hosted operators should
|
|
152
|
-
complete the
|
|
153
|
-
[YouTube operations guide](https://github.com/deifos/rolino/blob/main/docs/integrations/youtube.md)
|
|
154
|
-
and
|
|
155
|
-
[Bluesky operations guide](https://github.com/deifos/rolino/blob/main/docs/integrations/bluesky.md)
|
|
156
|
-
|
|
183
|
+
The CLI cannot enable YouTube, Bluesky, or LinkedIn, or infer provider approval.
|
|
184
|
+
Those are server-side deployment controls. Self-hosted operators should
|
|
185
|
+
complete the
|
|
186
|
+
[YouTube operations guide](https://github.com/deifos/rolino/blob/main/docs/integrations/youtube.md)
|
|
187
|
+
and
|
|
188
|
+
[Bluesky operations guide](https://github.com/deifos/rolino/blob/main/docs/integrations/bluesky.md), and
|
|
189
|
+
[LinkedIn operations guide](https://github.com/deifos/rolino/blob/main/docs/integrations/linkedin.md)
|
|
190
|
+
before live testing.
|
|
157
191
|
|
|
158
192
|
Configuration is read from `ROLINO_URL`, `ROLINO_TOKEN`, `ROLINO_CONFIG_DIR`,
|
|
159
193
|
`ROLINO_OUTPUT`, `ROLINO_TIMEOUT`, and the short-lived
|
|
@@ -183,8 +217,8 @@ Stable exit codes:
|
|
|
183
217
|
|
|
184
218
|
## Support, security, and license
|
|
185
219
|
|
|
186
|
-
Stable releases are published only after the maintainer approves the release
|
|
187
|
-
gate. Report bugs through the
|
|
220
|
+
Stable releases are published only after the maintainer approves the release
|
|
221
|
+
gate. Report bugs through the
|
|
188
222
|
[Rolino issue tracker](https://github.com/deifos/rolino/issues) and security
|
|
189
223
|
issues through the repository's
|
|
190
224
|
[security policy](https://github.com/deifos/rolino/security/policy).
|
package/dist/bin.cjs
CHANGED
|
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_node_crypto2 = require("crypto");
|
|
28
|
+
var import_node_fs2 = require("fs");
|
|
28
29
|
var import_promises2 = require("fs/promises");
|
|
29
30
|
var import_node_path2 = require("path");
|
|
30
31
|
var import_promises3 = require("readline/promises");
|
|
@@ -33,7 +34,7 @@ var import_commander = require("commander");
|
|
|
33
34
|
// package.json
|
|
34
35
|
var package_default = {
|
|
35
36
|
name: "@rolino/cli",
|
|
36
|
-
version: "0.
|
|
37
|
+
version: "0.3.0",
|
|
37
38
|
description: "Agent-friendly command-line interface for Rolino",
|
|
38
39
|
type: "module",
|
|
39
40
|
license: "MIT",
|
|
@@ -90,9 +91,9 @@ var package_default = {
|
|
|
90
91
|
dev: "tsx src/bin.ts"
|
|
91
92
|
},
|
|
92
93
|
dependencies: {
|
|
93
|
-
"@rolino/contracts": "0.
|
|
94
|
-
"@rolino/local-auth": "0.
|
|
95
|
-
"@rolino/sdk": "0.
|
|
94
|
+
"@rolino/contracts": "0.3.0",
|
|
95
|
+
"@rolino/local-auth": "0.3.0",
|
|
96
|
+
"@rolino/sdk": "0.3.0",
|
|
96
97
|
commander: "^15.0.0",
|
|
97
98
|
open: "^11.0.0"
|
|
98
99
|
},
|
|
@@ -476,7 +477,13 @@ function formatPost(post) {
|
|
|
476
477
|
] : [],
|
|
477
478
|
...post.publishedAt ? [`Published: ${post.publishedAt}`] : [],
|
|
478
479
|
`Media: ${post.media.length}`,
|
|
479
|
-
`Destinations: ${post.destinations.map((destination) => `${destination.provider}:${queued && destination.status === "SCHEDULED" ? "QUEUED" : destination.status}`).join(", ") || "none"}
|
|
480
|
+
`Destinations: ${post.destinations.map((destination) => `${destination.provider}:${queued && destination.status === "SCHEDULED" ? "QUEUED" : destination.status}`).join(", ") || "none"}`,
|
|
481
|
+
...post.providerSettings?.TIKTOK ? [
|
|
482
|
+
`TikTok: ${post.providerSettings.TIKTOK.postMode}, ${post.providerSettings.TIKTOK.privacyLevel ?? "visibility not set"}, settings ${post.providerSettings.TIKTOK.consentedAt ? "reviewed" : "not reviewed"}`
|
|
483
|
+
] : [],
|
|
484
|
+
...post.providerSettings?.YOUTUBE ? [
|
|
485
|
+
`YouTube: ${post.providerSettings.YOUTUBE.privacyStatus}, ${post.providerSettings.YOUTUBE.title}`
|
|
486
|
+
] : []
|
|
480
487
|
].join("\n");
|
|
481
488
|
}
|
|
482
489
|
function formatPostSchedulePending(pending) {
|
|
@@ -490,6 +497,28 @@ function formatIntegrationHealth(data) {
|
|
|
490
497
|
const rows = data.items.map((item) => `${item.provider} ${item.connected ? "connected" : "disconnected"} ${item.health.status} ${item.health.isStale ? "stale" : "current"} ${item.health.message}`);
|
|
491
498
|
return ["PROVIDER CONNECTION HEALTH FRESHNESS DETAIL", ...rows].join("\n");
|
|
492
499
|
}
|
|
500
|
+
function formatProviderDeliveryOptions(options) {
|
|
501
|
+
switch (options.provider) {
|
|
502
|
+
case "TIKTOK":
|
|
503
|
+
return [
|
|
504
|
+
`TikTok delivery options for ${options.account.displayName ?? options.account.username ?? "connected account"}`,
|
|
505
|
+
`Checked: ${options.checkedAt}`,
|
|
506
|
+
`Health: ${options.health.status} (${options.health.code}) ${options.health.message}`,
|
|
507
|
+
`Modes: ${options.allowedPostModes.join(", ") || "none"}`,
|
|
508
|
+
`Visibility: ${options.visibilityOptions.join(", ") || "none"}`,
|
|
509
|
+
`Interactions: comments ${options.interactions.comment.available ? "available" : "disabled"}, duet ${options.interactions.duet.available ? "available" : "disabled"}, stitch ${options.interactions.stitch.available ? "available" : "disabled"}`,
|
|
510
|
+
`Maximum video duration: ${options.maxVideoDurationMs === null ? "unknown" : `${options.maxVideoDurationMs} ms`}`
|
|
511
|
+
].join("\n");
|
|
512
|
+
case "LINKEDIN":
|
|
513
|
+
return [
|
|
514
|
+
`LinkedIn delivery options for ${options.account.displayName ?? "connected member"}`,
|
|
515
|
+
`Checked: ${options.checkedAt}`,
|
|
516
|
+
`Health: ${options.health.status} (${options.health.code}) ${options.health.message}`,
|
|
517
|
+
`Post types: ${options.supportedPostTypes.join(", ")}`,
|
|
518
|
+
`Images: up to ${options.image.maxItems}; ${options.image.mimeTypes.join(", ")}; under ${options.image.maxPixelsExclusive.toLocaleString()} pixels`
|
|
519
|
+
].join("\n");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
493
522
|
function formatPostReadiness(readiness) {
|
|
494
523
|
const attention = readiness.checks.filter((check) => check.status !== "pass");
|
|
495
524
|
const summary = readiness.hasBlockingChecks ? "Post is not ready to publish." : readiness.requiresLiveRefresh.length ? "Local checks passed; live provider health must be refreshed." : "Post is ready to publish.";
|
|
@@ -834,7 +863,32 @@ function collectPublishingProvider(value, previous) {
|
|
|
834
863
|
return [...previous ?? [], publishingProvider(value)];
|
|
835
864
|
}
|
|
836
865
|
function collectString(value, previous) {
|
|
837
|
-
return [...previous, value];
|
|
866
|
+
return [...previous ?? [], value];
|
|
867
|
+
}
|
|
868
|
+
function deliveryOptionsProvider(value) {
|
|
869
|
+
const normalized = value.toUpperCase();
|
|
870
|
+
const parsed = import_contracts2.ProviderDeliveryOptionsProviderSchema.safeParse(normalized);
|
|
871
|
+
if (parsed.success) return parsed.data;
|
|
872
|
+
throw new import_commander.InvalidArgumentError(
|
|
873
|
+
`Provider must be one of ${import_contracts2.ProviderDeliveryOptionsProviderSchema.options.join(", ")}.`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
function tiktokPostMode(value) {
|
|
877
|
+
const normalized = value.trim().toLowerCase();
|
|
878
|
+
if (normalized === "direct" || normalized === "direct_post") {
|
|
879
|
+
return "DIRECT_POST";
|
|
880
|
+
}
|
|
881
|
+
if (normalized === "inbox" || normalized === "media_upload") {
|
|
882
|
+
return "MEDIA_UPLOAD";
|
|
883
|
+
}
|
|
884
|
+
throw new import_commander.InvalidArgumentError("TikTok mode must be direct or inbox.");
|
|
885
|
+
}
|
|
886
|
+
function nonnegativeInteger(value) {
|
|
887
|
+
const parsed = Number(value);
|
|
888
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
889
|
+
throw new import_commander.InvalidArgumentError("Expected a nonnegative integer.");
|
|
890
|
+
}
|
|
891
|
+
return parsed;
|
|
838
892
|
}
|
|
839
893
|
function youtubePrivacy(value) {
|
|
840
894
|
const normalized = value.toUpperCase();
|
|
@@ -852,7 +906,7 @@ function yesOrNo(value) {
|
|
|
852
906
|
throw new import_commander.InvalidArgumentError("Expected yes or no.");
|
|
853
907
|
}
|
|
854
908
|
function youtubeSettings(options) {
|
|
855
|
-
const hasYouTubeOptions = options.youtubeTitle !== void 0 || options.youtubeCategoryId !== void 0 || options.youtubePrivacy !== void 0 || options.youtubeMadeForKids !== void 0 || options.youtubeSyntheticMedia === true || options.youtubeNotifySubscribers === false || options.youtubeTag
|
|
909
|
+
const hasYouTubeOptions = options.youtubeTitle !== void 0 || options.youtubeCategoryId !== void 0 || options.youtubePrivacy !== void 0 || options.youtubeMadeForKids !== void 0 || options.youtubeSyntheticMedia === true || options.youtubeNotifySubscribers === false || (options.youtubeTag?.length ?? 0) > 0;
|
|
856
910
|
if (!hasYouTubeOptions) return null;
|
|
857
911
|
return import_contracts2.YouTubePostSettingsSchema.parse({
|
|
858
912
|
title: options.youtubeTitle,
|
|
@@ -861,7 +915,7 @@ function youtubeSettings(options) {
|
|
|
861
915
|
madeForKids: options.youtubeMadeForKids,
|
|
862
916
|
containsSyntheticMedia: options.youtubeSyntheticMedia ?? false,
|
|
863
917
|
notifySubscribers: options.youtubeNotifySubscribers ?? true,
|
|
864
|
-
tags: options.youtubeTag
|
|
918
|
+
tags: options.youtubeTag ?? []
|
|
865
919
|
});
|
|
866
920
|
}
|
|
867
921
|
function positiveInteger(value) {
|
|
@@ -1007,6 +1061,23 @@ function errorForOutput(error) {
|
|
|
1007
1061
|
}
|
|
1008
1062
|
return { code: "CLI_ERROR", message: "The command failed unexpectedly." };
|
|
1009
1063
|
}
|
|
1064
|
+
function tiktokSettings(options) {
|
|
1065
|
+
const hasTikTokOptions = options.tiktokMode !== void 0 || options.tiktokVisibility !== void 0 || options.tiktokComments !== void 0 || options.tiktokDuet !== void 0 || options.tiktokStitch !== void 0 || options.tiktokCommercialContent !== void 0 || options.tiktokPromotesOwnBrand !== void 0 || options.tiktokPromotesThirdParty !== void 0 || options.tiktokAiGenerated !== void 0 || options.tiktokCoverTimestampMs !== void 0 || options.tiktokSettingsReviewed === true;
|
|
1066
|
+
if (!hasTikTokOptions) return void 0;
|
|
1067
|
+
return import_contracts2.TikTokDraftSettingsSchema.parse({
|
|
1068
|
+
postMode: options.tiktokMode,
|
|
1069
|
+
privacyLevel: options.tiktokVisibility,
|
|
1070
|
+
allowComment: options.tiktokComments,
|
|
1071
|
+
allowDuet: options.tiktokDuet,
|
|
1072
|
+
allowStitch: options.tiktokStitch,
|
|
1073
|
+
commercialContentEnabled: options.tiktokCommercialContent,
|
|
1074
|
+
promotesOwnBrand: options.tiktokPromotesOwnBrand,
|
|
1075
|
+
promotesThirdParty: options.tiktokPromotesThirdParty,
|
|
1076
|
+
isAiGenerated: options.tiktokAiGenerated,
|
|
1077
|
+
videoCoverTimestampMs: options.tiktokCoverTimestampMs,
|
|
1078
|
+
reviewed: options.tiktokSettingsReviewed === true
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1010
1081
|
function recoverySuggestions(code, command) {
|
|
1011
1082
|
switch (code) {
|
|
1012
1083
|
case "AUTH_REQUIRED":
|
|
@@ -1210,8 +1281,8 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1210
1281
|
const contentType = contentTypes[(0, import_node_path2.extname)(absolutePath).toLowerCase()];
|
|
1211
1282
|
if (!contentType) throw new TypeError("Use a JPEG, PNG, WebP, MP4, or MOV file.");
|
|
1212
1283
|
await requireWriteConsent({ yes: local.yes, global, runtime, subject: "media", preview: [`Upload ${(0, import_node_path2.basename)(absolutePath)} to Rolino?`, `Project: ${local.project}`, `Size: ${details.size} bytes`, "This adds a reusable media asset but does not create, schedule, or publish a post."].join("\n") });
|
|
1213
|
-
const
|
|
1214
|
-
const asset = await client.media.upload(local.project, { fileName: (0, import_node_path2.basename)(absolutePath), contentType, fileSize: details.size, body
|
|
1284
|
+
const body = await (0, import_node_fs2.openAsBlob)(absolutePath, { type: contentType });
|
|
1285
|
+
const asset = await client.media.upload(local.project, { fileName: (0, import_node_path2.basename)(absolutePath), contentType, fileSize: details.size, body }, { requestId: context.requestId });
|
|
1215
1286
|
writeSuccess(context, asset, formatMediaAsset(asset), [`rolino posts create --project ${local.project} --caption <text> --media ${asset.id} --agent --yes`]);
|
|
1216
1287
|
}
|
|
1217
1288
|
});
|
|
@@ -1434,7 +1505,7 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1434
1505
|
});
|
|
1435
1506
|
});
|
|
1436
1507
|
const posts = program.command("posts").description("Read and prepare posts in a Rolino project");
|
|
1437
|
-
posts.command("create").description("Create a draft post without scheduling or publishing it").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--caption <text>", "draft caption; use an empty string for media-only drafts").option("--platform <provider>", "INSTAGRAM, TIKTOK, YOUTUBE, or
|
|
1508
|
+
posts.command("create").description("Create a draft post without scheduling or publishing it").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--caption <text>", "draft caption; use an empty string for media-only drafts").option("--platform <provider>", "INSTAGRAM, TIKTOK, YOUTUBE, BLUESKY, or LINKEDIN; repeat as needed", collectPublishingProvider, []).option("--media <asset-id>", "existing project media asset ID; repeat for multiple", collectString, []).option("--instagram-caption <text>", "Instagram-specific caption override").option("--tiktok-caption <text>", "TikTok-specific caption override").option("--tiktok-mode <mode>", "direct publishing or inbox draft delivery", tiktokPostMode).option("--tiktok-visibility <value>", "exact visibility returned by integrations delivery-options").option("--tiktok-comments <yes|no>", "allow TikTok comments", yesOrNo).option("--tiktok-duet <yes|no>", "allow TikTok duets", yesOrNo).option("--tiktok-stitch <yes|no>", "allow TikTok stitches", yesOrNo).option("--tiktok-commercial-content <yes|no>", "declare commercial TikTok content", yesOrNo).option("--tiktok-promotes-own-brand <yes|no>", "declare own-brand promotion", yesOrNo).option("--tiktok-promotes-third-party <yes|no>", "declare third-party promotion", yesOrNo).option("--tiktok-ai-generated <yes|no>", "declare AI-generated TikTok content", yesOrNo).option("--tiktok-cover-timestamp-ms <number>", "video cover timestamp in milliseconds", nonnegativeInteger).option("--tiktok-settings-reviewed", "confirm the TikTok account choices were reviewed; separate from --yes").option("--youtube-caption <text>", "YouTube description override; otherwise the shared caption is used").option("--bluesky-caption <text>", "Bluesky-specific caption override; limited to 300 graphemes").option("--linkedin-caption <text>", "LinkedIn-specific caption override; limited to 3,000 characters").option("--youtube-title <text>", "required YouTube video title").option("--youtube-category-id <id>", "required numeric YouTube video category ID").option("--youtube-privacy <status>", "required PUBLIC, UNLISTED, or PRIVATE visibility", youtubePrivacy).option("--youtube-made-for-kids <yes|no>", "required explicit YouTube audience declaration", yesOrNo).option("--youtube-synthetic-media", "declare realistic altered or synthetic media to YouTube").option("--no-youtube-notify-subscribers", "disable eligible YouTube subscriber notifications").option("--youtube-tag <tag>", "YouTube tag; repeat as needed", collectString, []).option("--idempotency-key <key>", "stable retry key; defaults to the request ID").option("--yes", "confirm creation without an interactive prompt").action(async (local) => {
|
|
1438
1509
|
const global = program.opts();
|
|
1439
1510
|
commandExitCode = await execute({
|
|
1440
1511
|
command: "posts create",
|
|
@@ -1460,9 +1531,10 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1460
1531
|
...local.instagramCaption === void 0 ? {} : { INSTAGRAM: local.instagramCaption },
|
|
1461
1532
|
...local.tiktokCaption === void 0 ? {} : { TIKTOK: local.tiktokCaption },
|
|
1462
1533
|
...local.youtubeCaption === void 0 ? {} : { YOUTUBE: local.youtubeCaption },
|
|
1463
|
-
...local.blueskyCaption === void 0 ? {} : { BLUESKY: local.blueskyCaption }
|
|
1534
|
+
...local.blueskyCaption === void 0 ? {} : { BLUESKY: local.blueskyCaption },
|
|
1535
|
+
...local.linkedinCaption === void 0 ? {} : { LINKEDIN: local.linkedinCaption }
|
|
1464
1536
|
},
|
|
1465
|
-
tiktokSettings: null,
|
|
1537
|
+
tiktokSettings: tiktokSettings(local) ?? null,
|
|
1466
1538
|
youtubeSettings: youtubeSettings(local)
|
|
1467
1539
|
};
|
|
1468
1540
|
const post = await client.posts.create(local.project, input, {
|
|
@@ -1478,7 +1550,7 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1478
1550
|
}
|
|
1479
1551
|
});
|
|
1480
1552
|
});
|
|
1481
|
-
posts.command("update").description("
|
|
1553
|
+
posts.command("update").description("Update selected draft fields using optimistic concurrency").argument("<post-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--expected-version <number>", "current post version from posts show", positiveInteger).option("--caption <text>", "replacement draft caption; omitted fields are preserved").option("--platform <provider>", "replacement destinations; repeat as needed", collectPublishingProvider).option("--clear-platforms", "remove every draft destination").option("--media <asset-id>", "replacement media asset ID; repeat for multiple", collectString).option("--clear-media", "remove every media asset from the draft").option("--instagram-caption <text>", "Instagram-specific caption override").option("--tiktok-caption <text>", "TikTok-specific caption override").option("--tiktok-mode <mode>", "direct publishing or inbox draft delivery", tiktokPostMode).option("--tiktok-visibility <value>", "exact visibility returned by integrations delivery-options").option("--tiktok-comments <yes|no>", "allow TikTok comments", yesOrNo).option("--tiktok-duet <yes|no>", "allow TikTok duets", yesOrNo).option("--tiktok-stitch <yes|no>", "allow TikTok stitches", yesOrNo).option("--tiktok-commercial-content <yes|no>", "declare commercial TikTok content", yesOrNo).option("--tiktok-promotes-own-brand <yes|no>", "declare own-brand promotion", yesOrNo).option("--tiktok-promotes-third-party <yes|no>", "declare third-party promotion", yesOrNo).option("--tiktok-ai-generated <yes|no>", "declare AI-generated TikTok content", yesOrNo).option("--tiktok-cover-timestamp-ms <number>", "video cover timestamp in milliseconds", nonnegativeInteger).option("--tiktok-settings-reviewed", "confirm the TikTok account choices were reviewed; separate from --yes").option("--youtube-caption <text>", "YouTube description override; otherwise the shared caption is used").option("--bluesky-caption <text>", "Bluesky-specific caption override; limited to 300 graphemes").option("--linkedin-caption <text>", "LinkedIn-specific caption override; limited to 3,000 characters").option("--youtube-title <text>", "required YouTube video title").option("--youtube-category-id <id>", "required numeric YouTube video category ID").option("--youtube-privacy <status>", "required PUBLIC, UNLISTED, or PRIVATE visibility", youtubePrivacy).option("--youtube-made-for-kids <yes|no>", "required explicit YouTube audience declaration", yesOrNo).option("--youtube-synthetic-media", "declare realistic altered or synthetic media to YouTube").option("--no-youtube-notify-subscribers", "disable eligible YouTube subscriber notifications").option("--youtube-tag <tag>", "replacement YouTube tag; repeat as needed", collectString).option("--idempotency-key <key>", "stable retry key; defaults to the request ID").option("--yes", "confirm the update without an interactive prompt").action(async (postId, local) => {
|
|
1482
1554
|
const global = program.opts();
|
|
1483
1555
|
commandExitCode = await execute({
|
|
1484
1556
|
command: "posts update",
|
|
@@ -1490,24 +1562,34 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1490
1562
|
global,
|
|
1491
1563
|
runtime,
|
|
1492
1564
|
preview: [
|
|
1493
|
-
`
|
|
1565
|
+
`Update draft ${postId}?`,
|
|
1494
1566
|
`Project: ${local.project}`,
|
|
1495
1567
|
`Expected version: ${local.expectedVersion}`,
|
|
1496
1568
|
"This will not schedule or publish the post."
|
|
1497
1569
|
].join("\n")
|
|
1498
1570
|
});
|
|
1571
|
+
if (local.clearPlatforms && local.platform !== void 0) {
|
|
1572
|
+
throw new TypeError("Use either --platform or --clear-platforms, not both.");
|
|
1573
|
+
}
|
|
1574
|
+
if (local.clearMedia && local.media !== void 0) {
|
|
1575
|
+
throw new TypeError("Use either --media or --clear-media, not both.");
|
|
1576
|
+
}
|
|
1577
|
+
const captionOverrides = {
|
|
1578
|
+
...local.instagramCaption === void 0 ? {} : { INSTAGRAM: local.instagramCaption },
|
|
1579
|
+
...local.tiktokCaption === void 0 ? {} : { TIKTOK: local.tiktokCaption },
|
|
1580
|
+
...local.youtubeCaption === void 0 ? {} : { YOUTUBE: local.youtubeCaption },
|
|
1581
|
+
...local.blueskyCaption === void 0 ? {} : { BLUESKY: local.blueskyCaption },
|
|
1582
|
+
...local.linkedinCaption === void 0 ? {} : { LINKEDIN: local.linkedinCaption }
|
|
1583
|
+
};
|
|
1584
|
+
const resolvedTikTokSettings = tiktokSettings(local);
|
|
1585
|
+
const resolvedYouTubeSettings = youtubeSettings(local);
|
|
1499
1586
|
const input = {
|
|
1500
|
-
caption: local.caption,
|
|
1501
|
-
platforms: local.platform,
|
|
1502
|
-
mediaAssetIds: local.media,
|
|
1503
|
-
captionOverrides: {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
...local.youtubeCaption === void 0 ? {} : { YOUTUBE: local.youtubeCaption },
|
|
1507
|
-
...local.blueskyCaption === void 0 ? {} : { BLUESKY: local.blueskyCaption }
|
|
1508
|
-
},
|
|
1509
|
-
tiktokSettings: null,
|
|
1510
|
-
youtubeSettings: youtubeSettings(local)
|
|
1587
|
+
...local.caption === void 0 ? {} : { caption: local.caption },
|
|
1588
|
+
...local.platform === void 0 && !local.clearPlatforms ? {} : { platforms: local.clearPlatforms ? [] : local.platform },
|
|
1589
|
+
...local.media === void 0 && !local.clearMedia ? {} : { mediaAssetIds: local.clearMedia ? [] : local.media },
|
|
1590
|
+
...Object.keys(captionOverrides).length ? { captionOverrides } : {},
|
|
1591
|
+
...resolvedTikTokSettings === void 0 ? {} : { tiktokSettings: resolvedTikTokSettings },
|
|
1592
|
+
...resolvedYouTubeSettings === null ? {} : { youtubeSettings: resolvedYouTubeSettings }
|
|
1511
1593
|
};
|
|
1512
1594
|
const post = await client.posts.update(local.project, postId, input, {
|
|
1513
1595
|
requestId: context.requestId,
|
|
@@ -1645,7 +1727,7 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1645
1727
|
});
|
|
1646
1728
|
});
|
|
1647
1729
|
const publish = posts.command("publish").description("Preview and queue server-confirmed immediate publishing");
|
|
1648
|
-
publish.command("preview").description("Validate exact destinations and issue a five-minute confirmation").argument("<post-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--expected-version <number>", "current post version from posts show", positiveInteger).requiredOption("--platform <provider>", "INSTAGRAM, TIKTOK, YOUTUBE, or
|
|
1730
|
+
publish.command("preview").description("Validate exact destinations and issue a five-minute confirmation").argument("<post-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--expected-version <number>", "current post version from posts show", positiveInteger).requiredOption("--platform <provider>", "INSTAGRAM, TIKTOK, YOUTUBE, BLUESKY, or LINKEDIN; repeat as needed", collectPublishingProvider).action(async (postId, local) => {
|
|
1649
1731
|
const global = program.opts();
|
|
1650
1732
|
commandExitCode = await execute({
|
|
1651
1733
|
command: "posts publish preview",
|
|
@@ -1728,6 +1810,27 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1728
1810
|
}
|
|
1729
1811
|
});
|
|
1730
1812
|
});
|
|
1813
|
+
integrations.command("delivery-options").description("Refresh secret-safe creator delivery choices for one provider").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--provider <provider>", "publishing provider; TIKTOK or LINKEDIN", deliveryOptionsProvider).action(async (local) => {
|
|
1814
|
+
const global = program.opts();
|
|
1815
|
+
commandExitCode = await execute({
|
|
1816
|
+
command: "integrations delivery-options",
|
|
1817
|
+
global,
|
|
1818
|
+
runtime,
|
|
1819
|
+
async action(context, client) {
|
|
1820
|
+
const options = await client.integrations.deliveryOptions(
|
|
1821
|
+
local.project,
|
|
1822
|
+
local.provider,
|
|
1823
|
+
{ requestId: context.requestId }
|
|
1824
|
+
);
|
|
1825
|
+
writeSuccess(
|
|
1826
|
+
context,
|
|
1827
|
+
options,
|
|
1828
|
+
formatProviderDeliveryOptions(options),
|
|
1829
|
+
[`rolino posts create --project ${local.project} --caption <text> --platform ${local.provider} --agent --yes`]
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1731
1834
|
const calendar = program.command("calendar").description("Read scheduled publishing events");
|
|
1732
1835
|
calendar.command("list").description("List scheduled posts in a bounded calendar window").requiredOption("--project <project-id>", "exact Rolino project ID").option("--from <datetime>", "inclusive ISO-8601 window start", isoDateTime).option("--to <datetime>", "exclusive ISO-8601 window end", isoDateTime).option("--limit <number>", "maximum events to return", (value) => {
|
|
1733
1836
|
const parsed = Number(value);
|