@rolino/cli 0.5.0-beta.0 → 0.5.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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # @rolino/cli
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4f9e790: Add the optional `blog:manage` capability and synchronized agent-first Blog
8
+ setup, import, cadence planning, plan review, article creation, confirmed
9
+ connection provisioning, and safe verification operations.
10
+ - 1edd1e7: Add published Blog Studio delivery contracts and the server-only Next.js 16 Blog Studio package.
11
+ - 74a13ea: Expose immediate versus scheduled delivery and destination lifecycle stages,
12
+ preserve YouTube visibility for immediate publishing, and clarify remote
13
+ schedule confirmation across public clients.
14
+ - 4af607d: Add independent exact-bundle Blog approval, reviewed image upload and generation,
15
+ confirmed publication scheduling and cancellation, and the complete authorized
16
+ agent workflow without widening existing credentials.
17
+ - 4a893dd: Add provider-neutral Blog publishing destinations, safe delivery inspection,
18
+ and exact destination binding across public agent interfaces.
19
+ - 843a1db: Add the exact optional `seo:read` capability and bounded read-only SEO
20
+ opportunity and weekly-report parity across public agent contracts, clients,
21
+ CLI commands, and closed-world MCP tools. SEO reads now use the version 3 task
22
+ contract with the exact Ready, Research, or Rejected decision, selected format,
23
+ complete deliverables, bounded evidence, safe URLs, limitations, and no-publish
24
+ rules. Public output uses readable topic names and separate topic-relevance and
25
+ action-readiness confidence. Research tasks compare all supported formats and
26
+ must validate or reject the finding before a separate draft action is allowed.
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [4f9e790]
31
+ - Updated dependencies [1edd1e7]
32
+ - Updated dependencies [74a13ea]
33
+ - Updated dependencies [4af607d]
34
+ - Updated dependencies [4a893dd]
35
+ - Updated dependencies [843a1db]
36
+ - @rolino/contracts@0.5.0
37
+ - @rolino/sdk@0.5.0
38
+ - @rolino/local-auth@0.5.0
39
+
3
40
  ## 0.5.0-beta.0
4
41
 
5
42
  ### Minor Changes
@@ -37,6 +74,10 @@
37
74
 
38
75
  ### Minor Changes
39
76
 
77
+ - Add Blog image generation, exact local-file upload, image review, confirmed
78
+ exact-bundle approval, future scheduling, and schedule cancellation commands
79
+ with deterministic agent output, stable idempotency, safe errors, and
80
+ independent `blog:write`, `blog:approve`, and `blog:publish` boundaries.
40
81
  - Add Blog destination discovery, delivery inspection, and exact destination selection for publication commands.
41
82
 
42
83
  ## 0.4.0
package/README.md CHANGED
@@ -53,12 +53,21 @@ rolino integrations delivery-options --project PROJECT_ID --provider TIKTOK|LINK
53
53
  rolino calendar list --project PROJECT_ID [--from ISO_DATE --to ISO_DATE] [--limit 50] [--cursor CURSOR]
54
54
  rolino seo opportunities list --project PROJECT_ID [--limit 20] [--cursor CURSOR] [--kind KIND] [--impact HIGH|MEDIUM|LOW]
55
55
  rolino seo opportunities show OPPORTUNITY_ID --project PROJECT_ID
56
- rolino seo reports list --project PROJECT_ID [--limit 10] [--cursor CURSOR] [--status COMPLETE|PARTIAL]
57
- rolino seo reports show REPORT_ID --project PROJECT_ID
58
- rolino blog destinations providers --project PROJECT_ID
59
- rolino blog destinations list --project PROJECT_ID
60
- rolino blog destinations deliveries --project PROJECT_ID [--article ARTICLE_ID]
61
- rolino doctor
56
+ rolino seo reports list --project PROJECT_ID [--limit 10] [--cursor CURSOR] [--status COMPLETE|PARTIAL]
57
+ rolino seo reports show REPORT_ID --project PROJECT_ID
58
+ rolino blog destinations providers --project PROJECT_ID
59
+ rolino blog destinations list --project PROJECT_ID
60
+ rolino blog destinations deliveries --project PROJECT_ID [--article ARTICLE_ID]
61
+ rolino blog images generate ARTICLE_ID --project PROJECT_ID --revision REVISION_ID
62
+ rolino blog images upload ARTICLE_ID FILE --project PROJECT_ID --revision REVISION_ID --alt-text TEXT
63
+ rolino blog images review ARTICLE_ID IMAGE_ID --project PROJECT_ID --decision APPROVED|REJECTED --expected-version VERSION [--alt-text TEXT]
64
+ rolino blog approve preview ARTICLE_ID --project PROJECT_ID --revision REVISION_ID
65
+ rolino blog approve execute ARTICLE_ID --project PROJECT_ID --revision REVISION_ID --confirmation-token TOKEN --idempotency-key KEY --yes
66
+ rolino blog schedule preview ARTICLE_ID --project PROJECT_ID --revision REVISION_ID --at ISO --timezone IANA [--destination ID...]
67
+ rolino blog schedule execute ARTICLE_ID --project PROJECT_ID --revision REVISION_ID --at ISO --timezone IANA --confirmation-token TOKEN --idempotency-key KEY --yes
68
+ rolino blog schedule cancel preview ARTICLE_ID --project PROJECT_ID
69
+ rolino blog schedule cancel execute ARTICLE_ID --project PROJECT_ID --confirmation-token TOKEN --idempotency-key KEY --yes
70
+ rolino doctor
62
71
  ```
63
72
 
64
73
  Interactive login opens the configured Rolino instance in a browser, obtains
@@ -262,26 +271,54 @@ gain it automatically. Start every setup with:
262
271
  rolino --agent blog setup status --project PROJECT_ID
263
272
  ```
264
273
 
265
- The `blog site`, `blog plan`, `blog article`, `blog connection`, and
266
- `blog destinations` command groups mirror the versioned API. Use these safe
267
- publishing reads before setup or publication:
268
-
269
- ```bash
270
- rolino --agent blog destinations providers --project PROJECT_ID
271
- rolino --agent blog destinations list --project PROJECT_ID
272
- rolino --agent blog destinations deliveries --project PROJECT_ID
273
- rolino --agent blog destinations deliveries --project PROJECT_ID --article ARTICLE_ID
274
- ```
275
-
276
- The provider catalog requires `blog:manage`. It returns only providers enabled
277
- for the workspace, safe capabilities, and official documentation. Destination
278
- and delivery-attempt reads require `blog:read`. They omit draft content,
279
- prompts, evidence, secrets, and raw provider responses. A returned remote URL
280
- is not proof that a queued or failed delivery is live.
281
-
282
- Mutations require a stable idempotency key.
283
- Connection provisioning also requires preview, explicit confirmation, and
284
- execute. A first successful execute can return a delivery token or webhook
274
+ The `blog site`, `blog plan`, `blog article`, `blog connection`, and
275
+ `blog destinations` command groups mirror the versioned API. Use these safe
276
+ publishing reads before setup or publication:
277
+
278
+ ```bash
279
+ rolino --agent blog destinations providers --project PROJECT_ID
280
+ rolino --agent blog destinations list --project PROJECT_ID
281
+ rolino --agent blog destinations deliveries --project PROJECT_ID
282
+ rolino --agent blog destinations deliveries --project PROJECT_ID --article ARTICLE_ID
283
+ ```
284
+
285
+ The provider catalog requires `blog:manage`. It returns only providers enabled
286
+ for the workspace, safe capabilities, and official documentation. Destination
287
+ and delivery-attempt reads require `blog:read`. They omit draft content,
288
+ prompts, evidence, secrets, and raw provider responses. A returned remote URL
289
+ is not proof that a queued or failed delivery is live.
290
+
291
+ Mutations require a stable idempotency key.
292
+ Connection provisioning also requires preview, explicit confirmation, and
293
+ execute. A first successful execute can return a delivery token or webhook
285
294
  secret once; save it only in server-side environment configuration. Replays
286
295
  do not reveal it again. Blog publication remains the separate approved exact
287
296
  revision preview/execute workflow.
297
+
298
+ Blog image commands require `blog:write`; they cannot approve an article or
299
+ publish it. Image upload reads only the path in the command, accepts JPEG, PNG,
300
+ or WebP, and keeps its signed upload URL and storage key private. Approving an
301
+ exact revision and image bundle requires the independent `blog:approve`
302
+ capability and cannot publish. Scheduling, rescheduling, or canceling a future
303
+ publication requires the independent `blog:publish` capability and does not
304
+ grant approval.
305
+
306
+ Approval, schedule, and cancellation execution bind the short-lived token from
307
+ the matching preview and a stable idempotency key. Human output requires
308
+ `--yes`. JSON mode does not prompt, but it still requires the confirmation
309
+ token and idempotency key. A safe replay uses the same exact input and key.
310
+
311
+ Use this order for complete Blog work:
312
+
313
+ 1. Read setup status and current article or plan state.
314
+ 2. Ask for any missing website, cadence, destination, and access level.
315
+ 3. Import and plan through durable jobs, then poll instead of retrying blindly.
316
+ 4. Present evidence before accepting a plan item unless the user already authorized that exact bounded action.
317
+ 5. Create or update one immutable revision with catalog-backed links.
318
+ 6. Generate or upload one approved image, review it, and verify alt text.
319
+ 7. Without `blog:approve`, stop and ask the user to approve in Rolino.
320
+ 8. With `blog:approve`, preview and execute the unchanged exact bundle with confirmation and a stable idempotency key.
321
+ 9. Without `blog:publish`, stop after approval.
322
+ 10. With `blog:publish`, preview and execute unchanged immediate publication or scheduling with a stable idempotency key.
323
+ 11. Poll delivery attempts and verify the live remote URL; queued is not published.
324
+ 12. After any content or image change, restart from approval preview.
package/dist/bin.cjs CHANGED
@@ -34,7 +34,7 @@ var import_commander = require("commander");
34
34
  // package.json
35
35
  var package_default = {
36
36
  name: "@rolino/cli",
37
- version: "0.5.0-beta.0",
37
+ version: "0.5.0",
38
38
  description: "Agent-friendly command-line interface for Rolino",
39
39
  type: "module",
40
40
  license: "MIT",
@@ -91,9 +91,9 @@ var package_default = {
91
91
  dev: "tsx src/bin.ts"
92
92
  },
93
93
  dependencies: {
94
- "@rolino/contracts": "0.5.0-beta.0",
95
- "@rolino/local-auth": "0.5.0-beta.0",
96
- "@rolino/sdk": "0.5.0-beta.0",
94
+ "@rolino/contracts": "0.5.0",
95
+ "@rolino/local-auth": "0.5.0",
96
+ "@rolino/sdk": "0.5.0",
97
97
  commander: "^15.0.0",
98
98
  open: "^11.0.0"
99
99
  },
@@ -1053,6 +1053,10 @@ async function requireWriteConsent(options) {
1053
1053
  );
1054
1054
  }
1055
1055
  }
1056
+ function requireBlogExecutionConsent(options) {
1057
+ if (options.yes || resolveFormat(options.global, options.runtime) !== "human") return;
1058
+ throw new TypeError("This Blog execute command requires explicit consent. Review the confirmed operation and pass --yes.");
1059
+ }
1056
1060
  function formatMcpSetupPreview(result) {
1057
1061
  return [
1058
1062
  `Client: ${result.client === "codex" ? "Codex" : "Claude Code"}`,
@@ -2113,6 +2117,103 @@ Article: ${articleId}` });
2113
2117
  writeSuccess(context, data, JSON.stringify(data, null, 2));
2114
2118
  } });
2115
2119
  });
2120
+ const blogImages = blog.command("images").description("Create and review Blog images with blog:write; cannot approve or publish");
2121
+ blogImages.command("generate").description("Queue one exact-revision featured image with blog:write; cannot approve or publish").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--revision <revision-id>", "exact Blog revision ID").option("--editorial-brief <text>", "bounded visual direction; provider controls stay on the server").option("--idempotency-key <key>", "stable retry key; defaults to request ID").action(async (articleId, local) => {
2122
+ const global = program.opts();
2123
+ commandExitCode = await execute({ command: "blog images generate", global, runtime, async action(context, client) {
2124
+ const data = await client.blog.articles.generateImage(local.project, articleId, { revisionId: local.revision, idempotencyKey: local.idempotencyKey ?? context.requestId, editorialBrief: local.editorialBrief }, { requestId: context.requestId });
2125
+ writeSuccess(context, data, `Blog image generation queued.
2126
+ Image: ${data.image.id}
2127
+ Job: ${data.job.id}`);
2128
+ } });
2129
+ });
2130
+ blogImages.command("upload").description("Upload one JPEG, PNG, or WebP for an exact Blog revision with blog:write; cannot approve or publish").argument("<article-id>").argument("<file-path>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--revision <revision-id>", "exact Blog revision ID").requiredOption("--alt-text <text>", "reviewable image alt text").action(async (articleId, filePath, local) => {
2131
+ const global = program.opts();
2132
+ commandExitCode = await execute({ command: "blog images upload", global, runtime, async action(context, client) {
2133
+ const absolutePath = (0, import_node_path2.resolve)(runtime.cwd, filePath);
2134
+ const details = await (0, import_promises2.stat)(absolutePath).catch(() => null);
2135
+ if (!details?.isFile()) throw new TypeError("The Blog image path must point to a readable file.");
2136
+ const contentTypes = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp" };
2137
+ const extension = (0, import_node_path2.extname)(absolutePath).toLowerCase();
2138
+ const contentType = contentTypes[extension];
2139
+ if (!contentType) throw new TypeError("Use a JPEG, PNG, or WebP Blog image.");
2140
+ const body = await (0, import_node_fs2.openAsBlob)(absolutePath, { type: contentType });
2141
+ const data = await client.blog.articles.uploadImage(local.project, articleId, { revisionId: local.revision, fileName: (0, import_node_path2.basename)(absolutePath), contentType, fileSize: details.size, altText: local.altText, body }, { requestId: context.requestId });
2142
+ writeSuccess(context, data, `Blog image uploaded for review.
2143
+ Image: ${data.id}
2144
+ State: ${data.state}`);
2145
+ } });
2146
+ });
2147
+ blogImages.command("review").description("Review one exact Blog image with blog:write; does not approve the article or publish").argument("<article-id>").argument("<image-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--decision <decision>", "APPROVED or REJECTED").requiredOption("--expected-version <version>", "current image version", positiveInteger).option("--alt-text <text>", "final image alt text; required by the server for approval").option("--idempotency-key <key>", "stable retry key; defaults to request ID").action(async (articleId, imageId, local) => {
2148
+ const decision = local.decision.toUpperCase();
2149
+ if (decision !== "APPROVED" && decision !== "REJECTED") throw new TypeError("--decision must be APPROVED or REJECTED.");
2150
+ const global = program.opts();
2151
+ commandExitCode = await execute({ command: "blog images review", global, runtime, async action(context, client) {
2152
+ const data = await client.blog.articles.reviewImage(local.project, articleId, imageId, { decision, expectedVersion: local.expectedVersion, altText: local.altText ?? "", idempotencyKey: local.idempotencyKey ?? context.requestId }, { requestId: context.requestId });
2153
+ writeSuccess(context, data, `Blog image review saved.
2154
+ Image: ${data.id}
2155
+ State: ${data.state}`);
2156
+ } });
2157
+ });
2158
+ const blogApprove = blog.command("approve").description("Approve one exact Blog bundle with blog:approve; cannot publish");
2159
+ blogApprove.command("preview").description("Preview one exact approval bundle with blog:approve; creates a confirmation but cannot publish").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--revision <revision-id>", "exact Blog revision ID").action(async (articleId, local) => {
2160
+ const global = program.opts();
2161
+ commandExitCode = await execute({ command: "blog approve preview", global, runtime, async action(context, client) {
2162
+ const data = await client.blog.articles.previewApproval(local.project, articleId, { revisionId: local.revision }, { requestId: context.requestId });
2163
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2164
+ } });
2165
+ });
2166
+ blogApprove.command("execute").description("Approve the confirmed exact bundle with blog:approve; separate blog:publish access is still required").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--revision <revision-id>", "same exact revision used for preview").requiredOption("--confirmation-token <token>", "short-lived token returned by preview").requiredOption("--idempotency-key <key>", "stable retry key").option("--yes", "confirm exact bundle approval").action(async (articleId, local) => {
2167
+ const global = program.opts();
2168
+ commandExitCode = await execute({ command: "blog approve execute", global, runtime, async action(context, client) {
2169
+ requireBlogExecutionConsent({ yes: local.yes, global, runtime });
2170
+ const data = await client.blog.articles.executeApproval(local.project, articleId, { revisionId: local.revision, confirmationToken: local.confirmationToken, idempotencyKey: local.idempotencyKey }, { requestId: context.requestId });
2171
+ writeSuccess(context, data, `Exact Blog bundle approved.
2172
+ Revision: ${data.revisionId}
2173
+ Publishing still requires blog:publish.`);
2174
+ } });
2175
+ });
2176
+ const blogSchedule = blog.command("schedule").description("Schedule exact approved Blog bundles with blog:publish; does not grant approval");
2177
+ const addScheduleOptions = (command, executeSchedule) => {
2178
+ command.argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--revision <revision-id>", "exact approved Blog revision ID").requiredOption("--at <iso>", "future ISO date-time with UTC offset").requiredOption("--timezone <iana>", "explicit IANA timezone").option("--destination <destination-id...>", "exact destination IDs; defaults to the current primary");
2179
+ if (executeSchedule) command.requiredOption("--confirmation-token <token>", "short-lived token returned by preview").requiredOption("--idempotency-key <key>", "stable retry key").option("--yes", "confirm future external publication");
2180
+ return command;
2181
+ };
2182
+ addScheduleOptions(blogSchedule.command("preview").description("Preview an exact future Blog schedule with blog:publish; does not publish now"), false).action(async (articleId, local) => {
2183
+ const global = program.opts();
2184
+ commandExitCode = await execute({ command: "blog schedule preview", global, runtime, async action(context, client) {
2185
+ const data = await client.blog.articles.previewSchedule(local.project, articleId, { revisionId: local.revision, scheduledAt: local.at, timezone: local.timezone, destinationIds: local.destination }, { requestId: context.requestId });
2186
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2187
+ } });
2188
+ });
2189
+ addScheduleOptions(blogSchedule.command("execute").description("Execute a confirmed future Blog schedule with blog:publish; causes future external publication"), true).action(async (articleId, local) => {
2190
+ const global = program.opts();
2191
+ commandExitCode = await execute({ command: "blog schedule execute", global, runtime, async action(context, client) {
2192
+ requireBlogExecutionConsent({ yes: local.yes, global, runtime });
2193
+ const data = await client.blog.articles.executeSchedule(local.project, articleId, { revisionId: local.revision, scheduledAt: local.at, timezone: local.timezone, destinationIds: local.destination, confirmationToken: local.confirmationToken, idempotencyKey: local.idempotencyKey }, { requestId: context.requestId });
2194
+ writeSuccess(context, data, `Blog article scheduled.
2195
+ Time: ${data.scheduledAt}
2196
+ Timezone: ${data.timezone}`);
2197
+ } });
2198
+ });
2199
+ const blogScheduleCancel = blogSchedule.command("cancel").description("Cancel one future Blog schedule with blog:publish; never unpublishes a live article");
2200
+ blogScheduleCancel.command("preview").description("Preview exact schedule cancellation with blog:publish; does not unpublish").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").action(async (articleId, local) => {
2201
+ const global = program.opts();
2202
+ commandExitCode = await execute({ command: "blog schedule cancel preview", global, runtime, async action(context, client) {
2203
+ const data = await client.blog.articles.previewScheduleCancellation(local.project, articleId, { requestId: context.requestId });
2204
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2205
+ } });
2206
+ });
2207
+ blogScheduleCancel.command("execute").description("Cancel the confirmed future schedule with blog:publish; never unpublishes a live article").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--confirmation-token <token>", "short-lived token returned by preview").requiredOption("--idempotency-key <key>", "stable retry key").option("--yes", "confirm schedule cancellation").action(async (articleId, local) => {
2208
+ const global = program.opts();
2209
+ commandExitCode = await execute({ command: "blog schedule cancel execute", global, runtime, async action(context, client) {
2210
+ requireBlogExecutionConsent({ yes: local.yes, global, runtime });
2211
+ const data = await client.blog.articles.executeScheduleCancellation(local.project, articleId, { confirmationToken: local.confirmationToken, idempotencyKey: local.idempotencyKey }, { requestId: context.requestId });
2212
+ writeSuccess(context, data, `Future Blog schedule canceled.
2213
+ Article: ${data.articleId}
2214
+ A live article was not unpublished.`);
2215
+ } });
2216
+ });
2116
2217
  blog.command("job").argument("<job-id>").requiredOption("--project <project-id>", "exact Rolino project ID").action(async (jobId, local) => {
2117
2218
  const global = program.opts();
2118
2219
  commandExitCode = await execute({ command: "blog job", global, runtime, async action(context, client) {