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.
Files changed (58) hide show
  1. package/README.md +317 -20
  2. package/SKILL.md +20 -2
  3. package/bin/outrank-mcp.js +3 -0
  4. package/package.json +21 -5
  5. package/src/adapters/cli/jsonInput.js +42 -0
  6. package/src/adapters/cli/parse.js +124 -0
  7. package/src/adapters/cli/run.js +139 -0
  8. package/src/adapters/mcp/constants.js +49 -0
  9. package/src/adapters/mcp/descriptions.js +87 -0
  10. package/src/adapters/mcp/help.js +34 -0
  11. package/src/adapters/mcp/input.js +195 -0
  12. package/src/adapters/mcp/instructions.js +139 -0
  13. package/src/adapters/mcp/logger.js +45 -0
  14. package/src/adapters/mcp/main.js +36 -0
  15. package/src/adapters/mcp/options.js +118 -0
  16. package/src/adapters/mcp/result.js +33 -0
  17. package/src/adapters/mcp/server.js +174 -0
  18. package/src/adapters/mcp/session.js +274 -0
  19. package/src/adapters/mcp/toolNotes.js +426 -0
  20. package/src/adapters/mcp/toolsets.js +106 -0
  21. package/src/commands/articles.js +285 -301
  22. package/src/commands/auth.js +204 -86
  23. package/src/commands/billing.js +102 -104
  24. package/src/commands/improvements.js +319 -211
  25. package/src/commands/index.js +22 -0
  26. package/src/commands/keywords.js +266 -249
  27. package/src/commands/marketplace/buyer.js +386 -0
  28. package/src/commands/marketplace/index.js +83 -0
  29. package/src/commands/marketplace/notifications.js +60 -0
  30. package/src/commands/marketplace/seller.js +595 -0
  31. package/src/commands/marketplace/shared.js +27 -0
  32. package/src/commands/products/backlinks.js +181 -0
  33. package/src/commands/products/core.js +149 -0
  34. package/src/commands/products/gsc.js +250 -0
  35. package/src/commands/products/index.js +174 -0
  36. package/src/commands/products/integrations.js +343 -0
  37. package/src/commands/products/linking.js +192 -0
  38. package/src/commands/products/shared.js +8 -0
  39. package/src/commands/registry.js +272 -0
  40. package/src/commands/subscription.js +20 -30
  41. package/src/commands/usage.js +17 -30
  42. package/src/core/config.js +243 -62
  43. package/src/core/constants.js +101 -0
  44. package/src/core/context.js +311 -0
  45. package/src/core/errors.js +39 -0
  46. package/src/core/execute.js +71 -0
  47. package/src/core/files.js +186 -0
  48. package/src/core/http.js +393 -0
  49. package/src/core/profileSelection.js +7 -0
  50. package/src/core/query.js +11 -0
  51. package/src/core/redact.js +27 -0
  52. package/src/core/schema.js +222 -0
  53. package/src/core/version.js +12 -0
  54. package/src/index.js +30 -58
  55. package/src/commands/marketplace.js +0 -740
  56. package/src/commands/products.js +0 -1185
  57. package/src/core/api.js +0 -82
  58. package/src/core/args.js +0 -144
@@ -1,1185 +0,0 @@
1
- import { parseArgs, getRequiredPositional, readJsonInput, hasFlag, parseIntegerOption, getOption } from '../core/args.js';
2
- import { requestAgentApi } from '../core/api.js';
3
- import { cliSuccess } from '../core/errors.js';
4
-
5
- const BACKLINK_LIST_LIMIT_RANGE = {
6
- min: 1,
7
- max: 100,
8
- };
9
-
10
- export const PRODUCTS_HELP = `Usage:
11
- outrank-cli products list [--status active|disabled] [--limit <n>] [--cursor <iso-timestamp>]
12
- outrank-cli products get <product-id>
13
- outrank-cli products create --json <object>
14
- outrank-cli products update <product-id> --json <object>
15
- outrank-cli products pause <product-id>
16
- outrank-cli products resume <product-id>
17
- outrank-cli products integrations providers
18
- outrank-cli products integrations prepare <provider> <product-id> [--json <object>]
19
- outrank-cli products integrations connect <provider> <product-id> [--json <object>]
20
- outrank-cli products integrations poll <provider> <product-id> --attempt-id <attempt-id>
21
- outrank-cli products integrations notion databases <product-id> <integration-id>
22
- outrank-cli products integrations notion schema <product-id> <integration-id> <database-id>
23
- outrank-cli products integrations framer collections --json <object>
24
- outrank-cli products integrations framer fields --json <object>
25
- outrank-cli products integrations webflow sites --json <object>
26
- outrank-cli products integrations webflow collections --json <object>
27
- outrank-cli products integrations webflow fields --json <object>
28
- outrank-cli products integrations webflow items --json <object>
29
- outrank-cli products integrations validate <provider> --json <object>
30
- outrank-cli products integrations list <product-id>
31
- outrank-cli products integrations create <product-id> --json <object>
32
- outrank-cli products integrations get <product-id> <integration-id>
33
- outrank-cli products integrations update <product-id> <integration-id> --json <object>
34
- outrank-cli products integrations test <product-id> <integration-id>
35
- outrank-cli products integrations delete <product-id> <integration-id>
36
- outrank-cli products output-settings get <product-id>
37
- outrank-cli products output-settings update <product-id> --json <object>
38
- outrank-cli products target-audiences list <product-id>
39
- outrank-cli products target-audiences replace <product-id> --json <object>
40
- outrank-cli products competitors list <product-id>
41
- outrank-cli products competitors replace <product-id> --json <object>
42
- outrank-cli products gsc status <product-id>
43
- outrank-cli products gsc connect <product-id>
44
- outrank-cli products gsc poll <product-id> --attempt-id <attempt-id>
45
- outrank-cli products gsc reconnect <product-id>
46
- outrank-cli products gsc disconnect <product-id>
47
- outrank-cli products gsc accounts <product-id>
48
- outrank-cli products gsc properties <product-id> --email <email>
49
- outrank-cli products gsc set-property <product-id> --email <email> --site-url <url>
50
- outrank-cli products gsc performance <product-id> [--months <n>]
51
- outrank-cli products gsc search-analytics <product-id> [--dimensions <csv>] [--limit <n>] [--months <n>] [--start-date <yyyy-mm-dd>] [--end-date <yyyy-mm-dd>] [--page <text>] [--query <text>] [--country <cc>] [--device <device>]
52
- outrank-cli products gsc inspect-url <product-id> --url <url> [--language-code <code>]
53
- outrank-cli products gsc cannibalization <product-id>
54
- outrank-cli products linking sources get <product-id>
55
- outrank-cli products linking sources update <product-id> --json <object>
56
- outrank-cli products linking detect <product-id>
57
- outrank-cli products linking target-config get <product-id>
58
- outrank-cli products linking target-config update <product-id> --json <object>
59
- outrank-cli products linking targets list <product-id>
60
- outrank-cli products linking targets add-manual <product-id> --json <object>
61
- outrank-cli products linking targets upsert <product-id> --json <object>
62
- outrank-cli products linking targets clear-customizations <product-id> <target-id>
63
- outrank-cli products linking targets delete <product-id> <target-id>
64
- outrank-cli products backlinks get <product-id>
65
- outrank-cli products backlinks update <product-id> --json <object>
66
- outrank-cli products backlinks credits <product-id>
67
- outrank-cli products backlinks earned <product-id> [--limit <n>] [--cursor <iso>] [--status <status>] [--sort-by created_at|status|credits_applied] [--sort-order desc] [--include-article] [--include-summary]
68
- outrank-cli products backlinks unverified <product-id> [--limit <n>] [--cursor <iso>] [--status <status>] [--sort-by created_at|verification_error_reason] [--sort-order desc]
69
- outrank-cli products backlinks check-removal <product-id> <backlink-id>
70
- outrank-cli products backlinks reverify <product-id> <backlink-id>
71
- outrank-cli products backlinks subscription <product-id>
72
- outrank-cli products backlinks subscribe-quote <product-id> --plan premium|enterprise
73
-
74
- JSON Input:
75
- Use one of: --json '<object>', --json-file payload.json, or --json-stdin
76
-
77
- Examples:
78
- outrank-cli products list --limit 20
79
- outrank-cli products create --json-file product.json
80
- outrank-cli products update 00000000-0000-0000-0000-000000000000 --json '{"daily_article_count":2}'
81
- outrank-cli products integrations providers
82
- outrank-cli products integrations prepare wordpress-plugin 00000000-0000-0000-0000-000000000000
83
- outrank-cli products integrations connect wordpress-com 00000000-0000-0000-0000-000000000000 --json '{"name":"Main WordPress.com","publish_status":"publish"}'
84
- outrank-cli products integrations connect notion 00000000-0000-0000-0000-000000000000
85
- outrank-cli products integrations poll wordpress-com 00000000-0000-0000-0000-000000000000 --attempt-id <attempt-id>
86
- outrank-cli products integrations notion databases 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
87
- outrank-cli products integrations notion schema 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
88
- outrank-cli products integrations framer collections --json '{"project_url":"https://framer.com/projects/your-project","server_api_key":"framer_server_key"}'
89
- outrank-cli products integrations framer fields --json '{"project_url":"https://framer.com/projects/your-project","server_api_key":"framer_server_key","collection_id":"collection_id"}'
90
- outrank-cli products integrations webflow sites --json-file webflow-sites.json
91
- outrank-cli products integrations webflow collections --json '{"api_token":"wf_token","site_id":"site_id"}'
92
- outrank-cli products integrations webflow fields --json '{"api_token":"wf_token","collection_id":"collection_id"}'
93
- outrank-cli products integrations webflow items --json '{"api_token":"wf_token","collection_id":"collection_id"}'
94
- outrank-cli products integrations validate webhook --json '{"endpoint":"https://api.example.com/webhook","access_token":"secret"}'
95
- outrank-cli products integrations validate framer --json '{"project_url":"https://framer.com/projects/your-project","server_api_key":"framer_server_key"}'
96
- outrank-cli products integrations validate webflow --json '{"api_token":"wf_token"}'
97
- outrank-cli products integrations validate shopify --json '{"access_token":"shpat_xxx"}'
98
- outrank-cli products integrations validate wix --json '{"site_id":"00000000-0000-0000-0000-000000000000","api_key":"wix_api_key"}'
99
- outrank-cli products integrations validate wordpress-plugin --json '{"domain":"https://example.com","api_key":"secret-token"}'
100
- outrank-cli products integrations list 00000000-0000-0000-0000-000000000000
101
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"webhook","name":"Main Webhook","endpoint":"https://api.example.com/webhook","access_token":"secret"}'
102
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"framer","name":"Main Framer","project_url":"https://framer.com/projects/your-project","server_api_key":"framer_server_key","author_name":"Outrank AI","publish_mode":"deploy","collection_setup_mode":"create"}'
103
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"shopify","name":"Main Shopify","access_token":"shpat_xxx","blog_id":"123456789","author_name":"Outrank","publish_status":"publish"}'
104
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"wix","name":"Main Wix","site_id":"00000000-0000-0000-0000-000000000000","api_key":"wix_api_key","publish_status":"publish"}'
105
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json-file webflow-create.json
106
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"wordpress-plugin","domain":"https://example.com","api_key":"secret-token"}'
107
- outrank-cli products integrations create 00000000-0000-0000-0000-000000000000 --json '{"provider":"nextjs-blog"}'
108
- outrank-cli products integrations get 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
109
- outrank-cli products integrations update 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 --json '{"endpoint":"https://api.example.com/webhook-v2"}'
110
- outrank-cli products integrations update 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 --json '{"blog_id":"987654321","author_name":"Editorial Team","publish_status":"draft"}'
111
- outrank-cli products integrations update 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 --json '{"publish_status":"draft"}'
112
- outrank-cli products integrations update 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 --json-file webflow-update.json
113
- outrank-cli products integrations update 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 --json '{"domain":"https://example.com/blog","api_key":"new-secret-token"}'
114
- outrank-cli products integrations test 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
115
- outrank-cli products output-settings update 00000000-0000-0000-0000-000000000000 --json-file output-settings.json
116
- outrank-cli products target-audiences replace 00000000-0000-0000-0000-000000000000 --json-file target-audiences.json
117
- outrank-cli products competitors replace 00000000-0000-0000-0000-000000000000 --json-file competitors.json
118
- outrank-cli products gsc connect 00000000-0000-0000-0000-000000000000
119
- outrank-cli products gsc properties 00000000-0000-0000-0000-000000000000 --email owner@example.com
120
- outrank-cli products gsc performance 00000000-0000-0000-0000-000000000000 --months 3
121
- outrank-cli products gsc inspect-url 00000000-0000-0000-0000-000000000000 --url https://example.com/blog/post
122
- outrank-cli products linking sources get 00000000-0000-0000-0000-000000000000
123
- outrank-cli products linking sources update 00000000-0000-0000-0000-000000000000 --json-file linking-sources.json
124
- outrank-cli products linking detect 00000000-0000-0000-0000-000000000000
125
- outrank-cli products linking target-config update 00000000-0000-0000-0000-000000000000 --json '{"only_prioritized":true}'
126
- outrank-cli products linking targets add-manual 00000000-0000-0000-0000-000000000000 --json-file manual-target.json
127
- outrank-cli products linking targets clear-customizations 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
128
- outrank-cli products backlinks get 00000000-0000-0000-0000-000000000000
129
- outrank-cli products backlinks update 00000000-0000-0000-0000-000000000000 --json '{"enabled":true,"min_domain_rating":10}'
130
- outrank-cli products backlinks credits 00000000-0000-0000-0000-000000000000
131
- outrank-cli products backlinks earned 00000000-0000-0000-0000-000000000000 --limit 20 --include-article
132
- outrank-cli products backlinks unverified 00000000-0000-0000-0000-000000000000 --limit 20
133
- outrank-cli products backlinks check-removal 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
134
- outrank-cli products backlinks reverify 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
135
- outrank-cli products backlinks subscription 00000000-0000-0000-0000-000000000000
136
- outrank-cli products backlinks subscribe-quote 00000000-0000-0000-0000-000000000000 --plan premium
137
- `;
138
-
139
- const buildQueryPath = (basePath, searchParams) => {
140
- const url = new URL(`https://local${basePath}`);
141
- for (const [key, value] of Object.entries(searchParams)) {
142
- if (value !== undefined && value !== null && value !== '') {
143
- url.searchParams.set(key, String(value));
144
- }
145
- }
146
- return `${url.pathname}${url.search}`;
147
- };
148
-
149
- const handleProductsList = async (parsed) => {
150
- const path = buildQueryPath('/products', {
151
- status: getOption(parsed, 'status'),
152
- limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 100 }),
153
- cursor: getOption(parsed, 'cursor'),
154
- });
155
- const response = await requestAgentApi(parsed, {
156
- method: 'GET',
157
- path,
158
- });
159
- return response.payload;
160
- };
161
-
162
- const handleProductsGet = async (parsed) => {
163
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
164
- const response = await requestAgentApi(parsed, {
165
- method: 'GET',
166
- path: `/products/${productId}`,
167
- });
168
- return response.payload;
169
- };
170
-
171
- const handleProductsCreate = async (parsed) => {
172
- const body = await readJsonInput(parsed);
173
- const response = await requestAgentApi(parsed, {
174
- method: 'POST',
175
- path: '/products',
176
- body,
177
- });
178
- return response.payload;
179
- };
180
-
181
- const handleProductsUpdate = async (parsed) => {
182
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
183
- const body = await readJsonInput(parsed);
184
- const response = await requestAgentApi(parsed, {
185
- method: 'PATCH',
186
- path: `/products/${productId}`,
187
- body,
188
- });
189
- return response.payload;
190
- };
191
-
192
- const handleProductsPause = async (parsed) => {
193
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
194
- const response = await requestAgentApi(parsed, {
195
- method: 'POST',
196
- path: `/products/${productId}/pause`,
197
- });
198
- return response.payload;
199
- };
200
-
201
- const handleProductsResume = async (parsed) => {
202
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
203
- const response = await requestAgentApi(parsed, {
204
- method: 'POST',
205
- path: `/products/${productId}/resume`,
206
- });
207
- return response.payload;
208
- };
209
-
210
- const handleBacklinks = async (argv) => {
211
- const subcommand = argv[0];
212
- const parsed = parseArgs(argv.slice(1));
213
- const wantsHelp =
214
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
215
-
216
- if (wantsHelp) {
217
- return cliSuccess(
218
- {
219
- usage: `Usage:
220
- outrank-cli products backlinks get <product-id>
221
- outrank-cli products backlinks update <product-id> --json <object>
222
- outrank-cli products backlinks credits <product-id>
223
- outrank-cli products backlinks earned <product-id> [--limit <n>] [--cursor <iso>] [--status <status>] [--sort-by created_at|status|credits_applied] [--sort-order desc] [--include-article] [--include-summary]
224
- outrank-cli products backlinks unverified <product-id> [--limit <n>] [--cursor <iso>] [--status <status>] [--sort-by created_at|verification_error_reason] [--sort-order desc]
225
- outrank-cli products backlinks check-removal <product-id> <backlink-id>
226
- outrank-cli products backlinks reverify <product-id> <backlink-id>
227
- outrank-cli products backlinks subscription <product-id>
228
- outrank-cli products backlinks subscribe-quote <product-id> --plan premium|enterprise`,
229
- },
230
- 'Use `get` and `update` for product backlink settings, `credits` and `subscription` for status, `earned` and `unverified` for review queues, and the action commands for verification follow-up.',
231
- );
232
- }
233
-
234
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
235
- const basePath = `/products/${productId}/backlinks`;
236
-
237
- if (subcommand === 'get') {
238
- const response = await requestAgentApi(parsed, {
239
- method: 'GET',
240
- path: basePath,
241
- });
242
- return response.payload;
243
- }
244
-
245
- if (subcommand === 'update') {
246
- const body = await readJsonInput(parsed);
247
- const response = await requestAgentApi(parsed, {
248
- method: 'PATCH',
249
- path: basePath,
250
- body,
251
- });
252
- return response.payload;
253
- }
254
-
255
- if (subcommand === 'credits') {
256
- const response = await requestAgentApi(parsed, {
257
- method: 'GET',
258
- path: `${basePath}/credits`,
259
- });
260
- return response.payload;
261
- }
262
-
263
- if (subcommand === 'earned') {
264
- const path = buildQueryPath(`${basePath}/earned`, {
265
- limit: parseIntegerOption(parsed, 'limit', BACKLINK_LIST_LIMIT_RANGE),
266
- cursor: getOption(parsed, 'cursor'),
267
- status: getOption(parsed, 'status'),
268
- sort_by: getOption(parsed, 'sort-by'),
269
- sort_order: getOption(parsed, 'sort-order'),
270
- include_article: hasFlag(parsed, 'include-article') ? true : undefined,
271
- include_summary: hasFlag(parsed, 'include-summary') ? true : undefined,
272
- });
273
- const response = await requestAgentApi(parsed, {
274
- method: 'GET',
275
- path,
276
- });
277
- return response.payload;
278
- }
279
-
280
- if (subcommand === 'unverified') {
281
- const path = buildQueryPath(`${basePath}/unverified`, {
282
- limit: parseIntegerOption(parsed, 'limit', BACKLINK_LIST_LIMIT_RANGE),
283
- cursor: getOption(parsed, 'cursor'),
284
- status: getOption(parsed, 'status'),
285
- sort_by: getOption(parsed, 'sort-by'),
286
- sort_order: getOption(parsed, 'sort-order'),
287
- });
288
- const response = await requestAgentApi(parsed, {
289
- method: 'GET',
290
- path,
291
- });
292
- return response.payload;
293
- }
294
-
295
- if (subcommand === 'check-removal') {
296
- const backlinkId = getRequiredPositional(parsed, 1, '<backlink-id>');
297
- const response = await requestAgentApi(parsed, {
298
- method: 'POST',
299
- path: `${basePath}/${backlinkId}/check-removal`,
300
- });
301
- return response.payload;
302
- }
303
-
304
- if (subcommand === 'reverify') {
305
- const backlinkId = getRequiredPositional(parsed, 1, '<backlink-id>');
306
- const response = await requestAgentApi(parsed, {
307
- method: 'POST',
308
- path: `${basePath}/${backlinkId}/reverify`,
309
- });
310
- return response.payload;
311
- }
312
-
313
- if (subcommand === 'subscription') {
314
- const response = await requestAgentApi(parsed, {
315
- method: 'GET',
316
- path: `${basePath}/subscription`,
317
- });
318
- return response.payload;
319
- }
320
-
321
- if (subcommand === 'subscribe-quote') {
322
- const plan = getOption(parsed, 'plan');
323
-
324
- if (!plan) {
325
- throw Object.assign(new Error('Missing required option: --plan premium|enterprise.'), {
326
- cliCode: 'CLI_INVALID_USAGE',
327
- guidance:
328
- 'Run `outrank-cli products backlinks subscribe-quote <product-id> --plan premium|enterprise` to inspect the dashboard-equivalent quote.',
329
- });
330
- }
331
-
332
- const response = await requestAgentApi(parsed, {
333
- method: 'GET',
334
- path: buildQueryPath(`${basePath}/subscribe-quote`, {
335
- plan,
336
- }),
337
- });
338
- return response.payload;
339
- }
340
-
341
- throw Object.assign(new Error(`Unknown products backlinks subcommand: ${subcommand}.`), {
342
- cliCode: 'CLI_INVALID_USAGE',
343
- guidance: 'Run `outrank-cli products backlinks --help` to see the available backlink commands.',
344
- });
345
- };
346
-
347
- const handleOutputSettings = async (argv) => {
348
- const subcommand = argv[0];
349
- const parsed = parseArgs(argv.slice(1));
350
- const wantsHelp = !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
351
-
352
- if (wantsHelp) {
353
- return cliSuccess(
354
- {
355
- usage:
356
- 'Usage:\n outrank-cli products output-settings get <product-id>\n outrank-cli products output-settings update <product-id> --json <object>',
357
- },
358
- 'Use `get` to inspect current article settings and `update` to patch them.',
359
- );
360
- }
361
-
362
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
363
-
364
- if (subcommand === 'get') {
365
- const response = await requestAgentApi(parsed, {
366
- method: 'GET',
367
- path: `/products/${productId}/output-settings`,
368
- });
369
- return response.payload;
370
- }
371
-
372
- if (subcommand === 'update') {
373
- const body = await readJsonInput(parsed);
374
- const response = await requestAgentApi(parsed, {
375
- method: 'PATCH',
376
- path: `/products/${productId}/output-settings`,
377
- body,
378
- });
379
- return response.payload;
380
- }
381
-
382
- throw Object.assign(new Error(`Unknown products output-settings subcommand: ${subcommand}.`), {
383
- cliCode: 'CLI_INVALID_USAGE',
384
- guidance:
385
- 'Run `outrank-cli products output-settings --help` to see the available output-settings commands.',
386
- });
387
- };
388
-
389
- const handleIntegrations = async (argv) => {
390
- const subcommand = argv[0];
391
- const parsed = parseArgs(argv.slice(1));
392
- const wantsHelp =
393
- !subcommand ||
394
- subcommand === '--help' ||
395
- subcommand === '-h' ||
396
- subcommand === 'help' ||
397
- ((subcommand !== 'notion' && subcommand !== 'webflow' && subcommand !== 'framer') && hasFlag(parsed, 'help'));
398
-
399
- if (wantsHelp) {
400
- return cliSuccess(
401
- {
402
- usage: `Usage:
403
- outrank-cli products integrations providers
404
- outrank-cli products integrations prepare <provider> <product-id> [--json <object>]
405
- outrank-cli products integrations connect <provider> <product-id> [--json <object>]
406
- outrank-cli products integrations poll <provider> <product-id> --attempt-id <attempt-id>
407
- outrank-cli products integrations notion databases <product-id> <integration-id>
408
- outrank-cli products integrations notion schema <product-id> <integration-id> <database-id>
409
- outrank-cli products integrations framer collections --json <object>
410
- outrank-cli products integrations framer fields --json <object>
411
- outrank-cli products integrations webflow sites --json <object>
412
- outrank-cli products integrations webflow collections --json <object>
413
- outrank-cli products integrations webflow fields --json <object>
414
- outrank-cli products integrations webflow items --json <object>
415
- outrank-cli products integrations validate <provider> --json <object>
416
- outrank-cli products integrations list <product-id>
417
- outrank-cli products integrations create <product-id> --json <object>
418
- outrank-cli products integrations get <product-id> <integration-id>
419
- outrank-cli products integrations update <product-id> <integration-id> --json <object>
420
- outrank-cli products integrations test <product-id> <integration-id>
421
- outrank-cli products integrations delete <product-id> <integration-id>`,
422
- },
423
- 'Use `providers` to inspect known provider availability, auth method, required connect/create fields, and provider notes. For WordPress Plugin, start with `prepare` to generate the integration token, then validate and create. For WordPress.com and Notion, use `connect`, have the human open the returned connect_url, then `poll` until the OAuth handoff completes. Notion exposes `notion databases` and `notion schema` discovery commands. Framer exposes `framer collections` and `framer fields` discovery for the newer Server API-based setup, not the older plugin flow. Webflow exposes `webflow sites`, `webflow collections`, `webflow fields`, and `webflow items` discovery before create or update. Shopify validation returns the resolved `shop_name` plus available blogs, so the human should only need to provide the access token before blog selection. Wix is a direct validate/create/update/test flow using `site_id`, `api_key`, and `publish_status`. Next.js Blog is a one-step create flow that returns a one-time API key in the response. V2 products are intended to keep one publishing integration; multiple rows usually indicate legacy cleanup work.',
424
- );
425
- }
426
-
427
- if (subcommand === 'providers') {
428
- const response = await requestAgentApi(parsed, {
429
- method: 'GET',
430
- path: '/integration-providers',
431
- });
432
- return response.payload;
433
- }
434
-
435
- if (subcommand === 'prepare') {
436
- const provider = getRequiredPositional(parsed, 0, '<provider>');
437
- const productId = getRequiredPositional(parsed, 1, '<product-id>');
438
- const hasJsonInput =
439
- getOption(parsed, 'json') !== undefined ||
440
- getOption(parsed, 'json-file') !== undefined ||
441
- hasFlag(parsed, 'json-stdin');
442
- const body = hasJsonInput ? await readJsonInput(parsed) : undefined;
443
- const response = await requestAgentApi(parsed, {
444
- method: 'POST',
445
- path: `/products/${productId}/integrations/prepare/${provider}`,
446
- ...(body !== undefined ? { body } : {}),
447
- });
448
- return response.payload;
449
- }
450
-
451
- if (subcommand === 'connect') {
452
- const provider = getRequiredPositional(parsed, 0, '<provider>');
453
- const productId = getRequiredPositional(parsed, 1, '<product-id>');
454
- const hasJsonInput =
455
- getOption(parsed, 'json') !== undefined ||
456
- getOption(parsed, 'json-file') !== undefined ||
457
- hasFlag(parsed, 'json-stdin');
458
- const body = hasJsonInput ? await readJsonInput(parsed) : undefined;
459
- const response = await requestAgentApi(parsed, {
460
- method: 'POST',
461
- path: `/products/${productId}/integrations/connect/${provider}`,
462
- ...(body !== undefined ? { body } : {}),
463
- });
464
- return response.payload;
465
- }
466
-
467
- if (subcommand === 'poll') {
468
- const provider = getRequiredPositional(parsed, 0, '<provider>');
469
- const productId = getRequiredPositional(parsed, 1, '<product-id>');
470
- const attemptId = getOption(parsed, 'attempt-id');
471
-
472
- if (!attemptId) {
473
- throw Object.assign(new Error('Missing required option: --attempt-id <attempt-id>.'), {
474
- cliCode: 'CLI_INVALID_USAGE',
475
- guidance:
476
- 'Run `outrank-cli products integrations poll <provider> <product-id> --attempt-id <attempt-id>` after connect starts.',
477
- });
478
- }
479
-
480
- const response = await requestAgentApi(parsed, {
481
- method: 'GET',
482
- path: buildQueryPath(`/products/${productId}/integrations/connect/${provider}`, {
483
- attempt_id: attemptId,
484
- }),
485
- });
486
- return response.payload;
487
- }
488
-
489
- if (subcommand === 'notion') {
490
- const notionSubcommand = argv[1];
491
- const notionParsed = parseArgs(argv.slice(2));
492
-
493
- if (!notionSubcommand || notionSubcommand === '--help' || notionSubcommand === '-h' || notionSubcommand === 'help') {
494
- return cliSuccess(
495
- {
496
- usage: `Usage:
497
- outrank-cli products integrations notion databases <product-id> <integration-id>
498
- outrank-cli products integrations notion schema <product-id> <integration-id> <database-id>`,
499
- },
500
- 'Use `databases` after Notion OAuth completes to list accessible Notion databases, then use `schema` to inspect one database before PATCHing the saved integration row with name, database_id, and fields.',
501
- );
502
- }
503
-
504
- const productId = getRequiredPositional(notionParsed, 0, '<product-id>');
505
- const integrationId = getRequiredPositional(notionParsed, 1, '<integration-id>');
506
-
507
- if (notionSubcommand === 'databases') {
508
- const response = await requestAgentApi(notionParsed, {
509
- method: 'GET',
510
- path: `/products/${productId}/integrations/${integrationId}/notion/databases`,
511
- });
512
- return response.payload;
513
- }
514
-
515
- if (notionSubcommand === 'schema') {
516
- const databaseId = getRequiredPositional(notionParsed, 2, '<database-id>');
517
- const response = await requestAgentApi(notionParsed, {
518
- method: 'GET',
519
- path: `/products/${productId}/integrations/${integrationId}/notion/databases/${databaseId}/schema`,
520
- });
521
- return response.payload;
522
- }
523
-
524
- throw Object.assign(new Error(`Unknown products integrations notion subcommand: ${notionSubcommand}.`), {
525
- cliCode: 'CLI_INVALID_USAGE',
526
- guidance:
527
- 'Run `outrank-cli products integrations notion --help` to see the available Notion discovery commands.',
528
- });
529
- }
530
-
531
- if (subcommand === 'webflow') {
532
- const webflowSubcommand = argv[1];
533
- const webflowParsed = parseArgs(argv.slice(2));
534
-
535
- if (!webflowSubcommand || webflowSubcommand === '--help' || webflowSubcommand === '-h' || webflowSubcommand === 'help') {
536
- return cliSuccess(
537
- {
538
- usage: `Usage:
539
- outrank-cli products integrations webflow sites --json <object>
540
- outrank-cli products integrations webflow collections --json <object>
541
- outrank-cli products integrations webflow fields --json <object>
542
- outrank-cli products integrations webflow items --json <object>`,
543
- },
544
- 'Use `sites` first to validate a Webflow token and list accessible sites, then `collections`, `fields`, and `items` to build the exact create or update payload for the chosen CMS collection.',
545
- );
546
- }
547
-
548
- const body = await readJsonInput(webflowParsed);
549
- const response = await requestAgentApi(webflowParsed, {
550
- method: 'POST',
551
- path: `/integration-providers/webflow/discover/${webflowSubcommand}`,
552
- body,
553
- });
554
- return response.payload;
555
- }
556
-
557
- if (subcommand === 'framer') {
558
- const framerSubcommand = argv[1];
559
- const framerParsed = parseArgs(argv.slice(2));
560
-
561
- if (!framerSubcommand || framerSubcommand === '--help' || framerSubcommand === '-h' || framerSubcommand === 'help') {
562
- return cliSuccess(
563
- {
564
- usage: `Usage:
565
- outrank-cli products integrations framer collections --json <object>
566
- outrank-cli products integrations framer fields --json <object>`,
567
- },
568
- 'Use `collections` first with the Framer project URL and Server API key. Then use `fields` for one chosen collection if you want `collection_setup_mode: "existing"` instead of auto-creating a new collection.',
569
- );
570
- }
571
-
572
- const body = await readJsonInput(framerParsed);
573
- const response = await requestAgentApi(framerParsed, {
574
- method: 'POST',
575
- path: `/integration-providers/framer/discover/${framerSubcommand}`,
576
- body,
577
- });
578
- return response.payload;
579
- }
580
-
581
- if (subcommand === 'validate') {
582
- const provider = getRequiredPositional(parsed, 0, '<provider>');
583
- const body = await readJsonInput(parsed);
584
- const response = await requestAgentApi(parsed, {
585
- method: 'POST',
586
- path: `/integration-providers/${provider}/validate`,
587
- body,
588
- });
589
- return response.payload;
590
- }
591
-
592
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
593
-
594
- if (subcommand === 'list') {
595
- const response = await requestAgentApi(parsed, {
596
- method: 'GET',
597
- path: `/products/${productId}/integrations`,
598
- });
599
- return response.payload;
600
- }
601
-
602
- if (subcommand === 'create') {
603
- const body = await readJsonInput(parsed);
604
- const response = await requestAgentApi(parsed, {
605
- method: 'POST',
606
- path: `/products/${productId}/integrations`,
607
- body,
608
- });
609
- return response.payload;
610
- }
611
-
612
- if (subcommand === 'get') {
613
- const integrationId = getRequiredPositional(parsed, 1, '<integration-id>');
614
- const response = await requestAgentApi(parsed, {
615
- method: 'GET',
616
- path: `/products/${productId}/integrations/${integrationId}`,
617
- });
618
- return response.payload;
619
- }
620
-
621
- if (subcommand === 'update') {
622
- const integrationId = getRequiredPositional(parsed, 1, '<integration-id>');
623
- const body = await readJsonInput(parsed);
624
- const response = await requestAgentApi(parsed, {
625
- method: 'PATCH',
626
- path: `/products/${productId}/integrations/${integrationId}`,
627
- body,
628
- });
629
- return response.payload;
630
- }
631
-
632
- if (subcommand === 'test') {
633
- const integrationId = getRequiredPositional(parsed, 1, '<integration-id>');
634
- const response = await requestAgentApi(parsed, {
635
- method: 'POST',
636
- path: `/products/${productId}/integrations/${integrationId}/test`,
637
- });
638
- return response.payload;
639
- }
640
-
641
- if (subcommand === 'delete') {
642
- const integrationId = getRequiredPositional(parsed, 1, '<integration-id>');
643
- const response = await requestAgentApi(parsed, {
644
- method: 'DELETE',
645
- path: `/products/${productId}/integrations/${integrationId}`,
646
- });
647
- return response.payload;
648
- }
649
-
650
- throw Object.assign(new Error(`Unknown products integrations subcommand: ${subcommand}.`), {
651
- cliCode: 'CLI_INVALID_USAGE',
652
- guidance:
653
- 'Run `outrank-cli products integrations --help` to see the available integration commands.',
654
- });
655
- };
656
-
657
- const handleCompetitors = async (argv) => {
658
- const subcommand = argv[0];
659
- const parsed = parseArgs(argv.slice(1));
660
- const wantsHelp =
661
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
662
-
663
- if (wantsHelp) {
664
- return cliSuccess(
665
- {
666
- usage:
667
- 'Usage:\n outrank-cli products competitors list <product-id>\n outrank-cli products competitors replace <product-id> --json <object>',
668
- },
669
- 'Use `list` to inspect the competitor list and `replace` to submit the full replacement set.',
670
- );
671
- }
672
-
673
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
674
-
675
- if (subcommand === 'list') {
676
- const response = await requestAgentApi(parsed, {
677
- method: 'GET',
678
- path: `/products/${productId}/competitors`,
679
- });
680
- return response.payload;
681
- }
682
-
683
- if (subcommand === 'replace') {
684
- const body = await readJsonInput(parsed);
685
- const response = await requestAgentApi(parsed, {
686
- method: 'PUT',
687
- path: `/products/${productId}/competitors`,
688
- body,
689
- });
690
- return response.payload;
691
- }
692
-
693
- throw Object.assign(new Error(`Unknown products competitors subcommand: ${subcommand}.`), {
694
- cliCode: 'CLI_INVALID_USAGE',
695
- guidance:
696
- 'Run `outrank-cli products competitors --help` to see the available competitor commands.',
697
- });
698
- };
699
-
700
- const handleTargetAudiences = async (argv) => {
701
- const subcommand = argv[0];
702
- const parsed = parseArgs(argv.slice(1));
703
- const wantsHelp =
704
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
705
-
706
- if (wantsHelp) {
707
- return cliSuccess(
708
- {
709
- usage:
710
- 'Usage:\n outrank-cli products target-audiences list <product-id>\n outrank-cli products target-audiences replace <product-id> --json <object>',
711
- },
712
- 'Use `list` to inspect the target-audience list and `replace` to submit the full replacement set.',
713
- );
714
- }
715
-
716
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
717
-
718
- if (subcommand === 'list') {
719
- const response = await requestAgentApi(parsed, {
720
- method: 'GET',
721
- path: `/products/${productId}/target-audiences`,
722
- });
723
- return response.payload;
724
- }
725
-
726
- if (subcommand === 'replace') {
727
- const body = await readJsonInput(parsed);
728
- const response = await requestAgentApi(parsed, {
729
- method: 'PUT',
730
- path: `/products/${productId}/target-audiences`,
731
- body,
732
- });
733
- return response.payload;
734
- }
735
-
736
- throw Object.assign(new Error(`Unknown products target-audiences subcommand: ${subcommand}.`), {
737
- cliCode: 'CLI_INVALID_USAGE',
738
- guidance:
739
- 'Run `outrank-cli products target-audiences --help` to see the available target-audience commands.',
740
- });
741
- };
742
-
743
- const handleGoogleSearchConsole = async (argv) => {
744
- const subcommand = argv[0];
745
- const parsed = parseArgs(argv.slice(1));
746
- const wantsHelp =
747
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
748
-
749
- if (wantsHelp) {
750
- return cliSuccess(
751
- {
752
- usage: `Usage:
753
- outrank-cli products gsc status <product-id>
754
- outrank-cli products gsc connect <product-id>
755
- outrank-cli products gsc poll <product-id> --attempt-id <attempt-id>
756
- outrank-cli products gsc reconnect <product-id>
757
- outrank-cli products gsc disconnect <product-id>
758
- outrank-cli products gsc accounts <product-id>
759
- outrank-cli products gsc properties <product-id> --email <email>
760
- outrank-cli products gsc set-property <product-id> --email <email> --site-url <url>
761
- outrank-cli products gsc performance <product-id> [--months <n>]
762
- outrank-cli products gsc search-analytics <product-id> [--dimensions <csv>] [--limit <n>] [--months <n>] [--start-date <yyyy-mm-dd>] [--end-date <yyyy-mm-dd>] [--page <text>] [--query <text>] [--country <cc>] [--device <device>]
763
- outrank-cli products gsc inspect-url <product-id> --url <url> [--language-code <code>]
764
- outrank-cli products gsc cannibalization <product-id>`,
765
- },
766
- 'Use connect plus poll for the OAuth handoff, then inspect accounts, properties, performance, indexing, and keyword data from the same subtree.',
767
- );
768
- }
769
-
770
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
771
- const basePath = `/products/${productId}/gsc`;
772
-
773
- if (subcommand === 'status') {
774
- const response = await requestAgentApi(parsed, {
775
- method: 'GET',
776
- path: basePath,
777
- });
778
- return response.payload;
779
- }
780
-
781
- if (subcommand === 'connect') {
782
- const response = await requestAgentApi(parsed, {
783
- method: 'POST',
784
- path: `${basePath}/connect`,
785
- });
786
- return response.payload;
787
- }
788
-
789
- if (subcommand === 'poll') {
790
- const attemptId = getOption(parsed, 'attempt-id');
791
-
792
- if (!attemptId) {
793
- throw Object.assign(new Error('Missing required option: --attempt-id <attempt-id>.'), {
794
- cliCode: 'CLI_INVALID_USAGE',
795
- guidance:
796
- 'Run `outrank-cli products gsc poll <product-id> --attempt-id <attempt-id>` after connect starts.',
797
- });
798
- }
799
-
800
- const response = await requestAgentApi(parsed, {
801
- method: 'GET',
802
- path: buildQueryPath(`${basePath}/connect`, {
803
- attempt_id: attemptId,
804
- }),
805
- });
806
- return response.payload;
807
- }
808
-
809
- if (subcommand === 'reconnect') {
810
- const response = await requestAgentApi(parsed, {
811
- method: 'POST',
812
- path: `${basePath}/reconnect`,
813
- });
814
- return response.payload;
815
- }
816
-
817
- if (subcommand === 'disconnect') {
818
- const response = await requestAgentApi(parsed, {
819
- method: 'DELETE',
820
- path: basePath,
821
- });
822
- return response.payload;
823
- }
824
-
825
- if (subcommand === 'accounts') {
826
- const response = await requestAgentApi(parsed, {
827
- method: 'GET',
828
- path: `${basePath}/accounts`,
829
- });
830
- return response.payload;
831
- }
832
-
833
- if (subcommand === 'properties') {
834
- const email = getOption(parsed, 'email');
835
-
836
- if (!email) {
837
- throw Object.assign(new Error('Missing required option: --email <email>.'), {
838
- cliCode: 'CLI_INVALID_USAGE',
839
- guidance:
840
- 'Run `outrank-cli products gsc properties <product-id> --email <email>` to list properties for a connected Google account.',
841
- });
842
- }
843
-
844
- const response = await requestAgentApi(parsed, {
845
- method: 'GET',
846
- path: buildQueryPath(`${basePath}/properties`, {
847
- email,
848
- }),
849
- });
850
- return response.payload;
851
- }
852
-
853
- if (subcommand === 'set-property') {
854
- const email = getOption(parsed, 'email');
855
- const siteUrl = getOption(parsed, 'site-url');
856
-
857
- if (!email || !siteUrl) {
858
- throw Object.assign(new Error('Missing required options: --email <email> and --site-url <url>.'), {
859
- cliCode: 'CLI_INVALID_USAGE',
860
- guidance:
861
- 'Run `outrank-cli products gsc set-property <product-id> --email <email> --site-url <url>` to save the verified property.',
862
- });
863
- }
864
-
865
- const response = await requestAgentApi(parsed, {
866
- method: 'PUT',
867
- path: `${basePath}/property`,
868
- body: {
869
- email,
870
- site_url: siteUrl,
871
- },
872
- });
873
- return response.payload;
874
- }
875
-
876
- if (subcommand === 'performance') {
877
- const path = buildQueryPath(`${basePath}/performance`, {
878
- months: parseIntegerOption(parsed, 'months', { min: 1, max: 16 }),
879
- });
880
- const response = await requestAgentApi(parsed, {
881
- method: 'GET',
882
- path,
883
- });
884
- return response.payload;
885
- }
886
-
887
- if (subcommand === 'search-analytics') {
888
- const path = buildQueryPath(`${basePath}/search-analytics`, {
889
- dimensions: getOption(parsed, 'dimensions'),
890
- limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 1000 }),
891
- months: parseIntegerOption(parsed, 'months', { min: 1, max: 16 }),
892
- start_date: getOption(parsed, 'start-date'),
893
- end_date: getOption(parsed, 'end-date'),
894
- page: getOption(parsed, 'page'),
895
- query: getOption(parsed, 'query'),
896
- country: getOption(parsed, 'country'),
897
- device: getOption(parsed, 'device'),
898
- });
899
- const response = await requestAgentApi(parsed, {
900
- method: 'GET',
901
- path,
902
- });
903
- return response.payload;
904
- }
905
-
906
- if (subcommand === 'inspect-url') {
907
- const url = getOption(parsed, 'url');
908
-
909
- if (!url) {
910
- throw Object.assign(new Error('Missing required option: --url <url>.'), {
911
- cliCode: 'CLI_INVALID_USAGE',
912
- guidance:
913
- 'Run `outrank-cli products gsc inspect-url <product-id> --url <url>` to inspect indexing details for one page.',
914
- });
915
- }
916
-
917
- const response = await requestAgentApi(parsed, {
918
- method: 'POST',
919
- path: `${basePath}/url-inspection`,
920
- body: {
921
- inspection_url: url,
922
- ...(getOption(parsed, 'language-code') ? { language_code: getOption(parsed, 'language-code') } : {}),
923
- },
924
- });
925
- return response.payload;
926
- }
927
-
928
- if (subcommand === 'cannibalization') {
929
- const response = await requestAgentApi(parsed, {
930
- method: 'GET',
931
- path: `${basePath}/cannibalization`,
932
- });
933
- return response.payload;
934
- }
935
-
936
- throw Object.assign(new Error(`Unknown products gsc subcommand: ${subcommand}.`), {
937
- cliCode: 'CLI_INVALID_USAGE',
938
- guidance:
939
- 'Run `outrank-cli products gsc --help` to see the available Google Search Console commands.',
940
- });
941
- };
942
-
943
- const handleLinkingSources = async (argv) => {
944
- const subcommand = argv[0];
945
- const parsed = parseArgs(argv.slice(1));
946
- const wantsHelp =
947
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
948
-
949
- if (wantsHelp) {
950
- return cliSuccess(
951
- {
952
- usage:
953
- 'Usage:\n outrank-cli products linking sources get <product-id>\n outrank-cli products linking sources update <product-id> --json <object>',
954
- },
955
- 'Use `get` to inspect current linking sources and `update` to change the primary or additional sources.',
956
- );
957
- }
958
-
959
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
960
-
961
- if (subcommand === 'get') {
962
- const response = await requestAgentApi(parsed, {
963
- method: 'GET',
964
- path: `/products/${productId}/linking/sources`,
965
- });
966
- return response.payload;
967
- }
968
-
969
- if (subcommand === 'update' || subcommand === 'set') {
970
- const body = await readJsonInput(parsed);
971
- const response = await requestAgentApi(parsed, {
972
- method: 'PATCH',
973
- path: `/products/${productId}/linking/sources`,
974
- body,
975
- });
976
- return response.payload;
977
- }
978
-
979
- throw Object.assign(new Error(`Unknown products linking sources subcommand: ${subcommand}.`), {
980
- cliCode: 'CLI_INVALID_USAGE',
981
- guidance: 'Run `outrank-cli products linking sources --help` to see available linking-source commands.',
982
- });
983
- };
984
-
985
- const handleLinkingTargetConfig = async (argv) => {
986
- const subcommand = argv[0];
987
- const parsed = parseArgs(argv.slice(1));
988
- const wantsHelp =
989
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
990
-
991
- if (wantsHelp) {
992
- return cliSuccess(
993
- {
994
- usage:
995
- 'Usage:\n outrank-cli products linking target-config get <product-id>\n outrank-cli products linking target-config update <product-id> --json <object>',
996
- },
997
- 'Use `get` to inspect product-wide linking target behavior and `update` to change it.',
998
- );
999
- }
1000
-
1001
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
1002
-
1003
- if (subcommand === 'get') {
1004
- const response = await requestAgentApi(parsed, {
1005
- method: 'GET',
1006
- path: `/products/${productId}/linking/target-config`,
1007
- });
1008
- return response.payload;
1009
- }
1010
-
1011
- if (subcommand === 'update') {
1012
- const body = await readJsonInput(parsed);
1013
- const response = await requestAgentApi(parsed, {
1014
- method: 'PUT',
1015
- path: `/products/${productId}/linking/target-config`,
1016
- body,
1017
- });
1018
- return response.payload;
1019
- }
1020
-
1021
- throw Object.assign(new Error(`Unknown products linking target-config subcommand: ${subcommand}.`), {
1022
- cliCode: 'CLI_INVALID_USAGE',
1023
- guidance:
1024
- 'Run `outrank-cli products linking target-config --help` to see available target-config commands.',
1025
- });
1026
- };
1027
-
1028
- const handleLinkingTargets = async (argv) => {
1029
- const subcommand = argv[0];
1030
- const parsed = parseArgs(argv.slice(1));
1031
- const wantsHelp =
1032
- !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help' || hasFlag(parsed, 'help');
1033
-
1034
- if (wantsHelp) {
1035
- return cliSuccess(
1036
- {
1037
- usage: `Usage:
1038
- outrank-cli products linking targets list <product-id>
1039
- outrank-cli products linking targets add-manual <product-id> --json <object>
1040
- outrank-cli products linking targets upsert <product-id> --json <object>
1041
- outrank-cli products linking targets clear-customizations <product-id> <target-id>
1042
- outrank-cli products linking targets delete <product-id> <target-id>`,
1043
- },
1044
- 'Use `list` to inspect saved targets, `add-manual` or `upsert` to save URLs, and clear/delete for cleanup.',
1045
- );
1046
- }
1047
-
1048
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
1049
-
1050
- if (subcommand === 'list') {
1051
- const response = await requestAgentApi(parsed, {
1052
- method: 'GET',
1053
- path: `/products/${productId}/linking/targets`,
1054
- });
1055
- return response.payload;
1056
- }
1057
-
1058
- if (subcommand === 'add-manual') {
1059
- const body = await readJsonInput(parsed);
1060
- const response = await requestAgentApi(parsed, {
1061
- method: 'POST',
1062
- path: `/products/${productId}/linking/targets/manual`,
1063
- body,
1064
- });
1065
- return response.payload;
1066
- }
1067
-
1068
- if (subcommand === 'upsert') {
1069
- const body = await readJsonInput(parsed);
1070
- const response = await requestAgentApi(parsed, {
1071
- method: 'POST',
1072
- path: `/products/${productId}/linking/targets/upsert`,
1073
- body,
1074
- });
1075
- return response.payload;
1076
- }
1077
-
1078
- if (subcommand === 'clear-customizations') {
1079
- const targetId = getRequiredPositional(parsed, 1, '<target-id>');
1080
- const response = await requestAgentApi(parsed, {
1081
- method: 'PATCH',
1082
- path: `/products/${productId}/linking/targets/${targetId}`,
1083
- });
1084
- return response.payload;
1085
- }
1086
-
1087
- if (subcommand === 'delete') {
1088
- const targetId = getRequiredPositional(parsed, 1, '<target-id>');
1089
- const response = await requestAgentApi(parsed, {
1090
- method: 'DELETE',
1091
- path: `/products/${productId}/linking/targets/${targetId}`,
1092
- });
1093
- return response.payload;
1094
- }
1095
-
1096
- throw Object.assign(new Error(`Unknown products linking targets subcommand: ${subcommand}.`), {
1097
- cliCode: 'CLI_INVALID_USAGE',
1098
- guidance: 'Run `outrank-cli products linking targets --help` to see available linking-target commands.',
1099
- });
1100
- };
1101
-
1102
- const handleLinking = async (argv) => {
1103
- const subcommand = argv[0];
1104
- const parsed = parseArgs(argv.slice(1));
1105
- const wantsHelp = !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help';
1106
-
1107
- if (wantsHelp) {
1108
- return cliSuccess(
1109
- {
1110
- usage: `Usage:
1111
- outrank-cli products linking sources get <product-id>
1112
- outrank-cli products linking sources update <product-id> --json <object>
1113
- outrank-cli products linking detect <product-id>
1114
- outrank-cli products linking target-config get <product-id>
1115
- outrank-cli products linking target-config update <product-id> --json <object>
1116
- outrank-cli products linking targets list <product-id>
1117
- outrank-cli products linking targets add-manual <product-id> --json <object>
1118
- outrank-cli products linking targets upsert <product-id> --json <object>
1119
- outrank-cli products linking targets clear-customizations <product-id> <target-id>
1120
- outrank-cli products linking targets delete <product-id> <target-id>`,
1121
- },
1122
- 'Use `sources` for source selection, `detect` for discovery, `targets` for saved URLs, and `target-config` for product-wide rules.',
1123
- );
1124
- }
1125
-
1126
- if (subcommand === 'sources') {
1127
- return handleLinkingSources(argv.slice(1));
1128
- }
1129
-
1130
- if (subcommand === 'detect') {
1131
- const productId = getRequiredPositional(parsed, 0, '<product-id>');
1132
- const response = await requestAgentApi(parsed, {
1133
- method: 'POST',
1134
- path: `/products/${productId}/linking/detect`,
1135
- });
1136
- return response.payload;
1137
- }
1138
-
1139
- if (subcommand === 'target-config') {
1140
- return handleLinkingTargetConfig(argv.slice(1));
1141
- }
1142
-
1143
- if (subcommand === 'targets') {
1144
- return handleLinkingTargets(argv.slice(1));
1145
- }
1146
-
1147
- throw Object.assign(new Error(`Unknown products linking subcommand: ${subcommand}.`), {
1148
- cliCode: 'CLI_INVALID_USAGE',
1149
- guidance: 'Run `outrank-cli products linking --help` to see available linking commands.',
1150
- });
1151
- };
1152
-
1153
- export const handleProductsCommand = async (argv) => {
1154
- const subcommand = argv[0];
1155
- const parsed = parseArgs(argv.slice(1));
1156
- const wantsHelp = !subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help';
1157
-
1158
- if (wantsHelp) {
1159
- return cliSuccess(
1160
- {
1161
- usage: PRODUCTS_HELP.trim(),
1162
- },
1163
- 'Choose one of: list, get, create, update, pause, resume, integrations, output-settings, target-audiences, competitors, gsc, linking, backlinks.',
1164
- );
1165
- }
1166
-
1167
- if (subcommand === 'list') return handleProductsList(parsed);
1168
- if (subcommand === 'get') return handleProductsGet(parsed);
1169
- if (subcommand === 'create') return handleProductsCreate(parsed);
1170
- if (subcommand === 'update') return handleProductsUpdate(parsed);
1171
- if (subcommand === 'pause') return handleProductsPause(parsed);
1172
- if (subcommand === 'resume') return handleProductsResume(parsed);
1173
- if (subcommand === 'integrations') return handleIntegrations(argv.slice(1));
1174
- if (subcommand === 'output-settings') return handleOutputSettings(argv.slice(1));
1175
- if (subcommand === 'target-audiences') return handleTargetAudiences(argv.slice(1));
1176
- if (subcommand === 'competitors') return handleCompetitors(argv.slice(1));
1177
- if (subcommand === 'gsc') return handleGoogleSearchConsole(argv.slice(1));
1178
- if (subcommand === 'linking') return handleLinking(argv.slice(1));
1179
- if (subcommand === 'backlinks') return handleBacklinks(argv.slice(1));
1180
-
1181
- throw Object.assign(new Error(`Unknown products subcommand: ${subcommand}.`), {
1182
- cliCode: 'CLI_INVALID_USAGE',
1183
- guidance: 'Run `outrank-cli products --help` to see available product commands.',
1184
- });
1185
- };