outrank-cli 0.3.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/README.md CHANGED
@@ -50,6 +50,14 @@ 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`
58
+ - `marketplace seller status|onboard|eligibility|settings|integrations|stripe|complete-onboarding|placements|earnings|content-history`
59
+ - `marketplace notifications list|mark-read`
60
+ - `marketplace buyer status|billing-status|subscription|subscribe-quote|checkout|change-plan|target-pages|participation|placements|article|credits`
53
61
  - `subscription status`
54
62
  - `usage stats`
55
63
  - `billing portal-url`
@@ -110,6 +118,11 @@ outrank-cli articles replace 00000000-0000-0000-0000-000000000000 --json '{"find
110
118
  outrank-cli articles retry-publish 00000000-0000-0000-0000-000000000000
111
119
  outrank-cli articles precreate 00000000-0000-0000-0000-000000000000
112
120
  outrank-cli articles generate 00000000-0000-0000-0000-000000000000
121
+ outrank-cli improvements settings get 00000000-0000-0000-0000-000000000000
122
+ outrank-cli improvements candidates list 00000000-0000-0000-0000-000000000000 --type ctr_gap --limit 20
123
+ outrank-cli improvements candidates schedule 00000000-0000-0000-0000-000000000000 --scheduled-for 2026-06-01
124
+ outrank-cli improvements tasks execute 00000000-0000-0000-0000-000000000000
125
+ outrank-cli improvements executions push 00000000-0000-0000-0000-000000000000
113
126
  outrank-cli subscription status
114
127
  outrank-cli usage stats
115
128
  outrank-cli products get 00000000-0000-0000-0000-000000000000
@@ -192,6 +205,11 @@ outrank-cli products backlinks subscription 00000000-0000-0000-0000-000000000000
192
205
  outrank-cli products backlinks subscribe-quote 00000000-0000-0000-0000-000000000000 --plan premium
193
206
  outrank-cli keywords update 00000000-0000-0000-0000-000000000000 --json '{"scheduled_for":"2026-05-20"}'
194
207
  outrank-cli billing purchase-products quote --additional-products 2
208
+ outrank-cli marketplace seller status 00000000-0000-0000-0000-000000000000
209
+ outrank-cli marketplace seller onboard 00000000-0000-0000-0000-000000000000 --country US
210
+ outrank-cli marketplace seller placements list 00000000-0000-0000-0000-000000000000 --status needs_approval
211
+ outrank-cli marketplace seller earnings 00000000-0000-0000-0000-000000000000
212
+ outrank-cli marketplace buyer status 00000000-0000-0000-0000-000000000000
195
213
  ```
196
214
 
197
215
  ## Google Search Console workflow
@@ -271,6 +289,23 @@ Example payload:
271
289
  }
272
290
  ```
273
291
 
292
+ ## Marketplace workflow
293
+
294
+ Seller side (live):
295
+
296
+ - Start with `outrank-cli marketplace seller status <product-id>` — its guidance walks the onboarding wizard (Blog -> Articles -> Integration -> Payouts).
297
+ - `outrank-cli marketplace seller onboard <product-id> [--country <ISO2>]` orchestrates onboarding: it reports remaining steps, adopts an existing reusable CMS automatically (never ask the user to pick a platform when one is reusable), and creates the Stripe Express account once the earlier steps are done and a country is provided.
298
+ - Stripe identity/bank onboarding happens in a browser — surface the returned `onboardingUrl` to the human.
299
+ - Finish with `outrank-cli marketplace seller complete-onboarding <product-id>` and always report the eligibility check results (`marketplace seller eligibility`) to the human.
300
+ - Day-to-day: `placements list|approve|decline|publish|regenerate`, `earnings`, `settings get|update`, `stripe manage`, `content-history`, and `marketplace notifications list --role seller`.
301
+ - Admin-only (server-enforced against the key creator's current role): settings update, integrations writes, stripe commands, complete-onboarding, placements decline.
302
+
303
+ Buyer side:
304
+
305
+ - Start with `outrank-cli marketplace buyer status <product-id>` — its guidance drives the setup sequence.
306
+ - The mandatory sequence: Enterprise backlink plan (`buyer subscribe-quote`, a human org admin completes checkout — purchasing is admin-only) -> add target pages (`buyer target-pages discover|add`) -> `buyer participation on`.
307
+ - Then `buyer placements list|regenerate|revalidate`, `buyer article get|edit`, `buyer credits history`, and `marketplace notifications list --role buyer`.
308
+
274
309
  ## Keyword mutation rules
275
310
 
276
311
  - Run `outrank-cli keywords generation-status --product <product-id>` before keyword writes when you are not sure the product is writable.
@@ -283,10 +318,10 @@ Example payload:
283
318
  - If the main subscription is `past_due`, keyword mutations are blocked and reads still work.
284
319
  - `keywords list` defaults to planner-first ordering for scheduled results, so page 1 prioritizes upcoming scheduled items before past ones.
285
320
  - 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.
321
+ - 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.
322
+ - 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.
323
+ - 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.
324
+ - 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
325
  - Use `keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD"}'` for date-only rescheduling too; there is no separate reschedule command.
291
326
  - `keywords get <keyword-id>` returns `article_type_options`, which lists the valid dropdown values for `article_type`, `article_subtype`, and `expected_length`.
292
327
  - 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 +342,19 @@ Example payload:
307
342
  - `articles update --json '{"status":"published"}'` cannot be used to manually publish a future scheduled article before its due date.
308
343
  - `articles retry-publish <article-id>` is only valid for generated articles with a publish error.
309
344
 
345
+ ## Article improvements behavior notes
346
+
347
+ - 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`.
348
+ - `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.
349
+ - `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).
350
+ - `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`.
351
+ - `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.
352
+ - 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`.
353
+ - 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`.
354
+ - `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`.
355
+ - 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.
356
+ - `improvements candidates list` and `improvements executions list` use offset pagination through `data.next_offset`.
357
+
310
358
  ## Linking behavior notes
311
359
 
312
360
  - 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, quote/buy product slots, or work with the Outrank Marketplace (seller onboarding, eligibility, placements, earnings, Stripe payouts, buyer target pages and placements). 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,10 @@ 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
+
17
+ Use `marketplace ...` for the Outrank Marketplace: seller onboarding and eligibility, placement approval/decline/publishing, earnings and Stripe Express payouts, marketplace notifications, and buyer target pages, placements, and credits. This is distinct from `products backlinks ...`, which is the classic backlink exchange.
18
+
15
19
  ## Setup
16
20
 
17
21
  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 +35,8 @@ Run group help when needed:
31
35
  - `outrank-cli products --help`
32
36
  - `outrank-cli keywords --help`
33
37
  - `outrank-cli articles --help`
38
+ - `outrank-cli improvements --help`
39
+ - `outrank-cli marketplace --help`
34
40
  - `outrank-cli subscription --help`
35
41
  - `outrank-cli usage --help`
36
42
  - `outrank-cli billing --help`
@@ -47,6 +53,10 @@ Prefer inspecting before mutating:
47
53
  - `outrank-cli articles get-content <articleId>`
48
54
  - `outrank-cli articles upload-image --file <path> --article <articleId>`
49
55
  - `outrank-cli articles upload-featured-image <articleId> --file <path>`
56
+ - `outrank-cli improvements settings get <productId>`
57
+ - `outrank-cli improvements candidates list <productId>`
58
+ - `outrank-cli improvements tasks list <productId>`
59
+ - `outrank-cli improvements executions list <productId>`
50
60
  - `outrank-cli keywords export --product <productId>`
51
61
  - `outrank-cli products target-audiences list <productId>`
52
62
  - `outrank-cli products competitors list <productId>`
@@ -77,6 +87,13 @@ Prefer inspecting before mutating:
77
87
  - `outrank-cli products backlinks earned <productId>`
78
88
  - `outrank-cli products backlinks unverified <productId>`
79
89
  - `outrank-cli products backlinks subscription <productId>`
90
+ - `outrank-cli marketplace seller status <productId>`
91
+ - `outrank-cli marketplace seller eligibility <productId>`
92
+ - `outrank-cli marketplace seller placements list <productId>`
93
+ - `outrank-cli marketplace seller earnings <productId>`
94
+ - `outrank-cli marketplace buyer status <productId>`
95
+ - `outrank-cli marketplace buyer target-pages list <productId>`
96
+ - `outrank-cli marketplace notifications list <productId> --role seller`
80
97
  - `outrank-cli subscription status`
81
98
  - `outrank-cli usage stats`
82
99
 
@@ -395,6 +412,7 @@ When the user asks for keyword ideas:
395
412
 
396
413
  Treat `keywords suggest` as candidate-generation only. It does not change the content plan until you add keywords.
397
414
  Treat `keywords generate` as the dashboard Generate Keywords action. It creates scheduled keywords through the same backend pipeline.
415
+ Product-wide keyword research instructions live in product article settings as `global_keyword_prompt` (`products output-settings get|update`). They steer every keyword research run for the product and are separate from per-keyword `instructions`. When the human wants a standing rule for all future keyword research (topics, angles, audiences to focus on), update `global_keyword_prompt` instead of repeating it per keyword.
398
416
 
399
417
  Before `keywords generate`:
400
418
 
@@ -482,6 +500,36 @@ Important behavior to remember:
482
500
  - `articles retry-publish <articleId>` only works for generated articles with a publish error
483
501
  - `articles bulk-replace` is preview-first; review the returned excerpts, then rerun with `"confirm": true` in the JSON payload to write changes
484
502
 
503
+ ## Article improvements workflow
504
+
505
+ 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`.
506
+
507
+ Inspect before mutating:
508
+
509
+ - `outrank-cli improvements settings get <productId>` — shows `enabled`, `auto_push`, `publishing_days`, the CMS `readiness` (whether a push can actually land), and `activity` counts
510
+ - `outrank-cli improvements candidates list <productId> [--type <opportunity_type>]` — opportunities available to schedule
511
+ - `outrank-cli improvements tasks list <productId>` — scheduled tasks, each with its `latest_execution` for progress polling
512
+ - `outrank-cli improvements executions list <productId> --review-status awaiting_review` — the review inbox
513
+
514
+ Typical manual flow:
515
+
516
+ 1. `outrank-cli improvements analyze <productId>` — discover/refresh opportunities (heavy, multi-minute; re-list candidates after)
517
+ 2. `outrank-cli improvements candidates brief <candidateId>` — generate the rewrite brief (heavy; poll the candidate until `brief_status` is `ready`)
518
+ 3. optionally `outrank-cli improvements candidates edit-brief <candidateId> --json-file brief.json` to refine it
519
+ 4. `outrank-cli improvements candidates schedule <candidateId> --scheduled-for <iso-date>` — create the task
520
+ 5. `outrank-cli improvements tasks execute <taskId>` — run the rewrite now (heavy; poll `improvements executions get <executionId>` until `status` is `completed`)
521
+ 6. review and publish: `outrank-cli improvements executions get <executionId>`, then `outrank-cli improvements executions push <executionId>`
522
+
523
+ Important behavior to remember:
524
+
525
+ - 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.
526
+ - 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.
527
+ - 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
528
+ - `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
529
+ - decisions and push only work while `review_status` is `awaiting_review`
530
+ - 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
531
+ - `tasks execute --improve-images false` skips image regeneration (default regenerates images)
532
+
485
533
  ## Buying more product slots
486
534
 
487
535
  Always do this in two steps:
@@ -493,6 +541,52 @@ Always do this in two steps:
493
541
  Never guess `confirm-charge-cents`. Use the server-quoted number.
494
542
  Do not ask for billing approval until you have already clarified the intended product configuration and any defaulted article/publishing settings.
495
543
 
544
+ ## Marketplace workflow
545
+
546
+ The Outrank Marketplace connects sellers (sites that host backlink placements inside their upcoming Outrank articles and get paid per placement) with buyers (sites that spend backlink credits to place links on high-authority articles). Use `outrank-cli marketplace ...` for everything.
547
+
548
+ Role note: several marketplace commands are admin-only server-side — they succeed only when the user who created the API key currently holds an org admin role. Admin-only: `seller settings update`, all `seller integrations` writes, all `seller stripe` commands, `seller complete-onboarding`, `seller placements decline`, and the buyer billing/participation writes (checkout, change-plan, participation on/off). Buyer target-page writes (add, pause/activate, delete) are open to every org member. If a command returns `ADMIN_ROLE_REQUIRED`, tell the human an organization admin must do it (dashboard or an admin-created key).
549
+
550
+ ### Seller onboarding
551
+
552
+ The wizard order matches the dashboard: Blog -> Articles -> Integration -> Payouts, then complete. Start every session with:
553
+
554
+ - `outrank-cli marketplace seller status <productId>` — composite report (enrollment, per-step onboarding state, settings, gate state) whose guidance tells you the next step
555
+ - `outrank-cli marketplace seller onboard <productId> [--country <ISO2>]` — the orchestrator: reports remaining steps, and performs the safe automatic actions for you
556
+
557
+ Step-by-step, prompting the human for the same data the dashboard collects:
558
+
559
+ 1. **Blog** — collect/confirm the product's content details (`products get` / `products update`) and connect Google Search Console (`products gsc connect`, human opens the URL, then `gsc poll` and `gsc set-property`). GSC is mandatory before continuing.
560
+ 2. **Articles** — review article settings (`products output-settings get/update`) and set the marketplace publishing schedule: `marketplace seller settings update <productId> --json '{"dailyArticleLimit":<1-3>,"publishingDays":[<weekday numbers, at least 3>]}'` (defaults: 2/day on Mon/Wed/Fri).
561
+ 3. **Integration** — if the product already has a reusable CMS integration, **adopt it automatically and just inform the human — never ask them to pick a platform** (`marketplace seller integrations adopt`; `seller onboard` does this for you). Only when there is no reusable integration: run `marketplace seller integrations status` for the supported platforms, connect one with the `products integrations` commands, then `marketplace seller integrations claim <productId> --integration <id> --type <platform>`. Next.js Blog cannot be reused, and plugin-managed Framer setups need a new Framer connection — the error messages say so.
562
+ 4. **Payouts** — when the earlier steps are done it is safe to create the Stripe Express account: `marketplace seller stripe onboard <productId> --country <ISO2>` (ask the human for the two-letter country of their payout bank account). Surface the returned `onboardingUrl` — the human completes identity and bank details in a browser; the agent cannot.
563
+ 5. **Finish** — `marketplace seller complete-onboarding <productId>`, then check `marketplace seller eligibility <productId>`.
564
+
565
+ Always report the eligibility check results to the human — all five checks with actual vs required: GSC connected, Domain Rating >= 40, >= 100 avg monthly organic clicks (90-day window), >= 3 months of Search Console history, marketplace CMS connected. GSC and the CMS are fixable now; DR/clicks/history only grow over time — enrollment still completes, and the seller dashboard opens automatically once the site qualifies (re-checked automatically; force with `marketplace seller eligibility recheck`).
566
+
567
+ ### Seller operations (after onboarding)
568
+
569
+ - `marketplace seller placements list <productId> [--status needs_approval|planned|live|link_not_found|lost]` — the placement pipeline with counts
570
+ - `marketplace seller placements approve <placementId>` — only `pending_approval`; publishes immediately when the scheduled date is due
571
+ - `marketplace seller placements decline <placementId>` — admin-only; only `matched`/`pending_approval`; refunds the buyer and cleans up the pre-generated article
572
+ - `marketplace seller placements publish <placementId>` — publish early once the article is pregenerated and due
573
+ - `marketplace seller placements regenerate <placementId>` — retry a failed generation
574
+ - `marketplace seller earnings <productId>` — cents; payout states are available (cleared the 14-day hold), pending, paid
575
+ - `marketplace seller stripe manage <productId>` — Stripe Express dashboard login link for the human
576
+ - `marketplace seller settings get/update <productId>` — isActive (cannot enable while ineligible), approvalMode, dailyArticleLimit, publishingDays
577
+ - `marketplace seller content-history <productId>` — marketplace-only articles (articles list with track_scope=marketplace)
578
+ - `marketplace notifications list <productId> --role seller` and `mark-read`
579
+
580
+ ### Buyer
581
+
582
+ Start with `marketplace buyer status <productId>` — its guidance drives the whole setup sequence. The mandatory order:
583
+
584
+ 1. **Enterprise backlink plan** — `marketplace buyer billing-status <productId>`; if none, `marketplace buyer subscribe-quote <productId>` and surface `subscription_management_url` to the human (checkout is an interactive Stripe flow the agent cannot complete, and purchasing is restricted to org admins)
585
+ 2. **Target pages (mandatory before the dashboard opens)** — discover with `marketplace buyer target-pages discover <productId> [--source sitemap|root_url|integration]`, present candidates to the human, add with `marketplace buyer target-pages add <productId> --urls <u1,u2,...>` (own-domain only, max 10 pages, batches of up to 50), manage with `pause`/`activate`/`delete`
586
+ 3. **Participation** — `marketplace buyer participation on <productId>` (requires the plan plus >= 1 active target page)
587
+
588
+ Then: `placements list`, `placements regenerate|revalidate`, `article get/edit <placementId>` (editable only while the placement is matched or publishing and unpublished; keep the placement backlink intact), `credits history`, and `notifications --role buyer`. Each placement costs the host site's Domain Rating x 3 in backlink credits. Plan upgrades via `change-plan` are dashboard-only (interactive payment confirmation); downgrades, cancellations, and restores work from the CLI.
589
+
496
590
  ## Error handling
497
591
 
498
592
  - If `ok` is `false`, read both `error.message` and `agent_guidance`.
@@ -502,4 +596,4 @@ Do not ask for billing approval until you have already clarified the intended pr
502
596
 
503
597
  ## Current limitation
504
598
 
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.
599
+ The implementation plan is broader than the current live API. Keywords, core article flows, article improvements, linking, backlink exchange management, marketplace seller and buyer flows, 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, marketplace Enterprise checkout, Stripe Express identity/bank onboarding, and marketplace plan upgrades still require the dashboard or a browser. 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.5.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
+ };