outrank-cli 0.3.0 → 0.4.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/README.md CHANGED
@@ -50,6 +50,11 @@ This repo currently targets the agent endpoints that already exist in `outrank`:
50
50
  - `products backlinks get|update|credits|earned|unverified|check-removal|reverify|subscription|subscribe-quote`
51
51
  - `keywords list|get|export|generation-status|add|update|suggest|generate|bulk-reschedule|delete|bulk-delete`
52
52
  - `articles list|get|get-content|update|update-content|replace|bulk-replace|upload-image|upload-featured-image|retry-publish|delete|precreate|generate`
53
+ - `improvements settings get|update`
54
+ - `improvements analyze`
55
+ - `improvements candidates list|brief|edit-brief|schedule`
56
+ - `improvements tasks list|reschedule|cancel|edit-brief|execute`
57
+ - `improvements executions list|get|decisions|push`
53
58
  - `subscription status`
54
59
  - `usage stats`
55
60
  - `billing portal-url`
@@ -110,6 +115,11 @@ outrank-cli articles replace 00000000-0000-0000-0000-000000000000 --json '{"find
110
115
  outrank-cli articles retry-publish 00000000-0000-0000-0000-000000000000
111
116
  outrank-cli articles precreate 00000000-0000-0000-0000-000000000000
112
117
  outrank-cli articles generate 00000000-0000-0000-0000-000000000000
118
+ outrank-cli improvements settings get 00000000-0000-0000-0000-000000000000
119
+ outrank-cli improvements candidates list 00000000-0000-0000-0000-000000000000 --type ctr_gap --limit 20
120
+ outrank-cli improvements candidates schedule 00000000-0000-0000-0000-000000000000 --scheduled-for 2026-06-01
121
+ outrank-cli improvements tasks execute 00000000-0000-0000-0000-000000000000
122
+ outrank-cli improvements executions push 00000000-0000-0000-0000-000000000000
113
123
  outrank-cli subscription status
114
124
  outrank-cli usage stats
115
125
  outrank-cli products get 00000000-0000-0000-0000-000000000000
@@ -283,10 +293,10 @@ Example payload:
283
293
  - If the main subscription is `past_due`, keyword mutations are blocked and reads still work.
284
294
  - `keywords list` defaults to planner-first ordering for scheduled results, so page 1 prioritizes upcoming scheduled items before past ones.
285
295
  - Use `keywords list --product <product-id> --scope scheduled --q "<keyword>"` when looking for one planner keyword by text. If `next_offset` is not `null`, keep paging before claiming the keyword is absent.
286
- - Keyword records return top-level `article_type`, `article_subtype`, `expected_length`, and label fields. Read those from the keyword itself when answering article-type or expected-length questions; do not look in product output-settings for per-keyword length.
287
- - Use `keywords update <keyword-id> --json '{"instructions":"..."}'` to add or change per-keyword instructions after creation. This matches the dashboard flow, which creates the keyword first and updates the brief afterward.
288
- - Use `keywords update <keyword-id> --json '{"quality_questions":[{"id":"...","question":"...","answer":"..."}]}'` to answer or replace the keyword's quality questions through the same PATCH route.
289
- - Use `keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD","instructions":"..."}'` when you need to change the date and brief in one PATCH request.
296
+ - Keyword records return top-level `article_type`, `article_subtype`, `expected_length`, and label fields. Read those from the keyword itself when answering article-type or expected-length questions; do not look in product output-settings for per-keyword length.
297
+ - Use `keywords update <keyword-id> --json '{"instructions":"..."}'` to add or change per-keyword instructions after creation. This matches the dashboard flow, which creates the keyword first and updates the brief afterward.
298
+ - Use `keywords update <keyword-id> --json '{"quality_questions":[{"id":"...","question":"...","answer":"..."}]}'` to answer or replace the keyword's quality questions through the same PATCH route.
299
+ - Use `keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD","instructions":"..."}'` when you need to change the date and brief in one PATCH request.
290
300
  - Use `keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD"}'` for date-only rescheduling too; there is no separate reschedule command.
291
301
  - `keywords get <keyword-id>` returns `article_type_options`, which lists the valid dropdown values for `article_type`, `article_subtype`, and `expected_length`.
292
302
  - Use `keywords update --json '{"article_type":"guide","article_subtype":"explainer","expected_length":"long"}'` to mirror the scheduler card's "Article Type Settings" modal. The API rejects values outside the returned option lists.
@@ -307,6 +317,19 @@ Example payload:
307
317
  - `articles update --json '{"status":"published"}'` cannot be used to manually publish a future scheduled article before its due date.
308
318
  - `articles retry-publish <article-id>` is only valid for generated articles with a publish error.
309
319
 
320
+ ## Article improvements behavior notes
321
+
322
+ - Article improvements are a separate surface from generation: they rewrite an already-published post based on Search Console signals, then push the result to the CMS. They require the All-in-One plan. On Basic plan, `improvements candidates list` returns an empty list and write actions return `PLAN_UPGRADE_REQUIRED`.
323
+ - `improvements settings update --json '{"enabled":true}'` starts an unattended loop: weekly opportunity discovery, daily auto-scheduling on `publishing_days` (at most 2 weekday codes from Mon–Sun), and execution every ~15 minutes. Surface this to the human before enabling.
324
+ - `improvements settings update --json '{"auto_push":true}'` publishes every successful rewrite to the CMS without human review (still skipped automatically on a hard validation failure or an unverified backlink injection).
325
+ - `improvements settings get` returns `readiness`, which says whether the connected CMS can actually receive a pushed update (`ready`) or not (`not_connected`, `action_required`, `unsupported`). A push to an unsupported/disconnected CMS is reported as `skipped` with `requires_manual_publish`.
326
+ - `improvements analyze`, `improvements candidates brief`, and `improvements tasks execute` run in Firebase and can take minutes. If the call times out at the network edge, the work still completes server-side — re-list candidates or poll the execution rather than retrying.
327
+ - After `improvements candidates brief`, poll the candidate (via `improvements candidates list`) until its `brief_status` is `ready`. After `improvements tasks execute`, poll `improvements executions get <execution-id>` until `status` is `completed`, then read `review_status`.
328
+ - A candidate (or its underlying article) can have only one active scheduled task at a time, and an article can have only one active (pending/running) execution. Conflicts return `409`.
329
+ - `improvements executions get` returns `before`/`after`, paragraph-level `hunks` (each with an id), and `validation_issues`. Push with no JSON to accept all changes; send `{"decisions":{"<hunkId>":"rejected"},"edited_hunks":{"<hunkId>":"..."}}` to reject or edit specific hunks. `decisions` and `push` only work while `review_status` is `awaiting_review`.
330
+ - A push updates the article inside Outrank first, then dispatches to the CMS. `CMS_PUSH_FAILED` means the content was saved in Outrank but the CMS rejected it; inspect `error.details.cms_response` and retry.
331
+ - `improvements candidates list` and `improvements executions list` use offset pagination through `data.next_offset`.
332
+
310
333
  ## Linking behavior notes
311
334
 
312
335
  - Linking writes are blocked unless the product has an active backlink subscription.
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: outrank
3
- description: Drive the Outrank agent API from the terminal via outrank-cli. Use when the user asks to list/create/update Outrank products, inspect or manage publishing integrations, inspect or manage keyword plans, inspect or edit articles, manage article settings, target audiences, competitors, Google Search Console connections, internal linking configuration, or quote/buy product slots. Requires outrank-cli on PATH and an Outrank API key (env OUTRANK_API_KEY or stored via auth login).
3
+ description: Drive the Outrank agent API from the terminal via outrank-cli. Use when the user asks to list/create/update Outrank products, inspect or manage publishing integrations, inspect or manage keyword plans, inspect or edit articles, discover/schedule/run/review/publish article improvements for existing posts, manage article settings, target audiences, competitors, Google Search Console connections, internal linking configuration, or quote/buy product slots. Requires outrank-cli on PATH and an Outrank API key (env OUTRANK_API_KEY or stored via auth login).
4
4
  ---
5
5
 
6
6
  # Outrank Skill
@@ -12,6 +12,8 @@ In human-facing responses, call `products get` / `products update` the product's
12
12
  In human-facing responses, call product `output-settings` article settings or article preferences. Use `output-settings` only when you are quoting the literal CLI command or API path.
13
13
  Use `products backlinks ...` for backlink exchange settings, credits, review queues, and reverification actions.
14
14
 
15
+ Use `improvements ...` for article improvements: discovering, briefing, scheduling, running, reviewing, and publishing AI rewrites of **existing published articles** based on Search Console performance. This is a distinct area from article generation (`articles generate`/`precreate`) — offer it whenever the human wants to refresh, update, optimize, or improve existing/older posts, or act on GSC opportunities. It needs the All-in-One plan.
16
+
15
17
  ## Setup
16
18
 
17
19
  1. If `OUTRANK_API_KEY` is not set, ask the human for an Outrank API key from `https://outrank.so/dashboard/settings/api-keys`.
@@ -31,6 +33,7 @@ Run group help when needed:
31
33
  - `outrank-cli products --help`
32
34
  - `outrank-cli keywords --help`
33
35
  - `outrank-cli articles --help`
36
+ - `outrank-cli improvements --help`
34
37
  - `outrank-cli subscription --help`
35
38
  - `outrank-cli usage --help`
36
39
  - `outrank-cli billing --help`
@@ -47,6 +50,10 @@ Prefer inspecting before mutating:
47
50
  - `outrank-cli articles get-content <articleId>`
48
51
  - `outrank-cli articles upload-image --file <path> --article <articleId>`
49
52
  - `outrank-cli articles upload-featured-image <articleId> --file <path>`
53
+ - `outrank-cli improvements settings get <productId>`
54
+ - `outrank-cli improvements candidates list <productId>`
55
+ - `outrank-cli improvements tasks list <productId>`
56
+ - `outrank-cli improvements executions list <productId>`
50
57
  - `outrank-cli keywords export --product <productId>`
51
58
  - `outrank-cli products target-audiences list <productId>`
52
59
  - `outrank-cli products competitors list <productId>`
@@ -482,6 +489,36 @@ Important behavior to remember:
482
489
  - `articles retry-publish <articleId>` only works for generated articles with a publish error
483
490
  - `articles bulk-replace` is preview-first; review the returned excerpts, then rerun with `"confirm": true` in the JSON payload to write changes
484
491
 
492
+ ## Article improvements workflow
493
+
494
+ Article improvements take an already-published post, analyze its Search Console performance, draft a rewrite brief, run a multi-step rewrite, and push the improved version back to the CMS. This is a separate surface from article generation. It requires the All-in-One plan; on Basic plan candidate lists come back empty and write actions return `PLAN_UPGRADE_REQUIRED`.
495
+
496
+ Inspect before mutating:
497
+
498
+ - `outrank-cli improvements settings get <productId>` — shows `enabled`, `auto_push`, `publishing_days`, the CMS `readiness` (whether a push can actually land), and `activity` counts
499
+ - `outrank-cli improvements candidates list <productId> [--type <opportunity_type>]` — opportunities available to schedule
500
+ - `outrank-cli improvements tasks list <productId>` — scheduled tasks, each with its `latest_execution` for progress polling
501
+ - `outrank-cli improvements executions list <productId> --review-status awaiting_review` — the review inbox
502
+
503
+ Typical manual flow:
504
+
505
+ 1. `outrank-cli improvements analyze <productId>` — discover/refresh opportunities (heavy, multi-minute; re-list candidates after)
506
+ 2. `outrank-cli improvements candidates brief <candidateId>` — generate the rewrite brief (heavy; poll the candidate until `brief_status` is `ready`)
507
+ 3. optionally `outrank-cli improvements candidates edit-brief <candidateId> --json-file brief.json` to refine it
508
+ 4. `outrank-cli improvements candidates schedule <candidateId> --scheduled-for <iso-date>` — create the task
509
+ 5. `outrank-cli improvements tasks execute <taskId>` — run the rewrite now (heavy; poll `improvements executions get <executionId>` until `status` is `completed`)
510
+ 6. review and publish: `outrank-cli improvements executions get <executionId>`, then `outrank-cli improvements executions push <executionId>`
511
+
512
+ Important behavior to remember:
513
+
514
+ - turning settings `enabled` on starts an unattended loop — weekly opportunity discovery, daily auto-scheduling on `publishing_days` (max 2 weekdays), and execution every ~15 minutes. Always tell the human this before enabling.
515
+ - with `auto_push` on, each successful rewrite is published to the CMS with no human review (still skipped automatically on a hard validation failure or an unverified backlink). Only enable it via `settings update` after the human confirms.
516
+ - analyze, candidates brief, and tasks execute run in Firebase and can take minutes; if a call times out at the edge the work continues server-side, so re-list or poll the execution instead of retrying
517
+ - `executions get` returns `before`/`after`, paragraph-level `hunks` (each with an id), and `validation_issues`; to accept everything, `executions push` with no JSON; to reject or edit specific hunks, send `{"decisions":{"<hunkId>":"rejected"},"edited_hunks":{"<hunkId>":"..."}}` to `executions decisions` or directly in the push body
518
+ - decisions and push only work while `review_status` is `awaiting_review`
519
+ - a push always updates the article inside Outrank first; if `cms_response.status` is `skipped` the CMS is not connected/supported for auto-push (publish manually); a `CMS_PUSH_FAILED` error means the CMS rejected it — inspect `error.details.cms_response` and retry
520
+ - `tasks execute --improve-images false` skips image regeneration (default regenerates images)
521
+
485
522
  ## Buying more product slots
486
523
 
487
524
  Always do this in two steps:
@@ -502,4 +539,4 @@ Do not ask for billing approval until you have already clarified the intended pr
502
539
 
503
540
  ## Current limitation
504
541
 
505
- The implementation plan is broader than the current live API. Keywords, core article flows, linking, backlink exchange management, and the current integration-management flows are exposed in `outrank-cli`, including direct setup for webhook, Ghost, Shopify, Webflow, Wix, Framer, WordPress Plugin, and Next.js Blog, plus OAuth flows for WordPress.com and Notion. Purchasing or changing backlink add-ons and some onboarding flows still require the dashboard. If the user asks for something not present in `outrank-cli --help`, say that exact command surface is not shipped yet and do not invent unsupported commands.
542
+ The implementation plan is broader than the current live API. Keywords, core article flows, article improvements, linking, backlink exchange management, and the current integration-management flows are exposed in `outrank-cli`, including direct setup for webhook, Ghost, Shopify, Webflow, Wix, Framer, WordPress Plugin, and Next.js Blog, plus OAuth flows for WordPress.com and Notion. Purchasing or changing backlink add-ons and some onboarding flows still require the dashboard. If the user asks for something not present in `outrank-cli --help`, say that exact command surface is not shipped yet and do not invent unsupported commands.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outrank-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-first CLI for the Outrank agent API",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,277 @@
1
+ import { parseArgs, getRequiredPositional, readJsonInput, hasFlag, parseIntegerOption, getOption } from '../core/args.js';
2
+ import { requestAgentApi } from '../core/api.js';
3
+ import { cliSuccess } from '../core/errors.js';
4
+
5
+ export const IMPROVEMENTS_HELP = `Usage:
6
+ outrank-cli improvements settings get <product-id>
7
+ outrank-cli improvements settings update <product-id> --json <object>
8
+ outrank-cli improvements analyze <product-id>
9
+ outrank-cli improvements candidates list <product-id> [--type outsider|ctr_gap|declining|content_gap|thin_content] [--limit <n>] [--offset <n>]
10
+ outrank-cli improvements candidates brief <candidate-id> [--task <task-id>]
11
+ outrank-cli improvements candidates edit-brief <candidate-id> --json <object>
12
+ outrank-cli improvements candidates schedule <candidate-id> --scheduled-for <iso-date>
13
+ outrank-cli improvements tasks list <product-id>
14
+ outrank-cli improvements tasks reschedule <task-id> --scheduled-for <iso-date>
15
+ outrank-cli improvements tasks cancel <task-id>
16
+ outrank-cli improvements tasks edit-brief <task-id> --json <object>
17
+ outrank-cli improvements tasks execute <task-id> [--improve-images true|false]
18
+ outrank-cli improvements executions list <product-id> [--review-status awaiting_review|pushed|auto_pushed|rejected|all] [--limit <n>] [--offset <n>]
19
+ outrank-cli improvements executions get <execution-id>
20
+ outrank-cli improvements executions decisions <execution-id> --json <object>
21
+ outrank-cli improvements executions push <execution-id> [--json <object>]
22
+
23
+ JSON Input:
24
+ Use one of: --json '<object>', --json-file payload.json, or --json-stdin
25
+
26
+ Notes:
27
+ Article improvements need the All-in-One plan. Turning settings on starts an unattended loop
28
+ (weekly opportunity discovery, daily auto-scheduling on the configured publishing_days, and
29
+ execution every ~15 minutes). With auto_push on, each successful rewrite is published to the CMS
30
+ without review. analyze, candidates brief, and tasks execute are heavy multi-minute operations:
31
+ if a call times out at the edge, the work continues server-side — re-list/poll instead of retrying.
32
+
33
+ Examples:
34
+ outrank-cli improvements settings get 00000000-0000-0000-0000-000000000000
35
+ outrank-cli improvements settings update 00000000-0000-0000-0000-000000000000 --json '{"enabled":true,"auto_push":false,"publishing_days":["Mon","Thu"]}'
36
+ outrank-cli improvements analyze 00000000-0000-0000-0000-000000000000
37
+ outrank-cli improvements candidates list 00000000-0000-0000-0000-000000000000 --type ctr_gap --limit 20
38
+ outrank-cli improvements candidates brief 00000000-0000-0000-0000-000000000000
39
+ outrank-cli improvements candidates edit-brief 00000000-0000-0000-0000-000000000000 --json-file brief.json
40
+ outrank-cli improvements candidates schedule 00000000-0000-0000-0000-000000000000 --scheduled-for 2026-06-01
41
+ outrank-cli improvements tasks list 00000000-0000-0000-0000-000000000000
42
+ outrank-cli improvements tasks execute 00000000-0000-0000-0000-000000000000 --improve-images false
43
+ outrank-cli improvements executions list 00000000-0000-0000-0000-000000000000 --review-status awaiting_review
44
+ outrank-cli improvements executions get 00000000-0000-0000-0000-000000000000
45
+ outrank-cli improvements executions push 00000000-0000-0000-0000-000000000000
46
+ outrank-cli improvements executions push 00000000-0000-0000-0000-000000000000 --json '{"decisions":{"para-3":"rejected"}}'
47
+ `;
48
+
49
+ const invalidUsage = (message, guidance) =>
50
+ Object.assign(new Error(message), {
51
+ cliCode: 'CLI_INVALID_USAGE',
52
+ guidance: guidance || 'Run `outrank-cli improvements --help` to see available improvement commands.',
53
+ });
54
+
55
+ const buildQueryPath = (basePath, searchParams) => {
56
+ const url = new URL(`https://local${basePath}`);
57
+ for (const [key, value] of Object.entries(searchParams)) {
58
+ if (value !== undefined && value !== null && value !== '') {
59
+ url.searchParams.set(key, String(value));
60
+ }
61
+ }
62
+ return `${url.pathname}${url.search}`;
63
+ };
64
+
65
+ const hasJsonSource = (parsed) =>
66
+ Boolean(getOption(parsed, 'json') || getOption(parsed, 'json-file') || hasFlag(parsed, 'json-stdin'));
67
+
68
+ const parseImproveImages = (parsed) => {
69
+ const raw = getOption(parsed, 'improve-images');
70
+ if (raw === undefined) return undefined;
71
+ if (raw === 'true') return true;
72
+ if (raw === 'false') return false;
73
+ throw invalidUsage('--improve-images must be true or false.', 'Pass `--improve-images true` or `--improve-images false`, or omit it to use the default (images are regenerated).');
74
+ };
75
+
76
+ const requireScheduledFor = (parsed) => {
77
+ const value = getOption(parsed, 'scheduled-for');
78
+ if (!value) {
79
+ throw invalidUsage('Missing required option: --scheduled-for <iso-date>.', 'Pass a date or ISO timestamp, e.g. `--scheduled-for 2026-06-01`.');
80
+ }
81
+ return value;
82
+ };
83
+
84
+ const handleAnalyze = async (parsed) => {
85
+ const productId = getRequiredPositional(parsed, 0, '<product-id>');
86
+ const response = await requestAgentApi(parsed, {
87
+ method: 'POST',
88
+ path: `/products/${productId}/improvements/analyze`,
89
+ });
90
+ return response.payload;
91
+ };
92
+
93
+ const handleSettings = async (action, parsed) => {
94
+ const productId = getRequiredPositional(parsed, 0, '<product-id>');
95
+ if (action === 'get') {
96
+ const response = await requestAgentApi(parsed, {
97
+ method: 'GET',
98
+ path: `/products/${productId}/improvements/settings`,
99
+ });
100
+ return response.payload;
101
+ }
102
+ if (action === 'update') {
103
+ const body = await readJsonInput(parsed);
104
+ const response = await requestAgentApi(parsed, {
105
+ method: 'PATCH',
106
+ path: `/products/${productId}/improvements/settings`,
107
+ body,
108
+ });
109
+ return response.payload;
110
+ }
111
+ throw invalidUsage(`Unknown settings action: ${action ?? '(none)'}.`, 'Use: improvements settings get|update <product-id>.');
112
+ };
113
+
114
+ const handleCandidates = async (action, parsed) => {
115
+ if (action === 'list') {
116
+ const productId = getRequiredPositional(parsed, 0, '<product-id>');
117
+ const path = buildQueryPath(`/products/${productId}/improvements/candidates`, {
118
+ type: getOption(parsed, 'type'),
119
+ limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 50 }),
120
+ offset: parseIntegerOption(parsed, 'offset', { min: 0 }),
121
+ });
122
+ const response = await requestAgentApi(parsed, { method: 'GET', path });
123
+ return response.payload;
124
+ }
125
+ if (action === 'brief') {
126
+ const candidateId = getRequiredPositional(parsed, 0, '<candidate-id>');
127
+ const taskId = getOption(parsed, 'task');
128
+ const response = await requestAgentApi(parsed, {
129
+ method: 'POST',
130
+ path: `/improvements/candidates/${candidateId}/brief`,
131
+ body: taskId ? { task_id: taskId } : {},
132
+ });
133
+ return response.payload;
134
+ }
135
+ if (action === 'edit-brief') {
136
+ const candidateId = getRequiredPositional(parsed, 0, '<candidate-id>');
137
+ const body = await readJsonInput(parsed);
138
+ const response = await requestAgentApi(parsed, {
139
+ method: 'PATCH',
140
+ path: `/improvements/candidates/${candidateId}/brief`,
141
+ body,
142
+ });
143
+ return response.payload;
144
+ }
145
+ if (action === 'schedule') {
146
+ const candidateId = getRequiredPositional(parsed, 0, '<candidate-id>');
147
+ const scheduledFor = requireScheduledFor(parsed);
148
+ const response = await requestAgentApi(parsed, {
149
+ method: 'POST',
150
+ path: `/improvements/candidates/${candidateId}/schedule`,
151
+ body: { scheduled_for: scheduledFor },
152
+ });
153
+ return response.payload;
154
+ }
155
+ throw invalidUsage(`Unknown candidates action: ${action ?? '(none)'}.`, 'Use: improvements candidates list|brief|edit-brief|schedule.');
156
+ };
157
+
158
+ const handleTasks = async (action, parsed) => {
159
+ if (action === 'list') {
160
+ const productId = getRequiredPositional(parsed, 0, '<product-id>');
161
+ const response = await requestAgentApi(parsed, {
162
+ method: 'GET',
163
+ path: `/products/${productId}/improvements/tasks`,
164
+ });
165
+ return response.payload;
166
+ }
167
+ if (action === 'reschedule') {
168
+ const taskId = getRequiredPositional(parsed, 0, '<task-id>');
169
+ const scheduledFor = requireScheduledFor(parsed);
170
+ const response = await requestAgentApi(parsed, {
171
+ method: 'PATCH',
172
+ path: `/improvements/tasks/${taskId}`,
173
+ body: { scheduled_for: scheduledFor },
174
+ });
175
+ return response.payload;
176
+ }
177
+ if (action === 'cancel') {
178
+ const taskId = getRequiredPositional(parsed, 0, '<task-id>');
179
+ const response = await requestAgentApi(parsed, {
180
+ method: 'DELETE',
181
+ path: `/improvements/tasks/${taskId}`,
182
+ });
183
+ return response.payload;
184
+ }
185
+ if (action === 'edit-brief') {
186
+ const taskId = getRequiredPositional(parsed, 0, '<task-id>');
187
+ const body = await readJsonInput(parsed);
188
+ const response = await requestAgentApi(parsed, {
189
+ method: 'PATCH',
190
+ path: `/improvements/tasks/${taskId}/brief`,
191
+ body,
192
+ });
193
+ return response.payload;
194
+ }
195
+ if (action === 'execute') {
196
+ const taskId = getRequiredPositional(parsed, 0, '<task-id>');
197
+ const improveImages = parseImproveImages(parsed);
198
+ const response = await requestAgentApi(parsed, {
199
+ method: 'POST',
200
+ path: `/improvements/tasks/${taskId}/execute`,
201
+ body: improveImages === undefined ? {} : { improve_images: improveImages },
202
+ });
203
+ return response.payload;
204
+ }
205
+ throw invalidUsage(`Unknown tasks action: ${action ?? '(none)'}.`, 'Use: improvements tasks list|reschedule|cancel|edit-brief|execute.');
206
+ };
207
+
208
+ const handleExecutions = async (action, parsed) => {
209
+ if (action === 'list') {
210
+ const productId = getRequiredPositional(parsed, 0, '<product-id>');
211
+ const path = buildQueryPath(`/products/${productId}/improvements/executions`, {
212
+ review_status: getOption(parsed, 'review-status'),
213
+ limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 100 }),
214
+ offset: parseIntegerOption(parsed, 'offset', { min: 0 }),
215
+ });
216
+ const response = await requestAgentApi(parsed, { method: 'GET', path });
217
+ return response.payload;
218
+ }
219
+ if (action === 'get') {
220
+ const executionId = getRequiredPositional(parsed, 0, '<execution-id>');
221
+ const response = await requestAgentApi(parsed, {
222
+ method: 'GET',
223
+ path: `/improvements/executions/${executionId}`,
224
+ });
225
+ return response.payload;
226
+ }
227
+ if (action === 'decisions') {
228
+ const executionId = getRequiredPositional(parsed, 0, '<execution-id>');
229
+ const body = await readJsonInput(parsed);
230
+ const response = await requestAgentApi(parsed, {
231
+ method: 'PATCH',
232
+ path: `/improvements/executions/${executionId}/decisions`,
233
+ body,
234
+ });
235
+ return response.payload;
236
+ }
237
+ if (action === 'push') {
238
+ const executionId = getRequiredPositional(parsed, 0, '<execution-id>');
239
+ const body = hasJsonSource(parsed) ? await readJsonInput(parsed) : {};
240
+ const response = await requestAgentApi(parsed, {
241
+ method: 'POST',
242
+ path: `/improvements/executions/${executionId}/push`,
243
+ body,
244
+ });
245
+ return response.payload;
246
+ }
247
+ throw invalidUsage(`Unknown executions action: ${action ?? '(none)'}.`, 'Use: improvements executions list|get|decisions|push.');
248
+ };
249
+
250
+ export const handleImprovementsCommand = async (argv) => {
251
+ const subgroup = argv[0];
252
+ const wantsHelp =
253
+ !subgroup || subgroup === '--help' || subgroup === '-h' || subgroup === 'help';
254
+
255
+ if (wantsHelp) {
256
+ return cliSuccess(
257
+ {
258
+ usage: IMPROVEMENTS_HELP.trim(),
259
+ },
260
+ 'Choose a sub-noun: settings, analyze, candidates, tasks, or executions. The typical flow is analyze -> candidates brief -> candidates schedule -> tasks execute -> executions push.',
261
+ );
262
+ }
263
+
264
+ if (subgroup === 'analyze') {
265
+ return handleAnalyze(parseArgs(argv.slice(1)));
266
+ }
267
+
268
+ const action = argv[1];
269
+ const parsed = parseArgs(argv.slice(2));
270
+
271
+ if (subgroup === 'settings') return handleSettings(action, parsed);
272
+ if (subgroup === 'candidates') return handleCandidates(action, parsed);
273
+ if (subgroup === 'tasks') return handleTasks(action, parsed);
274
+ if (subgroup === 'executions') return handleExecutions(action, parsed);
275
+
276
+ throw invalidUsage(`Unknown improvements sub-noun: ${subgroup}.`);
277
+ };
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ import { handleSubscriptionCommand } from './commands/subscription.js';
12
12
  import { handleUsageCommand } from './commands/usage.js';
13
13
  import { handleKeywordsCommand } from './commands/keywords.js';
14
14
  import { handleArticlesCommand } from './commands/articles.js';
15
+ import { handleImprovementsCommand } from './commands/improvements.js';
15
16
 
16
17
  const getVersion = () => {
17
18
  try {
@@ -35,6 +36,7 @@ Groups:
35
36
  products List, create, inspect, and configure products, integrations, article settings, Google Search Console, linking, and backlinks.
36
37
  keywords Inspect, check write status, add, reschedule, delete, suggest, generate, and export keyword plans.
37
38
  articles Inspect article metadata and bodies, edit content, retry publish, precreate, and generate.
39
+ improvements Discover, brief, schedule, run, review, and publish article improvements for existing posts.
38
40
  subscription Inspect plan, trial, quota, and add-on status.
39
41
  usage Inspect current usage and quota consumption.
40
42
  billing Product-slot purchase flows.
@@ -55,6 +57,8 @@ Examples:
55
57
  outrank-cli products list --limit 20
56
58
  outrank-cli keywords list --product 00000000-0000-0000-0000-000000000000 --scope all
57
59
  outrank-cli articles list --product 00000000-0000-0000-0000-000000000000 --status published
60
+ outrank-cli improvements candidates list 00000000-0000-0000-0000-000000000000 --type ctr_gap
61
+ outrank-cli improvements tasks execute 00000000-0000-0000-0000-000000000000
58
62
  outrank-cli subscription status
59
63
  outrank-cli usage stats
60
64
  outrank-cli products create --json-file product.json
@@ -111,6 +115,8 @@ const main = async () => {
111
115
  result = await handleKeywordsCommand(rest);
112
116
  } else if (group === 'articles') {
113
117
  result = await handleArticlesCommand(rest);
118
+ } else if (group === 'improvements') {
119
+ result = await handleImprovementsCommand(rest);
114
120
  } else if (group === 'subscription') {
115
121
  result = await handleSubscriptionCommand(rest);
116
122
  } else if (group === 'usage') {