outrank-cli 0.5.0 → 0.6.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 +317 -20
- package/SKILL.md +20 -2
- package/bin/outrank-mcp.js +3 -0
- package/package.json +21 -5
- package/src/adapters/cli/jsonInput.js +42 -0
- package/src/adapters/cli/parse.js +124 -0
- package/src/adapters/cli/run.js +139 -0
- package/src/adapters/mcp/constants.js +49 -0
- package/src/adapters/mcp/descriptions.js +87 -0
- package/src/adapters/mcp/help.js +34 -0
- package/src/adapters/mcp/input.js +195 -0
- package/src/adapters/mcp/instructions.js +139 -0
- package/src/adapters/mcp/logger.js +45 -0
- package/src/adapters/mcp/main.js +36 -0
- package/src/adapters/mcp/options.js +118 -0
- package/src/adapters/mcp/result.js +33 -0
- package/src/adapters/mcp/server.js +174 -0
- package/src/adapters/mcp/session.js +274 -0
- package/src/adapters/mcp/toolNotes.js +426 -0
- package/src/adapters/mcp/toolsets.js +106 -0
- package/src/commands/articles.js +285 -301
- package/src/commands/auth.js +204 -86
- package/src/commands/billing.js +102 -104
- package/src/commands/improvements.js +319 -211
- package/src/commands/index.js +22 -0
- package/src/commands/keywords.js +266 -249
- package/src/commands/marketplace/buyer.js +386 -0
- package/src/commands/marketplace/index.js +83 -0
- package/src/commands/marketplace/notifications.js +60 -0
- package/src/commands/marketplace/seller.js +595 -0
- package/src/commands/marketplace/shared.js +27 -0
- package/src/commands/products/backlinks.js +181 -0
- package/src/commands/products/core.js +149 -0
- package/src/commands/products/gsc.js +250 -0
- package/src/commands/products/index.js +174 -0
- package/src/commands/products/integrations.js +343 -0
- package/src/commands/products/linking.js +192 -0
- package/src/commands/products/shared.js +8 -0
- package/src/commands/registry.js +272 -0
- package/src/commands/subscription.js +20 -30
- package/src/commands/usage.js +17 -30
- package/src/core/config.js +243 -62
- package/src/core/constants.js +101 -0
- package/src/core/context.js +311 -0
- package/src/core/errors.js +39 -0
- package/src/core/execute.js +71 -0
- package/src/core/files.js +186 -0
- package/src/core/http.js +393 -0
- package/src/core/profileSelection.js +7 -0
- package/src/core/query.js +11 -0
- package/src/core/redact.js +27 -0
- package/src/core/schema.js +222 -0
- package/src/core/version.js +12 -0
- package/src/index.js +30 -58
- package/src/commands/marketplace.js +0 -740
- package/src/commands/products.js +0 -1185
- package/src/core/api.js +0 -82
- package/src/core/args.js +0 -144
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { LIST_MAX_LIMIT, LIST_MIN_LIMIT, MIN_OFFSET } from '../../core/constants.js';
|
|
2
|
+
import { CliErrorCode, cliError, cliSuccess } from '../../core/errors.js';
|
|
3
|
+
import { BoolSerialize, bool, int, string, uuid } from '../../core/schema.js';
|
|
4
|
+
import { BodyMode, CommandKind, Toolset, defineCommand, defineGroup } from '../registry.js';
|
|
5
|
+
import {
|
|
6
|
+
MARKETPLACE_UNKNOWN_GUIDANCE,
|
|
7
|
+
PLACEMENT_CLI,
|
|
8
|
+
PRODUCT_CLI,
|
|
9
|
+
REQUIRED_OPTION,
|
|
10
|
+
listFilterQuery,
|
|
11
|
+
listFilters,
|
|
12
|
+
placementInput,
|
|
13
|
+
productInput,
|
|
14
|
+
unknownFor,
|
|
15
|
+
} from './shared.js';
|
|
16
|
+
|
|
17
|
+
const TOOLSETS = [Toolset.MARKETPLACE_SELLER];
|
|
18
|
+
const SELLER_PATH = '/products/{productId}/marketplace/seller';
|
|
19
|
+
const SELLER_INTEGRATION_PATH = `${SELLER_PATH}/integrations/{integrationId}`;
|
|
20
|
+
const STRIPE_PATH = `${SELLER_PATH}/stripe-connect`;
|
|
21
|
+
const PLACEMENT_ACTION_PATH = '/marketplace/placements/{placementId}/{action}';
|
|
22
|
+
const TRUE_TEXT = 'true';
|
|
23
|
+
const MARKETPLACE_TRACK_SCOPE = 'marketplace';
|
|
24
|
+
const MAX_SERVER_MESSAGE_CHARS = 200;
|
|
25
|
+
const STRIPE_ONBOARDING_HOST = 'connect.stripe.com';
|
|
26
|
+
const HTTPS_PROTOCOL = 'https:';
|
|
27
|
+
const ADOPT_FALLBACK_MESSAGE = 'the existing integration could not be reused';
|
|
28
|
+
const STRIPE_FALLBACK_MESSAGE = 'the Stripe Express account could not be created';
|
|
29
|
+
|
|
30
|
+
const SELLER_USAGE = `Usage:
|
|
31
|
+
outrank-cli marketplace seller status <product-id> [--refresh-stripe]
|
|
32
|
+
outrank-cli marketplace seller onboard <product-id> [--country <ISO2>]
|
|
33
|
+
outrank-cli marketplace seller eligibility <product-id> [--refresh]
|
|
34
|
+
outrank-cli marketplace seller eligibility recheck <product-id>
|
|
35
|
+
outrank-cli marketplace seller settings get <product-id>
|
|
36
|
+
outrank-cli marketplace seller settings update <product-id> --json '<object>'
|
|
37
|
+
outrank-cli marketplace seller integrations status <product-id>
|
|
38
|
+
outrank-cli marketplace seller integrations adopt <product-id>
|
|
39
|
+
outrank-cli marketplace seller integrations claim <product-id> --integration <integration-id> --type <platform>
|
|
40
|
+
outrank-cli marketplace seller integrations set-activation <product-id> --integration <integration-id> --type <platform> --status active|disabled
|
|
41
|
+
outrank-cli marketplace seller integrations delete <product-id> --integration <integration-id> --type <platform>
|
|
42
|
+
outrank-cli marketplace seller stripe onboard <product-id> --country <ISO2>
|
|
43
|
+
outrank-cli marketplace seller stripe manage <product-id>
|
|
44
|
+
outrank-cli marketplace seller stripe reset <product-id>
|
|
45
|
+
outrank-cli marketplace seller complete-onboarding <product-id>
|
|
46
|
+
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>]
|
|
47
|
+
outrank-cli marketplace seller placements approve <placement-id>
|
|
48
|
+
outrank-cli marketplace seller placements decline <placement-id>
|
|
49
|
+
outrank-cli marketplace seller placements publish <placement-id>
|
|
50
|
+
outrank-cli marketplace seller placements regenerate <placement-id>
|
|
51
|
+
outrank-cli marketplace seller earnings <product-id>
|
|
52
|
+
outrank-cli marketplace seller content-history <product-id> [--status <status>] [--limit <n>] [--offset <n>]`;
|
|
53
|
+
|
|
54
|
+
const ELIGIBILITY_USAGE = `Usage:
|
|
55
|
+
outrank-cli marketplace seller eligibility <product-id> [--refresh]
|
|
56
|
+
outrank-cli marketplace seller eligibility recheck <product-id>`;
|
|
57
|
+
const SETTINGS_USAGE = `Usage:
|
|
58
|
+
outrank-cli marketplace seller settings get <product-id>
|
|
59
|
+
outrank-cli marketplace seller settings update <product-id> --json '<object>'`;
|
|
60
|
+
const INTEGRATIONS_USAGE = `Usage:
|
|
61
|
+
outrank-cli marketplace seller integrations status <product-id>
|
|
62
|
+
outrank-cli marketplace seller integrations adopt <product-id>
|
|
63
|
+
outrank-cli marketplace seller integrations claim <product-id> --integration <integration-id> --type <platform>
|
|
64
|
+
outrank-cli marketplace seller integrations set-activation <product-id> --integration <integration-id> --type <platform> --status active|disabled
|
|
65
|
+
outrank-cli marketplace seller integrations delete <product-id> --integration <integration-id> --type <platform>`;
|
|
66
|
+
const STRIPE_USAGE = `Usage:
|
|
67
|
+
outrank-cli marketplace seller stripe onboard <product-id> --country <ISO2>
|
|
68
|
+
outrank-cli marketplace seller stripe manage <product-id>
|
|
69
|
+
outrank-cli marketplace seller stripe reset <product-id>`;
|
|
70
|
+
const PLACEMENTS_USAGE = `Usage:
|
|
71
|
+
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>]
|
|
72
|
+
outrank-cli marketplace seller placements approve <placement-id>
|
|
73
|
+
outrank-cli marketplace seller placements decline <placement-id>
|
|
74
|
+
outrank-cli marketplace seller placements publish <placement-id>
|
|
75
|
+
outrank-cli marketplace seller placements regenerate <placement-id>`;
|
|
76
|
+
|
|
77
|
+
const DECLINE_ACTION = 'decline';
|
|
78
|
+
const SELLER_GUIDANCE =
|
|
79
|
+
'Choose a seller subcommand: status, onboard, eligibility, settings, integrations, stripe, complete-onboarding, ' +
|
|
80
|
+
'placements, earnings, content-history.';
|
|
81
|
+
|
|
82
|
+
const INTEGRATION_PLACEHOLDERS = { integrationId: '<integration-id>', integrationType: '<platform>' };
|
|
83
|
+
const INTEGRATION_CLI = {
|
|
84
|
+
...PRODUCT_CLI,
|
|
85
|
+
options: { integration: 'integrationId', type: 'integrationType' },
|
|
86
|
+
placeholders: INTEGRATION_PLACEHOLDERS,
|
|
87
|
+
};
|
|
88
|
+
const integrationOptions = () => ({
|
|
89
|
+
integrationId: uuid().required(REQUIRED_OPTION),
|
|
90
|
+
integrationType: string().required(REQUIRED_OPTION),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const sellerGet = ({ id, summary, suffix, kind = CommandKind.READ, entryPoint = false, mcp = true }) =>
|
|
94
|
+
defineCommand({
|
|
95
|
+
id: `marketplace.seller.${id}`,
|
|
96
|
+
summary,
|
|
97
|
+
kind,
|
|
98
|
+
toolsets: TOOLSETS,
|
|
99
|
+
mcp,
|
|
100
|
+
input: productInput(),
|
|
101
|
+
cli: PRODUCT_CLI,
|
|
102
|
+
entryPoint,
|
|
103
|
+
run: (input, ctx) => ctx.http({ method: 'GET', path: `${SELLER_PATH}${suffix}`, pathParams: input }),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const sellerPost = ({ id, summary, suffix, kind = CommandKind.WRITE, entryPoint = false, body }) =>
|
|
107
|
+
defineCommand({
|
|
108
|
+
id: `marketplace.seller.${id}`,
|
|
109
|
+
summary,
|
|
110
|
+
kind,
|
|
111
|
+
toolsets: TOOLSETS,
|
|
112
|
+
input: productInput(),
|
|
113
|
+
cli: PRODUCT_CLI,
|
|
114
|
+
entryPoint,
|
|
115
|
+
run: (input, ctx) => ctx.http({ method: 'POST', path: `${SELLER_PATH}${suffix}`, pathParams: input, body }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const status = defineCommand({
|
|
119
|
+
id: 'marketplace.seller.status',
|
|
120
|
+
summary: 'Show the seller onboarding and enrollment status of a product.',
|
|
121
|
+
kind: CommandKind.READ,
|
|
122
|
+
toolsets: TOOLSETS,
|
|
123
|
+
input: { ...productInput(), refreshStripe: bool({ serialize: BoolSerialize.STRING }) },
|
|
124
|
+
cli: { ...PRODUCT_CLI, options: { 'refresh-stripe': 'refreshStripe' } },
|
|
125
|
+
entryPoint: true,
|
|
126
|
+
run: (input, ctx) =>
|
|
127
|
+
ctx.http({
|
|
128
|
+
method: 'GET',
|
|
129
|
+
path: `${SELLER_PATH}/status`,
|
|
130
|
+
pathParams: input,
|
|
131
|
+
query: { refresh_stripe: input.refreshStripe ? TRUE_TEXT : undefined },
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const quoteServerText = (text, fallback) => {
|
|
136
|
+
const value = typeof text === 'string' && text.trim() ? text.trim() : fallback;
|
|
137
|
+
const clipped = value.length > MAX_SERVER_MESSAGE_CHARS ? `${value.slice(0, MAX_SERVER_MESSAGE_CHARS)}…` : value;
|
|
138
|
+
return `server said: "${clipped.replaceAll('"', "'")}"`;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const safeOnboardingUrl = (value) => {
|
|
142
|
+
if (typeof value !== 'string') return null;
|
|
143
|
+
try {
|
|
144
|
+
const url = new URL(value);
|
|
145
|
+
return url.protocol === HTTPS_PROTOCOL && url.hostname === STRIPE_ONBOARDING_HOST ? url.toString() : null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const stepFailure = (step, completedSteps, actions, error) =>
|
|
152
|
+
cliError(
|
|
153
|
+
error?.cliCode ?? CliErrorCode.UNEXPECTED_ERROR,
|
|
154
|
+
error?.message ?? 'Unexpected CLI failure.',
|
|
155
|
+
error?.guidance ?? `The ${step} call failed; the steps that already succeeded are listed in error.details.`,
|
|
156
|
+
{ step, completed_steps: completedSteps, actions, ...(error?.details ?? {}) },
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const describeRemainingSteps = ({ ctx, productId, country, flags }) => {
|
|
160
|
+
const remaining = [];
|
|
161
|
+
const ref = (id, args) => ctx.ref(id, args);
|
|
162
|
+
const stripeOnboard = (extra = {}) => ref('marketplace.seller.stripe.onboard', { productId, ...extra });
|
|
163
|
+
|
|
164
|
+
if (!flags.gscConnected) {
|
|
165
|
+
remaining.push(
|
|
166
|
+
`Blog step: collect the product's content details from the human (${ref('products.get', { productId })} / ` +
|
|
167
|
+
`${ref('products.update', { productId })}) and connect Google Search Console (${ref('products.gsc.connect', {
|
|
168
|
+
productId,
|
|
169
|
+
})}, then ${ref('products.gsc.poll', { productId })} and ${ref('products.gsc.set-property', {
|
|
170
|
+
productId,
|
|
171
|
+
})}) — required before anything else`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
remaining.push(
|
|
175
|
+
`Articles step: review article settings (${ref('products.output-settings.get', { productId })} / ` +
|
|
176
|
+
`${ref('products.output-settings.update', { productId })}) and set the publishing schedule ` +
|
|
177
|
+
`(${ref('marketplace.seller.settings.update', { productId })} with dailyArticleLimit 1-3 and publishingDays)`,
|
|
178
|
+
);
|
|
179
|
+
if (!flags.cmsConnected) {
|
|
180
|
+
const integrationsStatus = ref('marketplace.seller.integrations.status', { productId });
|
|
181
|
+
remaining.push(
|
|
182
|
+
flags.adoptFailureMessage
|
|
183
|
+
? `Integration step: adopting the existing CMS failed (${flags.adoptFailureMessage}) — run ` +
|
|
184
|
+
`${integrationsStatus}, connect a new supported platform via the products integrations commands, then ` +
|
|
185
|
+
'claim it for the marketplace'
|
|
186
|
+
: `Integration step: no reusable CMS was found — run ${integrationsStatus}, connect a supported platform via ` +
|
|
187
|
+
'the products integrations commands, then claim it for the marketplace',
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (!flags.stripeAccountConnected) {
|
|
191
|
+
if (flags.stripeFailureMessage) {
|
|
192
|
+
remaining.push(
|
|
193
|
+
`Payouts step: Stripe Express onboarding failed (${flags.stripeFailureMessage}) — resolve that, then run ` +
|
|
194
|
+
stripeOnboard({ country }),
|
|
195
|
+
);
|
|
196
|
+
} else if (country) {
|
|
197
|
+
remaining.push(
|
|
198
|
+
`Payouts step: Stripe Express could not be created yet — finish the earlier steps, then run ${stripeOnboard({
|
|
199
|
+
country,
|
|
200
|
+
})}`,
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
remaining.push(
|
|
204
|
+
'Payouts step: ask the human for the two-letter country of their payout bank account, then run ' +
|
|
205
|
+
`${stripeOnboard({ country: '<ISO2>' })} (or re-run this command with the country)`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
} else if (flags.stripeOnboardingUrl) {
|
|
209
|
+
remaining.push(
|
|
210
|
+
`Payouts step: the Stripe Express account is ready — the human must open ${flags.stripeOnboardingUrl} in a ` +
|
|
211
|
+
'browser and finish identity and bank details',
|
|
212
|
+
);
|
|
213
|
+
} else if (!flags.payoutsEnabled) {
|
|
214
|
+
remaining.push(
|
|
215
|
+
`Payouts step: Stripe onboarding looks unfinished — get a fresh link with ${stripeOnboard(
|
|
216
|
+
country ? { country } : {},
|
|
217
|
+
)} and have the human complete it`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
remaining.push(
|
|
221
|
+
`Finish with ${ref('marketplace.seller.complete-onboarding', { productId })}, then re-check ` +
|
|
222
|
+
`${ref('marketplace.seller.status', { productId })}`,
|
|
223
|
+
);
|
|
224
|
+
return remaining;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const runOnboard = async (input, ctx) => {
|
|
228
|
+
const { productId, country } = input;
|
|
229
|
+
const completedSteps = [];
|
|
230
|
+
const actions = [];
|
|
231
|
+
const recordAction = (action, envelope) =>
|
|
232
|
+
actions.push({ action, ok: Boolean(envelope.ok), result: envelope.ok ? envelope.data : envelope.error });
|
|
233
|
+
const call = async (step, request) => {
|
|
234
|
+
try {
|
|
235
|
+
const { result } = await ctx.request(request);
|
|
236
|
+
if (result.ok) completedSteps.push(step);
|
|
237
|
+
return result;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return { failure: stepFailure(step, [...completedSteps], [...actions], error) };
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const statusResult = await call('status', {
|
|
244
|
+
method: 'GET',
|
|
245
|
+
path: `${SELLER_PATH}/status`,
|
|
246
|
+
pathParams: { productId },
|
|
247
|
+
});
|
|
248
|
+
if (statusResult.failure) return statusResult.failure;
|
|
249
|
+
if (!statusResult.ok) return statusResult;
|
|
250
|
+
|
|
251
|
+
const sellerStatus = statusResult.data ?? {};
|
|
252
|
+
if (sellerStatus.enrollment?.completed) {
|
|
253
|
+
return cliSuccess(
|
|
254
|
+
{ status: sellerStatus, actions },
|
|
255
|
+
`Seller onboarding is already complete for this product. ${statusResult.agent_guidance ?? ''}`.trim(),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const flags = {
|
|
260
|
+
cmsConnected: Boolean(sellerStatus.onboarding?.cms?.connected),
|
|
261
|
+
gscConnected: Boolean(sellerStatus.onboarding?.gsc?.connected),
|
|
262
|
+
stripeAccountConnected: Boolean(sellerStatus.onboarding?.stripe?.accountConnected),
|
|
263
|
+
payoutsEnabled: Boolean(sellerStatus.onboarding?.stripe?.payoutsEnabled),
|
|
264
|
+
stripeOnboardingUrl: null,
|
|
265
|
+
adoptFailureMessage: null,
|
|
266
|
+
stripeFailureMessage: null,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (!flags.cmsConnected && sellerStatus.onboarding?.cms?.existing) {
|
|
270
|
+
const adopt = await call('adopt-existing-integration', {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
path: `${SELLER_PATH}/integrations/adopt`,
|
|
273
|
+
pathParams: { productId },
|
|
274
|
+
});
|
|
275
|
+
if (adopt.failure) return adopt.failure;
|
|
276
|
+
recordAction('adopt-existing-integration', adopt);
|
|
277
|
+
if (adopt.ok) flags.cmsConnected = true;
|
|
278
|
+
else flags.adoptFailureMessage = quoteServerText(adopt.error?.message, ADOPT_FALLBACK_MESSAGE);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!flags.stripeAccountConnected && flags.gscConnected && flags.cmsConnected && country) {
|
|
282
|
+
const stripe = await call('stripe-express-onboard', {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
path: `${STRIPE_PATH}/onboard`,
|
|
285
|
+
pathParams: { productId },
|
|
286
|
+
body: { country },
|
|
287
|
+
});
|
|
288
|
+
if (stripe.failure) return stripe.failure;
|
|
289
|
+
recordAction('stripe-express-onboard', stripe);
|
|
290
|
+
if (stripe.ok) {
|
|
291
|
+
flags.stripeAccountConnected = true;
|
|
292
|
+
flags.stripeOnboardingUrl = safeOnboardingUrl(stripe.data?.onboardingUrl);
|
|
293
|
+
} else {
|
|
294
|
+
flags.stripeFailureMessage = quoteServerText(stripe.error?.message, STRIPE_FALLBACK_MESSAGE);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const eligibilityResult = await call('eligibility', {
|
|
299
|
+
method: 'GET',
|
|
300
|
+
path: `${SELLER_PATH}/eligibility`,
|
|
301
|
+
pathParams: { productId },
|
|
302
|
+
query: { refresh: TRUE_TEXT },
|
|
303
|
+
});
|
|
304
|
+
if (eligibilityResult.failure) return eligibilityResult.failure;
|
|
305
|
+
const eligibility = eligibilityResult.ok ? eligibilityResult.data : null;
|
|
306
|
+
const eligibilityNote = eligibilityResult.agent_guidance
|
|
307
|
+
? ` Eligibility (${quoteServerText(eligibilityResult.agent_guidance, '')}).`
|
|
308
|
+
: '';
|
|
309
|
+
|
|
310
|
+
const remaining = describeRemainingSteps({ ctx, productId, country, flags });
|
|
311
|
+
const actionsNote =
|
|
312
|
+
actions.length > 0
|
|
313
|
+
? `Automatic actions taken: ${actions
|
|
314
|
+
.map((entry) => `${entry.action} (${entry.ok ? 'ok' : 'failed'})`)
|
|
315
|
+
.join(', ')}. `
|
|
316
|
+
: '';
|
|
317
|
+
return cliSuccess(
|
|
318
|
+
{ status: sellerStatus, actions, eligibility },
|
|
319
|
+
`Seller onboarding in progress (wizard order: Blog -> Articles -> Integration -> Payouts). ${actionsNote}` +
|
|
320
|
+
`Steps to complete with the human: ${remaining.join('; ')}.${eligibilityNote} Always report the eligibility ` +
|
|
321
|
+
'check results to the human.',
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const onboard = defineCommand({
|
|
326
|
+
id: 'marketplace.seller.onboard',
|
|
327
|
+
summary: 'Run the seller onboarding orchestrator: status, auto-adopt CMS, Stripe Express, eligibility.',
|
|
328
|
+
kind: CommandKind.WRITE,
|
|
329
|
+
toolsets: TOOLSETS,
|
|
330
|
+
input: { ...productInput(), country: string() },
|
|
331
|
+
cli: PRODUCT_CLI,
|
|
332
|
+
entryPoint: true,
|
|
333
|
+
run: runOnboard,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const eligibilityGroup = defineGroup({
|
|
337
|
+
id: 'marketplace.seller.eligibility',
|
|
338
|
+
help: {
|
|
339
|
+
usage: ELIGIBILITY_USAGE,
|
|
340
|
+
guidance: 'Run without a subcommand to read eligibility, or `recheck` to recompute it.',
|
|
341
|
+
},
|
|
342
|
+
unknown: unknownFor('seller eligibility'),
|
|
343
|
+
command: {
|
|
344
|
+
summary: 'Read the marketplace seller eligibility of a product.',
|
|
345
|
+
kind: CommandKind.READ,
|
|
346
|
+
toolsets: TOOLSETS,
|
|
347
|
+
entryPoint: true,
|
|
348
|
+
input: { ...productInput(), refresh: bool({ serialize: BoolSerialize.STRING }) },
|
|
349
|
+
cli: PRODUCT_CLI,
|
|
350
|
+
run: (input, ctx) =>
|
|
351
|
+
ctx.http({
|
|
352
|
+
method: 'GET',
|
|
353
|
+
path: `${SELLER_PATH}/eligibility`,
|
|
354
|
+
pathParams: input,
|
|
355
|
+
query: { refresh: input.refresh ? TRUE_TEXT : undefined },
|
|
356
|
+
}),
|
|
357
|
+
},
|
|
358
|
+
children: [
|
|
359
|
+
sellerPost({
|
|
360
|
+
id: 'eligibility.recheck',
|
|
361
|
+
summary: 'Recompute the marketplace seller eligibility of a product.',
|
|
362
|
+
suffix: '/eligibility/recheck',
|
|
363
|
+
entryPoint: true,
|
|
364
|
+
}),
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const settingsGroup = defineGroup({
|
|
369
|
+
id: 'marketplace.seller.settings',
|
|
370
|
+
help: { usage: SETTINGS_USAGE, guidance: 'Use `get` to read the seller settings and `update` to patch them.' },
|
|
371
|
+
unknown: unknownFor('seller settings'),
|
|
372
|
+
children: [
|
|
373
|
+
sellerGet({
|
|
374
|
+
id: 'settings.get',
|
|
375
|
+
summary: 'Get the marketplace seller settings of a product.',
|
|
376
|
+
suffix: '/settings',
|
|
377
|
+
}),
|
|
378
|
+
defineCommand({
|
|
379
|
+
id: 'marketplace.seller.settings.update',
|
|
380
|
+
summary: 'Update the marketplace seller settings of a product from a JSON body (admin only).',
|
|
381
|
+
kind: CommandKind.WRITE,
|
|
382
|
+
toolsets: TOOLSETS,
|
|
383
|
+
input: productInput(),
|
|
384
|
+
cli: PRODUCT_CLI,
|
|
385
|
+
body: BodyMode.JSON,
|
|
386
|
+
run: (input, ctx) =>
|
|
387
|
+
ctx.http({ method: 'PATCH', path: `${SELLER_PATH}/settings`, pathParams: input, body: input.body }),
|
|
388
|
+
}),
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const integrationsGroup = defineGroup({
|
|
393
|
+
id: 'marketplace.seller.integrations',
|
|
394
|
+
help: {
|
|
395
|
+
usage: INTEGRATIONS_USAGE,
|
|
396
|
+
guidance:
|
|
397
|
+
'Use `status` to see reusable CMS integrations, `adopt` or `claim` to attach one, and the writes are admin ' +
|
|
398
|
+
'only.',
|
|
399
|
+
},
|
|
400
|
+
unknown: unknownFor('seller integrations'),
|
|
401
|
+
children: [
|
|
402
|
+
sellerGet({
|
|
403
|
+
id: 'integrations.status',
|
|
404
|
+
summary: 'Show the marketplace CMS integration status of a product.',
|
|
405
|
+
suffix: '/integrations',
|
|
406
|
+
entryPoint: true,
|
|
407
|
+
}),
|
|
408
|
+
sellerPost({
|
|
409
|
+
id: 'integrations.adopt',
|
|
410
|
+
summary: 'Adopt the existing reusable CMS integration for the marketplace (admin only).',
|
|
411
|
+
suffix: '/integrations/adopt',
|
|
412
|
+
}),
|
|
413
|
+
defineCommand({
|
|
414
|
+
id: 'marketplace.seller.integrations.claim',
|
|
415
|
+
summary: 'Claim a publishing integration for the marketplace (admin only).',
|
|
416
|
+
kind: CommandKind.WRITE,
|
|
417
|
+
toolsets: TOOLSETS,
|
|
418
|
+
input: { ...productInput(), ...integrationOptions() },
|
|
419
|
+
cli: INTEGRATION_CLI,
|
|
420
|
+
run: (input, ctx) =>
|
|
421
|
+
ctx.http({
|
|
422
|
+
method: 'POST',
|
|
423
|
+
path: `${SELLER_PATH}/integrations/claim`,
|
|
424
|
+
pathParams: input,
|
|
425
|
+
body: { integrationId: input.integrationId, integrationType: input.integrationType },
|
|
426
|
+
}),
|
|
427
|
+
}),
|
|
428
|
+
defineCommand({
|
|
429
|
+
id: 'marketplace.seller.integrations.set-activation',
|
|
430
|
+
summary: 'Activate or disable a marketplace CMS integration (admin only).',
|
|
431
|
+
kind: CommandKind.WRITE,
|
|
432
|
+
toolsets: TOOLSETS,
|
|
433
|
+
input: { ...productInput(), ...integrationOptions(), activationStatus: string().required(REQUIRED_OPTION) },
|
|
434
|
+
cli: {
|
|
435
|
+
...INTEGRATION_CLI,
|
|
436
|
+
options: { ...INTEGRATION_CLI.options, status: 'activationStatus' },
|
|
437
|
+
placeholders: { ...INTEGRATION_PLACEHOLDERS, activationStatus: 'active|disabled' },
|
|
438
|
+
},
|
|
439
|
+
run: (input, ctx) =>
|
|
440
|
+
ctx.http({
|
|
441
|
+
method: 'PATCH',
|
|
442
|
+
path: SELLER_INTEGRATION_PATH,
|
|
443
|
+
pathParams: input,
|
|
444
|
+
body: { integrationType: input.integrationType, activationStatus: input.activationStatus },
|
|
445
|
+
}),
|
|
446
|
+
}),
|
|
447
|
+
defineCommand({
|
|
448
|
+
id: 'marketplace.seller.integrations.delete',
|
|
449
|
+
summary: 'Remove a marketplace CMS integration (admin only).',
|
|
450
|
+
kind: CommandKind.DESTRUCTIVE,
|
|
451
|
+
toolsets: TOOLSETS,
|
|
452
|
+
input: { ...productInput(), ...integrationOptions() },
|
|
453
|
+
cli: INTEGRATION_CLI,
|
|
454
|
+
run: (input, ctx) =>
|
|
455
|
+
ctx.http({
|
|
456
|
+
method: 'DELETE',
|
|
457
|
+
path: SELLER_INTEGRATION_PATH,
|
|
458
|
+
pathParams: input,
|
|
459
|
+
query: { integration_type: input.integrationType },
|
|
460
|
+
}),
|
|
461
|
+
}),
|
|
462
|
+
],
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const stripeGroup = defineGroup({
|
|
466
|
+
id: 'marketplace.seller.stripe',
|
|
467
|
+
help: {
|
|
468
|
+
usage: STRIPE_USAGE,
|
|
469
|
+
guidance:
|
|
470
|
+
'Use `onboard` to create or refresh the Stripe Express onboarding link, `manage` for the dashboard link, ' +
|
|
471
|
+
'`reset` to start over. Admin only.',
|
|
472
|
+
},
|
|
473
|
+
unknown: unknownFor('seller stripe'),
|
|
474
|
+
children: [
|
|
475
|
+
defineCommand({
|
|
476
|
+
id: 'marketplace.seller.stripe.onboard',
|
|
477
|
+
summary: 'Create the Stripe Express payout account or refresh its onboarding link (admin only).',
|
|
478
|
+
kind: CommandKind.WRITE,
|
|
479
|
+
toolsets: TOOLSETS,
|
|
480
|
+
input: { ...productInput(), country: string() },
|
|
481
|
+
cli: PRODUCT_CLI,
|
|
482
|
+
entryPoint: true,
|
|
483
|
+
run: (input, ctx) =>
|
|
484
|
+
ctx.http({
|
|
485
|
+
method: 'POST',
|
|
486
|
+
path: `${STRIPE_PATH}/onboard`,
|
|
487
|
+
pathParams: input,
|
|
488
|
+
body: input.country ? { country: input.country } : {},
|
|
489
|
+
}),
|
|
490
|
+
}),
|
|
491
|
+
sellerPost({
|
|
492
|
+
id: 'stripe.manage',
|
|
493
|
+
summary: 'Get the Stripe Express dashboard link (admin only).',
|
|
494
|
+
suffix: '/stripe-connect/manage',
|
|
495
|
+
kind: CommandKind.READ,
|
|
496
|
+
}),
|
|
497
|
+
sellerPost({
|
|
498
|
+
id: 'stripe.reset',
|
|
499
|
+
summary: 'Reset the Stripe Express payout account (admin only).',
|
|
500
|
+
suffix: '/stripe-connect/reset',
|
|
501
|
+
kind: CommandKind.DESTRUCTIVE,
|
|
502
|
+
}),
|
|
503
|
+
],
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const placementsGroup = defineGroup({
|
|
507
|
+
id: 'marketplace.seller.placements',
|
|
508
|
+
help: {
|
|
509
|
+
usage: PLACEMENTS_USAGE,
|
|
510
|
+
guidance: 'Use `list` to review placements, then approve, decline (admin only), publish, or regenerate one by id.',
|
|
511
|
+
},
|
|
512
|
+
unknown: unknownFor('seller placements'),
|
|
513
|
+
children: [
|
|
514
|
+
defineCommand({
|
|
515
|
+
id: 'marketplace.seller.placements.list',
|
|
516
|
+
summary: 'List marketplace placements hosted by a product. Placement briefs are counterparty data.',
|
|
517
|
+
kind: CommandKind.READ,
|
|
518
|
+
toolsets: TOOLSETS,
|
|
519
|
+
mcp: { untrustedOutput: true },
|
|
520
|
+
input: { ...productInput(), ...listFilters() },
|
|
521
|
+
cli: PRODUCT_CLI,
|
|
522
|
+
run: (input, ctx) =>
|
|
523
|
+
ctx.http({
|
|
524
|
+
method: 'GET',
|
|
525
|
+
path: `${SELLER_PATH}/placements`,
|
|
526
|
+
pathParams: input,
|
|
527
|
+
query: listFilterQuery(input),
|
|
528
|
+
}),
|
|
529
|
+
}),
|
|
530
|
+
...['approve', DECLINE_ACTION, 'publish', 'regenerate'].map((action) =>
|
|
531
|
+
defineCommand({
|
|
532
|
+
id: `marketplace.seller.placements.${action}`,
|
|
533
|
+
summary: `${action[0].toUpperCase()}${action.slice(1)} a marketplace placement.`,
|
|
534
|
+
kind: action === DECLINE_ACTION ? CommandKind.DESTRUCTIVE : CommandKind.WRITE,
|
|
535
|
+
toolsets: TOOLSETS,
|
|
536
|
+
input: placementInput(),
|
|
537
|
+
cli: PLACEMENT_CLI,
|
|
538
|
+
run: (input, ctx) =>
|
|
539
|
+
ctx.http({ method: 'POST', path: PLACEMENT_ACTION_PATH, pathParams: { ...input, action } }),
|
|
540
|
+
}),
|
|
541
|
+
),
|
|
542
|
+
],
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const contentHistory = defineCommand({
|
|
546
|
+
id: 'marketplace.seller.content-history',
|
|
547
|
+
summary: 'List the marketplace articles a product has hosted.',
|
|
548
|
+
kind: CommandKind.READ,
|
|
549
|
+
toolsets: TOOLSETS,
|
|
550
|
+
input: {
|
|
551
|
+
...productInput(),
|
|
552
|
+
status: string(),
|
|
553
|
+
limit: int({ min: LIST_MIN_LIMIT, max: LIST_MAX_LIMIT }),
|
|
554
|
+
offset: int({ min: MIN_OFFSET }),
|
|
555
|
+
},
|
|
556
|
+
cli: PRODUCT_CLI,
|
|
557
|
+
run: (input, ctx) =>
|
|
558
|
+
ctx.http({
|
|
559
|
+
method: 'GET',
|
|
560
|
+
path: '/articles',
|
|
561
|
+
query: {
|
|
562
|
+
product_id: input.productId,
|
|
563
|
+
track_scope: MARKETPLACE_TRACK_SCOPE,
|
|
564
|
+
status: input.status,
|
|
565
|
+
limit: input.limit,
|
|
566
|
+
offset: input.offset,
|
|
567
|
+
},
|
|
568
|
+
}),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
export const sellerGroup = defineGroup({
|
|
572
|
+
id: 'marketplace.seller',
|
|
573
|
+
help: { usage: SELLER_USAGE, guidance: SELLER_GUIDANCE },
|
|
574
|
+
unknown: (word) => ({
|
|
575
|
+
message: `Unknown marketplace seller subcommand: ${word}.`,
|
|
576
|
+
guidance: MARKETPLACE_UNKNOWN_GUIDANCE,
|
|
577
|
+
}),
|
|
578
|
+
children: [
|
|
579
|
+
status,
|
|
580
|
+
onboard,
|
|
581
|
+
eligibilityGroup,
|
|
582
|
+
settingsGroup,
|
|
583
|
+
integrationsGroup,
|
|
584
|
+
stripeGroup,
|
|
585
|
+
sellerPost({
|
|
586
|
+
id: 'complete-onboarding',
|
|
587
|
+
summary: 'Mark seller onboarding as complete (admin only).',
|
|
588
|
+
suffix: '/complete-onboarding',
|
|
589
|
+
entryPoint: true,
|
|
590
|
+
}),
|
|
591
|
+
placementsGroup,
|
|
592
|
+
sellerGet({ id: 'earnings', summary: 'Show marketplace earnings for a product.', suffix: '/earnings' }),
|
|
593
|
+
contentHistory,
|
|
594
|
+
],
|
|
595
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LIST_MAX_LIMIT, LIST_MIN_LIMIT } from '../../core/constants.js';
|
|
2
|
+
import { int, string, uuid } from '../../core/schema.js';
|
|
3
|
+
|
|
4
|
+
export const MARKETPLACE_UNKNOWN_GUIDANCE =
|
|
5
|
+
'Run `outrank-cli marketplace --help` to see the available marketplace commands.';
|
|
6
|
+
|
|
7
|
+
export const PRODUCT_CLI = { positionals: ['productId'] };
|
|
8
|
+
export const PLACEMENT_CLI = { positionals: ['placementId'] };
|
|
9
|
+
|
|
10
|
+
export const productInput = () => ({ productId: uuid().required() });
|
|
11
|
+
export const placementInput = () => ({ placementId: uuid().required() });
|
|
12
|
+
|
|
13
|
+
export const pageInput = () => ({
|
|
14
|
+
page: int({ min: LIST_MIN_LIMIT }),
|
|
15
|
+
pageSize: int({ min: LIST_MIN_LIMIT, max: LIST_MAX_LIMIT }),
|
|
16
|
+
});
|
|
17
|
+
export const pageQuery = (input) => ({ page: input.page, page_size: input.pageSize });
|
|
18
|
+
|
|
19
|
+
export const REQUIRED_OPTION = Object.freeze({ guidance: MARKETPLACE_UNKNOWN_GUIDANCE });
|
|
20
|
+
|
|
21
|
+
export const listFilters = () => ({ status: string(), search: string(), ...pageInput() });
|
|
22
|
+
export const listFilterQuery = (input) => ({ status: input.status, search: input.search, ...pageQuery(input) });
|
|
23
|
+
|
|
24
|
+
export const unknownFor = (scope) => (word) => ({
|
|
25
|
+
message: `Unknown marketplace ${scope} subcommand: ${word}.`,
|
|
26
|
+
guidance: MARKETPLACE_UNKNOWN_GUIDANCE,
|
|
27
|
+
});
|