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 +52 -4
- package/SKILL.md +96 -2
- package/package.json +1 -1
- package/src/commands/improvements.js +277 -0
- package/src/commands/marketplace.js +740 -0
- package/src/index.js +13 -0
|
@@ -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
|
+
};
|