outrank-cli 0.4.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 +25 -0
- package/SKILL.md +59 -2
- package/package.json +1 -1
- package/src/commands/marketplace.js +740 -0
- package/src/index.js +7 -0
package/README.md
CHANGED
|
@@ -55,6 +55,9 @@ This repo currently targets the agent endpoints that already exist in `outrank`:
|
|
|
55
55
|
- `improvements candidates list|brief|edit-brief|schedule`
|
|
56
56
|
- `improvements tasks list|reschedule|cancel|edit-brief|execute`
|
|
57
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`
|
|
58
61
|
- `subscription status`
|
|
59
62
|
- `usage stats`
|
|
60
63
|
- `billing portal-url`
|
|
@@ -202,6 +205,11 @@ outrank-cli products backlinks subscription 00000000-0000-0000-0000-000000000000
|
|
|
202
205
|
outrank-cli products backlinks subscribe-quote 00000000-0000-0000-0000-000000000000 --plan premium
|
|
203
206
|
outrank-cli keywords update 00000000-0000-0000-0000-000000000000 --json '{"scheduled_for":"2026-05-20"}'
|
|
204
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
|
|
205
213
|
```
|
|
206
214
|
|
|
207
215
|
## Google Search Console workflow
|
|
@@ -281,6 +289,23 @@ Example payload:
|
|
|
281
289
|
}
|
|
282
290
|
```
|
|
283
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
|
+
|
|
284
309
|
## Keyword mutation rules
|
|
285
310
|
|
|
286
311
|
- Run `outrank-cli keywords generation-status --product <product-id>` before keyword writes when you are not sure the product is writable.
|
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, discover/schedule/run/review/publish article improvements for existing posts, manage article settings, target audiences, competitors, Google Search Console connections, internal linking configuration,
|
|
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
|
|
@@ -14,6 +14,8 @@ Use `products backlinks ...` for backlink exchange settings, credits, review que
|
|
|
14
14
|
|
|
15
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
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
|
+
|
|
17
19
|
## Setup
|
|
18
20
|
|
|
19
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`.
|
|
@@ -34,6 +36,7 @@ Run group help when needed:
|
|
|
34
36
|
- `outrank-cli keywords --help`
|
|
35
37
|
- `outrank-cli articles --help`
|
|
36
38
|
- `outrank-cli improvements --help`
|
|
39
|
+
- `outrank-cli marketplace --help`
|
|
37
40
|
- `outrank-cli subscription --help`
|
|
38
41
|
- `outrank-cli usage --help`
|
|
39
42
|
- `outrank-cli billing --help`
|
|
@@ -84,6 +87,13 @@ Prefer inspecting before mutating:
|
|
|
84
87
|
- `outrank-cli products backlinks earned <productId>`
|
|
85
88
|
- `outrank-cli products backlinks unverified <productId>`
|
|
86
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`
|
|
87
97
|
- `outrank-cli subscription status`
|
|
88
98
|
- `outrank-cli usage stats`
|
|
89
99
|
|
|
@@ -402,6 +412,7 @@ When the user asks for keyword ideas:
|
|
|
402
412
|
|
|
403
413
|
Treat `keywords suggest` as candidate-generation only. It does not change the content plan until you add keywords.
|
|
404
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.
|
|
405
416
|
|
|
406
417
|
Before `keywords generate`:
|
|
407
418
|
|
|
@@ -530,6 +541,52 @@ Always do this in two steps:
|
|
|
530
541
|
Never guess `confirm-charge-cents`. Use the server-quoted number.
|
|
531
542
|
Do not ask for billing approval until you have already clarified the intended product configuration and any defaulted article/publishing settings.
|
|
532
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
|
+
|
|
533
590
|
## Error handling
|
|
534
591
|
|
|
535
592
|
- If `ok` is `false`, read both `error.message` and `agent_guidance`.
|
|
@@ -539,4 +596,4 @@ Do not ask for billing approval until you have already clarified the intended pr
|
|
|
539
596
|
|
|
540
597
|
## Current limitation
|
|
541
598
|
|
|
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
|
|
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
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { parseArgs, getRequiredPositional, readJsonInput, hasFlag, parseIntegerOption, getOption, getOptions } from '../core/args.js';
|
|
2
|
+
import { requestAgentApi } from '../core/api.js';
|
|
3
|
+
import { cliSuccess } from '../core/errors.js';
|
|
4
|
+
|
|
5
|
+
export const MARKETPLACE_HELP = `Usage:
|
|
6
|
+
outrank-cli marketplace seller status <product-id> [--refresh-stripe]
|
|
7
|
+
outrank-cli marketplace seller onboard <product-id> [--country <ISO2>]
|
|
8
|
+
outrank-cli marketplace seller eligibility <product-id> [--refresh]
|
|
9
|
+
outrank-cli marketplace seller eligibility recheck <product-id>
|
|
10
|
+
outrank-cli marketplace seller settings get <product-id>
|
|
11
|
+
outrank-cli marketplace seller settings update <product-id> --json '<object>'
|
|
12
|
+
outrank-cli marketplace seller integrations status <product-id>
|
|
13
|
+
outrank-cli marketplace seller integrations adopt <product-id>
|
|
14
|
+
outrank-cli marketplace seller integrations claim <product-id> --integration <integration-id> --type <platform>
|
|
15
|
+
outrank-cli marketplace seller integrations set-activation <product-id> --integration <integration-id> --type <platform> --status active|disabled
|
|
16
|
+
outrank-cli marketplace seller integrations delete <product-id> --integration <integration-id> --type <platform>
|
|
17
|
+
outrank-cli marketplace seller stripe onboard <product-id> --country <ISO2>
|
|
18
|
+
outrank-cli marketplace seller stripe manage <product-id>
|
|
19
|
+
outrank-cli marketplace seller stripe reset <product-id>
|
|
20
|
+
outrank-cli marketplace seller complete-onboarding <product-id>
|
|
21
|
+
outrank-cli marketplace seller placements list <product-id> [--status all|needs_approval|planned|live|link_not_found|lost] [--search <text>] [--page <n>] [--page-size <n>]
|
|
22
|
+
outrank-cli marketplace seller placements approve <placement-id>
|
|
23
|
+
outrank-cli marketplace seller placements decline <placement-id>
|
|
24
|
+
outrank-cli marketplace seller placements publish <placement-id>
|
|
25
|
+
outrank-cli marketplace seller placements regenerate <placement-id>
|
|
26
|
+
outrank-cli marketplace seller earnings <product-id>
|
|
27
|
+
outrank-cli marketplace seller content-history <product-id> [--status <status>] [--limit <n>] [--offset <n>]
|
|
28
|
+
outrank-cli marketplace notifications list <product-id> --role seller|buyer [--type <type>] [--sort oldest] [--page <n>] [--page-size <n>]
|
|
29
|
+
outrank-cli marketplace notifications mark-read <product-id> --role seller|buyer [--ids <id1,id2,...>]
|
|
30
|
+
|
|
31
|
+
outrank-cli marketplace buyer status <product-id>
|
|
32
|
+
outrank-cli marketplace buyer billing-status <product-id>
|
|
33
|
+
outrank-cli marketplace buyer subscription <product-id>
|
|
34
|
+
outrank-cli marketplace buyer subscribe-quote <product-id>
|
|
35
|
+
outrank-cli marketplace buyer checkout <product-id> --tier <tier>
|
|
36
|
+
outrank-cli marketplace buyer change-plan <product-id> --action change_tier|cancel_scheduled|cancel_subscription|restore_subscription [--tier <tier>]
|
|
37
|
+
outrank-cli marketplace buyer target-pages list <product-id>
|
|
38
|
+
outrank-cli marketplace buyer target-pages add <product-id> --urls <url1,url2,...> [--source manual|discovered]
|
|
39
|
+
outrank-cli marketplace buyer target-pages discover <product-id> [--source sitemap|root_url|integration] [--source-url <url>]
|
|
40
|
+
outrank-cli marketplace buyer target-pages pause <target-page-id>
|
|
41
|
+
outrank-cli marketplace buyer target-pages activate <target-page-id>
|
|
42
|
+
outrank-cli marketplace buyer target-pages delete <target-page-id>
|
|
43
|
+
outrank-cli marketplace buyer participation on <product-id>
|
|
44
|
+
outrank-cli marketplace buyer participation off <product-id>
|
|
45
|
+
outrank-cli marketplace buyer placements list <product-id> [--status all|live|in_progress|lost|rejected] [--search <text>] [--page <n>] [--page-size <n>]
|
|
46
|
+
outrank-cli marketplace buyer placements regenerate <placement-id>
|
|
47
|
+
outrank-cli marketplace buyer placements revalidate <placement-id>
|
|
48
|
+
outrank-cli marketplace buyer article get <placement-id>
|
|
49
|
+
outrank-cli marketplace buyer article edit <placement-id> --json '<object>'
|
|
50
|
+
outrank-cli marketplace buyer credits history <product-id> [--kind all|charge|refund] [--page <n>] [--page-size <n>]
|
|
51
|
+
|
|
52
|
+
Notes:
|
|
53
|
+
Seller onboarding wizard order: Blog (content details + Google Search Console) -> Articles (article settings + publishing schedule) -> Integration (marketplace CMS) -> Payouts (Stripe Express).
|
|
54
|
+
\`seller onboard\` reports every remaining step, auto-adopts an existing reusable CMS without asking, and creates the Stripe Express account when the earlier steps are done and --country is provided.
|
|
55
|
+
Admin-only commands (need an API key created by an org admin who still holds that role): settings update, all integrations writes, all stripe commands, complete-onboarding, placements decline, and buyer checkout/change-plan/participation. Buyer target-page writes are open to every org member.
|
|
56
|
+
The mandatory buyer sequence: Enterprise backlink plan (subscribe-quote -> a human org admin completes checkout) -> add target pages -> participation on.
|
|
57
|
+
Purchasing the Enterprise backlink plan is admin-only and happens through interactive Stripe checkout — surface subscription_management_url to the human.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
outrank-cli marketplace seller status 00000000-0000-0000-0000-000000000000
|
|
61
|
+
outrank-cli marketplace seller onboard 00000000-0000-0000-0000-000000000000 --country US
|
|
62
|
+
outrank-cli marketplace seller settings update 00000000-0000-0000-0000-000000000000 --json '{"dailyArticleLimit":2,"publishingDays":[1,3,5]}'
|
|
63
|
+
outrank-cli marketplace seller placements list 00000000-0000-0000-0000-000000000000 --status needs_approval
|
|
64
|
+
outrank-cli marketplace seller placements approve 00000000-0000-0000-0000-000000000000
|
|
65
|
+
outrank-cli marketplace notifications list 00000000-0000-0000-0000-000000000000 --role seller
|
|
66
|
+
outrank-cli marketplace buyer status 00000000-0000-0000-0000-000000000000
|
|
67
|
+
outrank-cli marketplace buyer target-pages add 00000000-0000-0000-0000-000000000000 --urls https://example.com/pricing,https://example.com/features
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const buildQueryPath = (basePath, searchParams) => {
|
|
71
|
+
const url = new URL(`https://local${basePath}`);
|
|
72
|
+
for (const [key, value] of Object.entries(searchParams)) {
|
|
73
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
74
|
+
url.searchParams.set(key, String(value));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return `${url.pathname}${url.search}`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const invalidUsage = (message, guidance) =>
|
|
81
|
+
Object.assign(new Error(message), {
|
|
82
|
+
cliCode: 'CLI_INVALID_USAGE',
|
|
83
|
+
guidance: guidance || 'Run `outrank-cli marketplace --help` to see the available marketplace commands.',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const requireOption = (parsed, name, label) => {
|
|
87
|
+
const value = getOption(parsed, name);
|
|
88
|
+
if (!value) {
|
|
89
|
+
throw invalidUsage(`Missing required option: --${name} ${label || ''}`.trim() + '.');
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const splitList = (values) =>
|
|
95
|
+
values
|
|
96
|
+
.flatMap((value) => value.split(','))
|
|
97
|
+
.map((value) => value.trim())
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
|
|
100
|
+
const handleSellerStatus = async (parsed) => {
|
|
101
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
102
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/seller/status`, {
|
|
103
|
+
refresh_stripe: hasFlag(parsed, 'refresh-stripe') ? 'true' : undefined,
|
|
104
|
+
});
|
|
105
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
106
|
+
return response.payload;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleSellerOnboard = async (parsed) => {
|
|
110
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
111
|
+
const country = getOption(parsed, 'country');
|
|
112
|
+
|
|
113
|
+
const statusResponse = await requestAgentApi(parsed, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
path: `/products/${productId}/marketplace/seller/status`,
|
|
116
|
+
});
|
|
117
|
+
if (!statusResponse.ok) return statusResponse.payload;
|
|
118
|
+
|
|
119
|
+
const status = statusResponse.payload?.data ?? {};
|
|
120
|
+
const actions = [];
|
|
121
|
+
|
|
122
|
+
if (status.enrollment?.completed) {
|
|
123
|
+
return cliSuccess(
|
|
124
|
+
{ status, actions },
|
|
125
|
+
`Seller onboarding is already complete for this product. ${statusResponse.payload?.agent_guidance ?? ''}`.trim(),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let cmsConnected = Boolean(status.onboarding?.cms?.connected);
|
|
130
|
+
const gscConnected = Boolean(status.onboarding?.gsc?.connected);
|
|
131
|
+
let stripeAccountConnected = Boolean(status.onboarding?.stripe?.accountConnected);
|
|
132
|
+
let stripeOnboardingUrl = null;
|
|
133
|
+
let adoptFailureMessage = null;
|
|
134
|
+
|
|
135
|
+
if (!cmsConnected && status.onboarding?.cms?.existing) {
|
|
136
|
+
const adoptResponse = await requestAgentApi(parsed, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
path: `/products/${productId}/marketplace/seller/integrations/adopt`,
|
|
139
|
+
});
|
|
140
|
+
actions.push({
|
|
141
|
+
action: 'adopt-existing-integration',
|
|
142
|
+
ok: Boolean(adoptResponse.payload?.ok),
|
|
143
|
+
result: adoptResponse.payload?.ok ? adoptResponse.payload.data : adoptResponse.payload?.error,
|
|
144
|
+
});
|
|
145
|
+
if (adoptResponse.payload?.ok) {
|
|
146
|
+
cmsConnected = true;
|
|
147
|
+
} else {
|
|
148
|
+
adoptFailureMessage = adoptResponse.payload?.error?.message ?? 'the existing integration could not be reused';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!stripeAccountConnected && gscConnected && cmsConnected && country) {
|
|
153
|
+
const stripeResponse = await requestAgentApi(parsed, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
path: `/products/${productId}/marketplace/seller/stripe-connect/onboard`,
|
|
156
|
+
body: { country },
|
|
157
|
+
});
|
|
158
|
+
actions.push({
|
|
159
|
+
action: 'stripe-express-onboard',
|
|
160
|
+
ok: Boolean(stripeResponse.payload?.ok),
|
|
161
|
+
result: stripeResponse.payload?.ok ? stripeResponse.payload.data : stripeResponse.payload?.error,
|
|
162
|
+
});
|
|
163
|
+
if (stripeResponse.payload?.ok) {
|
|
164
|
+
stripeAccountConnected = true;
|
|
165
|
+
stripeOnboardingUrl = stripeResponse.payload.data?.onboardingUrl ?? null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const eligibilityResponse = await requestAgentApi(parsed, {
|
|
170
|
+
method: 'GET',
|
|
171
|
+
path: buildQueryPath(`/products/${productId}/marketplace/seller/eligibility`, { refresh: 'true' }),
|
|
172
|
+
});
|
|
173
|
+
const eligibility = eligibilityResponse.payload?.ok ? eligibilityResponse.payload.data : null;
|
|
174
|
+
|
|
175
|
+
const remaining = [];
|
|
176
|
+
if (!gscConnected) {
|
|
177
|
+
remaining.push(
|
|
178
|
+
`Blog step: collect the product's content details from the human (outrank-cli products get/update ${productId}) and connect Google Search Console (outrank-cli products gsc connect ${productId}, then gsc poll and gsc set-property) — required before anything else`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
remaining.push(
|
|
182
|
+
`Articles step: review article settings (outrank-cli products output-settings get/update ${productId}) and set the publishing schedule (outrank-cli marketplace seller settings update ${productId} --json '{"dailyArticleLimit":<1-3>,"publishingDays":[...]}')`,
|
|
183
|
+
);
|
|
184
|
+
if (!cmsConnected) {
|
|
185
|
+
remaining.push(
|
|
186
|
+
adoptFailureMessage
|
|
187
|
+
? `Integration step: adopting the existing CMS failed (${adoptFailureMessage}) — run outrank-cli marketplace seller integrations status ${productId}, connect a new supported platform via the products integrations commands, then claim it for the marketplace`
|
|
188
|
+
: `Integration step: no reusable CMS was found — run outrank-cli marketplace seller integrations status ${productId}, connect a supported platform via the products integrations commands, then claim it for the marketplace`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (!stripeAccountConnected) {
|
|
192
|
+
remaining.push(
|
|
193
|
+
country
|
|
194
|
+
? `Payouts step: Stripe Express could not be created yet — finish the earlier steps, then run outrank-cli marketplace seller stripe onboard ${productId} --country ${country}`
|
|
195
|
+
: `Payouts step: ask the human for the two-letter country of their payout bank account, then run outrank-cli marketplace seller stripe onboard ${productId} --country <ISO2> (or re-run this command with --country)`,
|
|
196
|
+
);
|
|
197
|
+
} else if (stripeOnboardingUrl) {
|
|
198
|
+
remaining.push(
|
|
199
|
+
`Payouts step: the Stripe Express account is ready — the human must open ${stripeOnboardingUrl} in a browser and finish identity and bank details`,
|
|
200
|
+
);
|
|
201
|
+
} else if (!status.onboarding?.stripe?.payoutsEnabled) {
|
|
202
|
+
remaining.push(
|
|
203
|
+
`Payouts step: Stripe onboarding looks unfinished — get a fresh link with outrank-cli marketplace seller stripe onboard ${productId}${country ? ` --country ${country}` : ''} and have the human complete it`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
remaining.push(
|
|
207
|
+
`Finish with outrank-cli marketplace seller complete-onboarding ${productId}, then re-check outrank-cli marketplace seller status ${productId}`,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const eligibilityNote = eligibilityResponse.payload?.agent_guidance
|
|
211
|
+
? ` Eligibility: ${eligibilityResponse.payload.agent_guidance}`
|
|
212
|
+
: '';
|
|
213
|
+
|
|
214
|
+
return cliSuccess(
|
|
215
|
+
{ status, actions, eligibility },
|
|
216
|
+
`Seller onboarding in progress (wizard order: Blog -> Articles -> Integration -> Payouts). ${actions.length > 0 ? `Automatic actions taken: ${actions.map((entry) => `${entry.action} (${entry.ok ? 'ok' : 'failed'})`).join(', ')}. ` : ''}Steps to complete with the human: ${remaining.join('; ')}.${eligibilityNote} Always report the eligibility check results to the human.`,
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const handleSellerEligibility = async (argv) => {
|
|
221
|
+
if (argv[0] === 'recheck') {
|
|
222
|
+
const parsed = parseArgs(argv.slice(1));
|
|
223
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
224
|
+
const response = await requestAgentApi(parsed, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
path: `/products/${productId}/marketplace/seller/eligibility/recheck`,
|
|
227
|
+
});
|
|
228
|
+
return response.payload;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const parsed = parseArgs(argv);
|
|
232
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
233
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/seller/eligibility`, {
|
|
234
|
+
refresh: hasFlag(parsed, 'refresh') ? 'true' : undefined,
|
|
235
|
+
});
|
|
236
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
237
|
+
return response.payload;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleSellerSettings = async (argv) => {
|
|
241
|
+
const subcommand = argv[0];
|
|
242
|
+
const parsed = parseArgs(argv.slice(1));
|
|
243
|
+
|
|
244
|
+
if (subcommand === 'get') {
|
|
245
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
246
|
+
const response = await requestAgentApi(parsed, {
|
|
247
|
+
method: 'GET',
|
|
248
|
+
path: `/products/${productId}/marketplace/seller/settings`,
|
|
249
|
+
});
|
|
250
|
+
return response.payload;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (subcommand === 'update') {
|
|
254
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
255
|
+
const body = await readJsonInput(parsed);
|
|
256
|
+
const response = await requestAgentApi(parsed, {
|
|
257
|
+
method: 'PATCH',
|
|
258
|
+
path: `/products/${productId}/marketplace/seller/settings`,
|
|
259
|
+
body,
|
|
260
|
+
});
|
|
261
|
+
return response.payload;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw invalidUsage(`Unknown marketplace seller settings subcommand: ${subcommand}.`);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleSellerIntegrations = async (argv) => {
|
|
268
|
+
const subcommand = argv[0];
|
|
269
|
+
const parsed = parseArgs(argv.slice(1));
|
|
270
|
+
|
|
271
|
+
if (subcommand === 'status') {
|
|
272
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
273
|
+
const response = await requestAgentApi(parsed, {
|
|
274
|
+
method: 'GET',
|
|
275
|
+
path: `/products/${productId}/marketplace/seller/integrations`,
|
|
276
|
+
});
|
|
277
|
+
return response.payload;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (subcommand === 'adopt') {
|
|
281
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
282
|
+
const response = await requestAgentApi(parsed, {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
path: `/products/${productId}/marketplace/seller/integrations/adopt`,
|
|
285
|
+
});
|
|
286
|
+
return response.payload;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (subcommand === 'claim') {
|
|
290
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
291
|
+
const response = await requestAgentApi(parsed, {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
path: `/products/${productId}/marketplace/seller/integrations/claim`,
|
|
294
|
+
body: {
|
|
295
|
+
integrationId: requireOption(parsed, 'integration', '<integration-id>'),
|
|
296
|
+
integrationType: requireOption(parsed, 'type', '<platform>'),
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
return response.payload;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (subcommand === 'set-activation') {
|
|
303
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
304
|
+
const integrationId = requireOption(parsed, 'integration', '<integration-id>');
|
|
305
|
+
const response = await requestAgentApi(parsed, {
|
|
306
|
+
method: 'PATCH',
|
|
307
|
+
path: `/products/${productId}/marketplace/seller/integrations/${integrationId}`,
|
|
308
|
+
body: {
|
|
309
|
+
integrationType: requireOption(parsed, 'type', '<platform>'),
|
|
310
|
+
activationStatus: requireOption(parsed, 'status', 'active|disabled'),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
return response.payload;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (subcommand === 'delete') {
|
|
317
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
318
|
+
const integrationId = requireOption(parsed, 'integration', '<integration-id>');
|
|
319
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/seller/integrations/${integrationId}`, {
|
|
320
|
+
integration_type: requireOption(parsed, 'type', '<platform>'),
|
|
321
|
+
});
|
|
322
|
+
const response = await requestAgentApi(parsed, { method: 'DELETE', path });
|
|
323
|
+
return response.payload;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
throw invalidUsage(`Unknown marketplace seller integrations subcommand: ${subcommand}.`);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const handleSellerStripe = async (argv) => {
|
|
330
|
+
const subcommand = argv[0];
|
|
331
|
+
const parsed = parseArgs(argv.slice(1));
|
|
332
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
333
|
+
|
|
334
|
+
if (subcommand === 'onboard') {
|
|
335
|
+
const country = getOption(parsed, 'country');
|
|
336
|
+
const response = await requestAgentApi(parsed, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
path: `/products/${productId}/marketplace/seller/stripe-connect/onboard`,
|
|
339
|
+
body: country ? { country } : {},
|
|
340
|
+
});
|
|
341
|
+
return response.payload;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (subcommand === 'manage') {
|
|
345
|
+
const response = await requestAgentApi(parsed, {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
path: `/products/${productId}/marketplace/seller/stripe-connect/manage`,
|
|
348
|
+
});
|
|
349
|
+
return response.payload;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (subcommand === 'reset') {
|
|
353
|
+
const response = await requestAgentApi(parsed, {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
path: `/products/${productId}/marketplace/seller/stripe-connect/reset`,
|
|
356
|
+
});
|
|
357
|
+
return response.payload;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
throw invalidUsage(`Unknown marketplace seller stripe subcommand: ${subcommand}.`);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const SELLER_PLACEMENT_ACTIONS = new Set(['approve', 'decline', 'publish', 'regenerate']);
|
|
364
|
+
|
|
365
|
+
const handleSellerPlacements = async (argv) => {
|
|
366
|
+
const subcommand = argv[0];
|
|
367
|
+
const parsed = parseArgs(argv.slice(1));
|
|
368
|
+
|
|
369
|
+
if (subcommand === 'list') {
|
|
370
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
371
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/seller/placements`, {
|
|
372
|
+
status: getOption(parsed, 'status'),
|
|
373
|
+
search: getOption(parsed, 'search'),
|
|
374
|
+
page: parseIntegerOption(parsed, 'page', { min: 1 }),
|
|
375
|
+
page_size: parseIntegerOption(parsed, 'page-size', { min: 1, max: 100 }),
|
|
376
|
+
});
|
|
377
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
378
|
+
return response.payload;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (SELLER_PLACEMENT_ACTIONS.has(subcommand)) {
|
|
382
|
+
const placementId = getRequiredPositional(parsed, 0, '<placement-id>');
|
|
383
|
+
const response = await requestAgentApi(parsed, {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
path: `/marketplace/placements/${placementId}/${subcommand}`,
|
|
386
|
+
});
|
|
387
|
+
return response.payload;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
throw invalidUsage(`Unknown marketplace seller placements subcommand: ${subcommand}.`);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const handleSellerContentHistory = async (parsed) => {
|
|
394
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
395
|
+
const path = buildQueryPath('/articles', {
|
|
396
|
+
product_id: productId,
|
|
397
|
+
track_scope: 'marketplace',
|
|
398
|
+
status: getOption(parsed, 'status'),
|
|
399
|
+
limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 100 }),
|
|
400
|
+
offset: parseIntegerOption(parsed, 'offset', { min: 0 }),
|
|
401
|
+
});
|
|
402
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
403
|
+
return response.payload;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const groupHelp = (guidance) =>
|
|
407
|
+
cliSuccess(
|
|
408
|
+
{
|
|
409
|
+
usage: MARKETPLACE_HELP.trim(),
|
|
410
|
+
},
|
|
411
|
+
guidance,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const handleSellerCommand = async (argv) => {
|
|
415
|
+
const subcommand = argv[0];
|
|
416
|
+
|
|
417
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
418
|
+
return groupHelp(
|
|
419
|
+
'Choose a seller subcommand: status, onboard, eligibility, settings, integrations, stripe, complete-onboarding, placements, earnings, content-history.',
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (subcommand === 'status') return handleSellerStatus(parseArgs(argv.slice(1)));
|
|
424
|
+
if (subcommand === 'onboard') return handleSellerOnboard(parseArgs(argv.slice(1)));
|
|
425
|
+
if (subcommand === 'eligibility') return handleSellerEligibility(argv.slice(1));
|
|
426
|
+
if (subcommand === 'settings') return handleSellerSettings(argv.slice(1));
|
|
427
|
+
if (subcommand === 'integrations') return handleSellerIntegrations(argv.slice(1));
|
|
428
|
+
if (subcommand === 'stripe') return handleSellerStripe(argv.slice(1));
|
|
429
|
+
if (subcommand === 'placements') return handleSellerPlacements(argv.slice(1));
|
|
430
|
+
if (subcommand === 'earnings') {
|
|
431
|
+
const parsed = parseArgs(argv.slice(1));
|
|
432
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
433
|
+
const response = await requestAgentApi(parsed, {
|
|
434
|
+
method: 'GET',
|
|
435
|
+
path: `/products/${productId}/marketplace/seller/earnings`,
|
|
436
|
+
});
|
|
437
|
+
return response.payload;
|
|
438
|
+
}
|
|
439
|
+
if (subcommand === 'complete-onboarding') {
|
|
440
|
+
const parsed = parseArgs(argv.slice(1));
|
|
441
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
442
|
+
const response = await requestAgentApi(parsed, {
|
|
443
|
+
method: 'POST',
|
|
444
|
+
path: `/products/${productId}/marketplace/seller/complete-onboarding`,
|
|
445
|
+
});
|
|
446
|
+
return response.payload;
|
|
447
|
+
}
|
|
448
|
+
if (subcommand === 'content-history') return handleSellerContentHistory(parseArgs(argv.slice(1)));
|
|
449
|
+
|
|
450
|
+
throw invalidUsage(`Unknown marketplace seller subcommand: ${subcommand}.`);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const handleNotifications = async (argv) => {
|
|
454
|
+
const subcommand = argv[0];
|
|
455
|
+
|
|
456
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
457
|
+
return groupHelp('Choose a notifications subcommand: list or mark-read (both take --role seller|buyer).');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const parsed = parseArgs(argv.slice(1));
|
|
461
|
+
|
|
462
|
+
if (subcommand === 'list') {
|
|
463
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
464
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/notifications`, {
|
|
465
|
+
role: requireOption(parsed, 'role', 'seller|buyer'),
|
|
466
|
+
type: getOption(parsed, 'type'),
|
|
467
|
+
sort: getOption(parsed, 'sort'),
|
|
468
|
+
page: parseIntegerOption(parsed, 'page', { min: 1 }),
|
|
469
|
+
page_size: parseIntegerOption(parsed, 'page-size', { min: 1, max: 100 }),
|
|
470
|
+
});
|
|
471
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
472
|
+
return response.payload;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (subcommand === 'mark-read') {
|
|
476
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
477
|
+
const ids = splitList(getOptions(parsed, 'ids'));
|
|
478
|
+
const response = await requestAgentApi(parsed, {
|
|
479
|
+
method: 'PATCH',
|
|
480
|
+
path: `/products/${productId}/marketplace/notifications`,
|
|
481
|
+
body: {
|
|
482
|
+
role: requireOption(parsed, 'role', 'seller|buyer'),
|
|
483
|
+
...(ids.length > 0 ? { ids } : {}),
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
return response.payload;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
throw invalidUsage(`Unknown marketplace notifications subcommand: ${subcommand}.`);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const handleBuyerTargetPages = async (argv) => {
|
|
493
|
+
const subcommand = argv[0];
|
|
494
|
+
const parsed = parseArgs(argv.slice(1));
|
|
495
|
+
|
|
496
|
+
if (subcommand === 'list') {
|
|
497
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
498
|
+
const response = await requestAgentApi(parsed, {
|
|
499
|
+
method: 'GET',
|
|
500
|
+
path: `/products/${productId}/marketplace/buyer/target-pages`,
|
|
501
|
+
});
|
|
502
|
+
return response.payload;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (subcommand === 'add') {
|
|
506
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
507
|
+
const urls = splitList(getOptions(parsed, 'urls'));
|
|
508
|
+
if (urls.length === 0) {
|
|
509
|
+
throw invalidUsage('Missing required option: --urls <url1,url2,...>.');
|
|
510
|
+
}
|
|
511
|
+
const source = getOption(parsed, 'source');
|
|
512
|
+
const response = await requestAgentApi(parsed, {
|
|
513
|
+
method: 'POST',
|
|
514
|
+
path: `/products/${productId}/marketplace/buyer/target-pages`,
|
|
515
|
+
body: { urls, ...(source ? { source } : {}) },
|
|
516
|
+
});
|
|
517
|
+
return response.payload;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (subcommand === 'discover') {
|
|
521
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
522
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/buyer/target-pages/discover`, {
|
|
523
|
+
source_type: getOption(parsed, 'source'),
|
|
524
|
+
source_url: getOption(parsed, 'source-url'),
|
|
525
|
+
});
|
|
526
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
527
|
+
return response.payload;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (subcommand === 'pause' || subcommand === 'activate') {
|
|
531
|
+
const targetPageId = getRequiredPositional(parsed, 0, '<target-page-id>');
|
|
532
|
+
const response = await requestAgentApi(parsed, {
|
|
533
|
+
method: 'PATCH',
|
|
534
|
+
path: `/marketplace/target-pages/${targetPageId}`,
|
|
535
|
+
body: { status: subcommand === 'pause' ? 'paused' : 'active' },
|
|
536
|
+
});
|
|
537
|
+
return response.payload;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (subcommand === 'delete') {
|
|
541
|
+
const targetPageId = getRequiredPositional(parsed, 0, '<target-page-id>');
|
|
542
|
+
const response = await requestAgentApi(parsed, {
|
|
543
|
+
method: 'DELETE',
|
|
544
|
+
path: `/marketplace/target-pages/${targetPageId}`,
|
|
545
|
+
});
|
|
546
|
+
return response.payload;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
throw invalidUsage(`Unknown marketplace buyer target-pages subcommand: ${subcommand}.`);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const handleBuyerPlacements = async (argv) => {
|
|
553
|
+
const subcommand = argv[0];
|
|
554
|
+
const parsed = parseArgs(argv.slice(1));
|
|
555
|
+
|
|
556
|
+
if (subcommand === 'list') {
|
|
557
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
558
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/buyer/placements`, {
|
|
559
|
+
status: getOption(parsed, 'status'),
|
|
560
|
+
search: getOption(parsed, 'search'),
|
|
561
|
+
page: parseIntegerOption(parsed, 'page', { min: 1 }),
|
|
562
|
+
page_size: parseIntegerOption(parsed, 'page-size', { min: 1, max: 100 }),
|
|
563
|
+
});
|
|
564
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
565
|
+
return response.payload;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (subcommand === 'regenerate' || subcommand === 'revalidate') {
|
|
569
|
+
const placementId = getRequiredPositional(parsed, 0, '<placement-id>');
|
|
570
|
+
const response = await requestAgentApi(parsed, {
|
|
571
|
+
method: 'POST',
|
|
572
|
+
path: `/marketplace/buyer-placements/${placementId}/${subcommand}`,
|
|
573
|
+
});
|
|
574
|
+
return response.payload;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
throw invalidUsage(`Unknown marketplace buyer placements subcommand: ${subcommand}.`);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const handleBuyerArticle = async (argv) => {
|
|
581
|
+
const subcommand = argv[0];
|
|
582
|
+
const parsed = parseArgs(argv.slice(1));
|
|
583
|
+
const placementId = getRequiredPositional(parsed, 0, '<placement-id>');
|
|
584
|
+
|
|
585
|
+
if (subcommand === 'get') {
|
|
586
|
+
const response = await requestAgentApi(parsed, {
|
|
587
|
+
method: 'GET',
|
|
588
|
+
path: `/marketplace/buyer-placements/${placementId}/article`,
|
|
589
|
+
});
|
|
590
|
+
return response.payload;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (subcommand === 'edit') {
|
|
594
|
+
const body = await readJsonInput(parsed);
|
|
595
|
+
const response = await requestAgentApi(parsed, {
|
|
596
|
+
method: 'PATCH',
|
|
597
|
+
path: `/marketplace/buyer-placements/${placementId}/article`,
|
|
598
|
+
body,
|
|
599
|
+
});
|
|
600
|
+
return response.payload;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
throw invalidUsage(`Unknown marketplace buyer article subcommand: ${subcommand}.`);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const handleBuyerCredits = async (argv) => {
|
|
607
|
+
const subcommand = argv[0];
|
|
608
|
+
const parsed = parseArgs(argv.slice(1));
|
|
609
|
+
|
|
610
|
+
if (subcommand === 'history') {
|
|
611
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
612
|
+
const path = buildQueryPath(`/products/${productId}/marketplace/buyer/credits/history`, {
|
|
613
|
+
kind: getOption(parsed, 'kind'),
|
|
614
|
+
page: parseIntegerOption(parsed, 'page', { min: 1 }),
|
|
615
|
+
page_size: parseIntegerOption(parsed, 'page-size', { min: 1, max: 100 }),
|
|
616
|
+
});
|
|
617
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
618
|
+
return response.payload;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
throw invalidUsage(`Unknown marketplace buyer credits subcommand: ${subcommand}.`);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const handleBuyerCommand = async (argv) => {
|
|
625
|
+
const subcommand = argv[0];
|
|
626
|
+
|
|
627
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
628
|
+
return groupHelp(
|
|
629
|
+
'Choose a buyer subcommand: status, billing-status, subscription, subscribe-quote, checkout, change-plan, target-pages, participation, placements, article, credits. Start with `marketplace buyer status <productId>` — its guidance drives the setup sequence.',
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (subcommand === 'status') {
|
|
634
|
+
const parsed = parseArgs(argv.slice(1));
|
|
635
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
636
|
+
const response = await requestAgentApi(parsed, {
|
|
637
|
+
method: 'GET',
|
|
638
|
+
path: `/products/${productId}/marketplace/buyer/status`,
|
|
639
|
+
});
|
|
640
|
+
return response.payload;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (subcommand === 'billing-status') {
|
|
644
|
+
const parsed = parseArgs(argv.slice(1));
|
|
645
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
646
|
+
const response = await requestAgentApi(parsed, {
|
|
647
|
+
method: 'GET',
|
|
648
|
+
path: `/products/${productId}/marketplace/buyer/billing-status`,
|
|
649
|
+
});
|
|
650
|
+
return response.payload;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (subcommand === 'subscription') {
|
|
654
|
+
const parsed = parseArgs(argv.slice(1));
|
|
655
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
656
|
+
const response = await requestAgentApi(parsed, {
|
|
657
|
+
method: 'GET',
|
|
658
|
+
path: `/products/${productId}/marketplace/buyer/subscription`,
|
|
659
|
+
});
|
|
660
|
+
return response.payload;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (subcommand === 'subscribe-quote') {
|
|
664
|
+
const parsed = parseArgs(argv.slice(1));
|
|
665
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
666
|
+
const path = buildQueryPath(`/products/${productId}/backlinks/subscribe-quote`, { plan: 'enterprise' });
|
|
667
|
+
const response = await requestAgentApi(parsed, { method: 'GET', path });
|
|
668
|
+
return response.payload;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (subcommand === 'checkout') {
|
|
672
|
+
const parsed = parseArgs(argv.slice(1));
|
|
673
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
674
|
+
const response = await requestAgentApi(parsed, {
|
|
675
|
+
method: 'POST',
|
|
676
|
+
path: `/products/${productId}/marketplace/buyer/checkout`,
|
|
677
|
+
body: { tier: requireOption(parsed, 'tier', '<tier>') },
|
|
678
|
+
});
|
|
679
|
+
return response.payload;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (subcommand === 'change-plan') {
|
|
683
|
+
const parsed = parseArgs(argv.slice(1));
|
|
684
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
685
|
+
const tier = getOption(parsed, 'tier');
|
|
686
|
+
const response = await requestAgentApi(parsed, {
|
|
687
|
+
method: 'POST',
|
|
688
|
+
path: `/products/${productId}/marketplace/buyer/change-plan`,
|
|
689
|
+
body: {
|
|
690
|
+
action: requireOption(parsed, 'action', 'change_tier|cancel_scheduled|cancel_subscription|restore_subscription'),
|
|
691
|
+
...(tier ? { tier } : {}),
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
return response.payload;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (subcommand === 'participation') {
|
|
698
|
+
const mode = argv[1];
|
|
699
|
+
if (mode !== 'on' && mode !== 'off') {
|
|
700
|
+
throw invalidUsage('marketplace buyer participation requires `on` or `off`.');
|
|
701
|
+
}
|
|
702
|
+
const parsed = parseArgs(argv.slice(2));
|
|
703
|
+
const productId = getRequiredPositional(parsed, 0, '<product-id>');
|
|
704
|
+
const response = await requestAgentApi(parsed, {
|
|
705
|
+
method: 'POST',
|
|
706
|
+
path: `/products/${productId}/marketplace/buyer/participation`,
|
|
707
|
+
body: { enabled: mode === 'on' },
|
|
708
|
+
});
|
|
709
|
+
return response.payload;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (subcommand === 'target-pages') return handleBuyerTargetPages(argv.slice(1));
|
|
713
|
+
if (subcommand === 'placements') return handleBuyerPlacements(argv.slice(1));
|
|
714
|
+
if (subcommand === 'article') return handleBuyerArticle(argv.slice(1));
|
|
715
|
+
if (subcommand === 'credits') return handleBuyerCredits(argv.slice(1));
|
|
716
|
+
|
|
717
|
+
throw invalidUsage(`Unknown marketplace buyer subcommand: ${subcommand}.`);
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
export const handleMarketplaceCommand = async (argv) => {
|
|
721
|
+
const subcommand = argv[0];
|
|
722
|
+
const parsed = parseArgs(argv.slice(1));
|
|
723
|
+
const wantsHelp =
|
|
724
|
+
!subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
|
|
725
|
+
|
|
726
|
+
if (wantsHelp) {
|
|
727
|
+
return cliSuccess(
|
|
728
|
+
{
|
|
729
|
+
usage: MARKETPLACE_HELP.trim(),
|
|
730
|
+
},
|
|
731
|
+
'Choose `seller`, `buyer`, or `notifications`. Sellers earn by hosting marketplace backlinks in their articles; buyers spend backlink credits to place links on high-authority articles. Start buyers with `marketplace buyer status` and sellers with `marketplace seller status`.',
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (subcommand === 'seller') return handleSellerCommand(argv.slice(1));
|
|
736
|
+
if (subcommand === 'buyer') return handleBuyerCommand(argv.slice(1));
|
|
737
|
+
if (subcommand === 'notifications') return handleNotifications(argv.slice(1));
|
|
738
|
+
|
|
739
|
+
throw invalidUsage(`Unknown marketplace subcommand: ${subcommand}.`);
|
|
740
|
+
};
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { handleUsageCommand } from './commands/usage.js';
|
|
|
13
13
|
import { handleKeywordsCommand } from './commands/keywords.js';
|
|
14
14
|
import { handleArticlesCommand } from './commands/articles.js';
|
|
15
15
|
import { handleImprovementsCommand } from './commands/improvements.js';
|
|
16
|
+
import { handleMarketplaceCommand } from './commands/marketplace.js';
|
|
16
17
|
|
|
17
18
|
const getVersion = () => {
|
|
18
19
|
try {
|
|
@@ -37,6 +38,7 @@ Groups:
|
|
|
37
38
|
keywords Inspect, check write status, add, reschedule, delete, suggest, generate, and export keyword plans.
|
|
38
39
|
articles Inspect article metadata and bodies, edit content, retry publish, precreate, and generate.
|
|
39
40
|
improvements Discover, brief, schedule, run, review, and publish article improvements for existing posts.
|
|
41
|
+
marketplace Seller onboarding, eligibility, placements, earnings, Stripe payouts, and buyer setup for the Outrank Marketplace.
|
|
40
42
|
subscription Inspect plan, trial, quota, and add-on status.
|
|
41
43
|
usage Inspect current usage and quota consumption.
|
|
42
44
|
billing Product-slot purchase flows.
|
|
@@ -72,6 +74,9 @@ Examples:
|
|
|
72
74
|
outrank-cli products backlinks get <product-id>
|
|
73
75
|
outrank-cli products backlinks earned <product-id> --limit 20 --include-article
|
|
74
76
|
outrank-cli billing purchase-products quote --additional-products 2
|
|
77
|
+
outrank-cli marketplace seller status <product-id>
|
|
78
|
+
outrank-cli marketplace seller onboard <product-id> --country US
|
|
79
|
+
outrank-cli marketplace seller placements list <product-id> --status needs_approval
|
|
75
80
|
`;
|
|
76
81
|
|
|
77
82
|
const main = async () => {
|
|
@@ -117,6 +122,8 @@ const main = async () => {
|
|
|
117
122
|
result = await handleArticlesCommand(rest);
|
|
118
123
|
} else if (group === 'improvements') {
|
|
119
124
|
result = await handleImprovementsCommand(rest);
|
|
125
|
+
} else if (group === 'marketplace') {
|
|
126
|
+
result = await handleMarketplaceCommand(rest);
|
|
120
127
|
} else if (group === 'subscription') {
|
|
121
128
|
result = await handleSubscriptionCommand(rest);
|
|
122
129
|
} else if (group === 'usage') {
|