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
package/src/commands/keywords.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
KEYWORDS_GENERATE_MAX_COUNT,
|
|
3
|
+
KEYWORDS_GENERATE_MIN_COUNT,
|
|
4
|
+
KEYWORDS_SUGGEST_MAX_LIMIT,
|
|
5
|
+
LIST_MAX_LIMIT,
|
|
6
|
+
LIST_MIN_LIMIT,
|
|
7
|
+
MAX_DIFFICULTY,
|
|
8
|
+
MIN_DIFFICULTY,
|
|
9
|
+
MIN_OFFSET,
|
|
10
|
+
MIN_SEARCH_VOLUME,
|
|
11
|
+
Surface,
|
|
12
|
+
} from '../core/constants.js';
|
|
13
|
+
import { MCP_USAGE_GUIDANCE, usageError } from '../core/errors.js';
|
|
14
|
+
import { int, list, oneOf, string, uuid } from '../core/schema.js';
|
|
15
|
+
import { BodyMode, CommandKind, Toolset, defineCommand, defineGroup } from './registry.js';
|
|
12
16
|
|
|
13
17
|
export const KEYWORDS_HELP = `Usage:
|
|
14
18
|
outrank-cli keywords list --product <product-id> [--scope scheduled|stored|all] [--status published|scheduled|generating_article|pregenerated|disabled] [--q <text>] [--article-type <value>] [--article-subtype <value>] [--scheduled-from <date>] [--scheduled-to <date>] [--search-volume-min <n>] [--search-volume-max <n>] [--difficulty-min <n>] [--difficulty-max <n>] [--limit <n>] [--offset <n>]
|
|
@@ -43,262 +47,275 @@ Examples:
|
|
|
43
47
|
outrank-cli keywords delete 00000000-0000-0000-0000-000000000000
|
|
44
48
|
`;
|
|
45
49
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return `${url.pathname}${url.search}`;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const getRequiredProductOption = (parsed) => {
|
|
57
|
-
const productId = getOption(parsed, 'product');
|
|
58
|
-
if (!productId) {
|
|
59
|
-
throw Object.assign(new Error('Missing required option: --product <product-id>.'), {
|
|
60
|
-
cliCode: 'CLI_INVALID_USAGE',
|
|
61
|
-
guidance: 'Pass the target product UUID with `--product <product-id>`. Use `outrank-cli products list` if you need to look it up first.',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return productId;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const handleKeywordsList = async (parsed) => {
|
|
68
|
-
const path = buildQueryPath('/keywords', {
|
|
69
|
-
product_id: getRequiredProductOption(parsed),
|
|
70
|
-
scope: getOption(parsed, 'scope'),
|
|
71
|
-
status: getOption(parsed, 'status'),
|
|
72
|
-
q: getOption(parsed, 'q'),
|
|
73
|
-
article_type: getOption(parsed, 'article-type'),
|
|
74
|
-
article_subtype: getOption(parsed, 'article-subtype'),
|
|
75
|
-
scheduled_from: getOption(parsed, 'scheduled-from'),
|
|
76
|
-
scheduled_to: getOption(parsed, 'scheduled-to'),
|
|
77
|
-
search_volume_min: parseIntegerOption(parsed, 'search-volume-min', { min: 0 }),
|
|
78
|
-
search_volume_max: parseIntegerOption(parsed, 'search-volume-max', { min: 0 }),
|
|
79
|
-
difficulty_min: parseIntegerOption(parsed, 'difficulty-min', { min: 0, max: 100 }),
|
|
80
|
-
difficulty_max: parseIntegerOption(parsed, 'difficulty-max', { min: 0, max: 100 }),
|
|
81
|
-
limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 100 }),
|
|
82
|
-
offset: parseIntegerOption(parsed, 'offset', { min: 0 }),
|
|
83
|
-
});
|
|
84
|
-
const response = await requestAgentApi(parsed, {
|
|
85
|
-
method: 'GET',
|
|
86
|
-
path,
|
|
87
|
-
});
|
|
88
|
-
return response.payload;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const handleKeywordsGet = async (parsed) => {
|
|
92
|
-
const keywordId = getRequiredPositional(parsed, 0, '<keyword-id>');
|
|
93
|
-
const path = buildQueryPath(`/keywords/${keywordId}`, {
|
|
94
|
-
kind: getOption(parsed, 'kind'),
|
|
95
|
-
});
|
|
96
|
-
const response = await requestAgentApi(parsed, {
|
|
97
|
-
method: 'GET',
|
|
98
|
-
path,
|
|
99
|
-
});
|
|
100
|
-
return response.payload;
|
|
101
|
-
};
|
|
50
|
+
const GenerateSource = Object.freeze({ COMPETITORS: 'competitors', AUDIENCES: 'audiences', BOTH: 'both' });
|
|
51
|
+
const GENERATE_SOURCES = Object.values(GenerateSource);
|
|
52
|
+
const KEYWORD_PATH = '/keywords/{keywordId}';
|
|
53
|
+
const KEYWORD_ID_CLI = { positionals: ['keywordId'] };
|
|
54
|
+
const TOOLSETS = [Toolset.KEYWORDS];
|
|
102
55
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const response = await requestAgentApi(parsed, {
|
|
108
|
-
method: 'GET',
|
|
109
|
-
path,
|
|
110
|
-
});
|
|
111
|
-
return response.payload;
|
|
56
|
+
const PRODUCT_REQUIRED = {
|
|
57
|
+
guidance:
|
|
58
|
+
'Pass the target product UUID with `--product <product-id>`. Use `outrank-cli products list` if you need to ' +
|
|
59
|
+
'look it up first.',
|
|
112
60
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const response = await requestAgentApi(parsed, {
|
|
119
|
-
method: 'GET',
|
|
120
|
-
path,
|
|
121
|
-
});
|
|
122
|
-
return response.payload;
|
|
61
|
+
const COUNT_REQUIRED = {
|
|
62
|
+
guidance:
|
|
63
|
+
'Ask the human how many keywords to generate first. The dashboard-equivalent flow allows 5-30 keywords when ' +
|
|
64
|
+
'enough scheduler slots are free. Use `outrank-cli keywords generation-status --product <product-id>` to ' +
|
|
65
|
+
'inspect current slot capacity.',
|
|
123
66
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
body,
|
|
132
|
-
});
|
|
133
|
-
return response.payload;
|
|
67
|
+
const SOURCE_GUIDANCE =
|
|
68
|
+
'Choose a dashboard generation source with `--source competitors`, `--source audiences`, or `--source both`.';
|
|
69
|
+
const LEGACY_GENERATE_OPTION = {
|
|
70
|
+
message: 'The generate command no longer accepts --theme or --country.',
|
|
71
|
+
guidance:
|
|
72
|
+
'Use `--count`, `--source`, repeated `--competitor`, and repeated `--audience` to match the dashboard ' +
|
|
73
|
+
'Generate Keywords flow.',
|
|
134
74
|
};
|
|
75
|
+
const SUGGEST_TERM_GUIDANCE = 'Pass either `--keyword <text>` or `--theme <text>` when requesting keyword suggestions.';
|
|
135
76
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
const body = await readJsonInput(parsed);
|
|
139
|
-
const response = await requestAgentApi(parsed, {
|
|
140
|
-
method: 'PATCH',
|
|
141
|
-
path: `/keywords/${keywordId}`,
|
|
142
|
-
body,
|
|
143
|
-
});
|
|
144
|
-
return response.payload;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const handleKeywordsSuggest = async (parsed) => {
|
|
148
|
-
const keyword = getOption(parsed, 'keyword');
|
|
149
|
-
const theme = getOption(parsed, 'theme');
|
|
150
|
-
if (!keyword && !theme) {
|
|
151
|
-
throw Object.assign(new Error('Missing required option: --keyword <text> or --theme <text>.'), {
|
|
152
|
-
cliCode: 'CLI_INVALID_USAGE',
|
|
153
|
-
guidance: 'Pass either `--keyword <text>` or `--theme <text>` when requesting keyword suggestions.',
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const body = {
|
|
158
|
-
product_id: getRequiredProductOption(parsed),
|
|
159
|
-
...(keyword ? { keyword } : {}),
|
|
160
|
-
...(theme ? { theme } : {}),
|
|
161
|
-
...(getOption(parsed, 'keyword-region') ? { keyword_region: getOption(parsed, 'keyword-region') } : {}),
|
|
162
|
-
...(parseIntegerOption(parsed, 'limit', { min: 1, max: 20 }) !== undefined
|
|
163
|
-
? { limit: parseIntegerOption(parsed, 'limit', { min: 1, max: 20 }) }
|
|
164
|
-
: {}),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const response = await requestAgentApi(parsed, {
|
|
168
|
-
method: 'POST',
|
|
169
|
-
path: '/keywords/suggest',
|
|
170
|
-
body,
|
|
171
|
-
});
|
|
172
|
-
return response.payload;
|
|
173
|
-
};
|
|
77
|
+
const productOption = () => uuid().required(PRODUCT_REQUIRED);
|
|
78
|
+
const productCli = { options: { product: 'productId' }, placeholders: { productId: '<product-id>' } };
|
|
174
79
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
80
|
+
const listCommand = defineCommand({
|
|
81
|
+
id: 'keywords.list',
|
|
82
|
+
summary: 'List keywords for a product with optional filters and pagination.',
|
|
83
|
+
kind: CommandKind.READ,
|
|
84
|
+
toolsets: TOOLSETS,
|
|
85
|
+
input: {
|
|
86
|
+
productId: productOption(),
|
|
87
|
+
scope: string(),
|
|
88
|
+
status: string(),
|
|
89
|
+
q: string(),
|
|
90
|
+
articleType: string(),
|
|
91
|
+
articleSubtype: string(),
|
|
92
|
+
scheduledFrom: string(),
|
|
93
|
+
scheduledTo: string(),
|
|
94
|
+
searchVolumeMin: int({ min: MIN_SEARCH_VOLUME }),
|
|
95
|
+
searchVolumeMax: int({ min: MIN_SEARCH_VOLUME }),
|
|
96
|
+
difficultyMin: int({ min: MIN_DIFFICULTY, max: MAX_DIFFICULTY }),
|
|
97
|
+
difficultyMax: int({ min: MIN_DIFFICULTY, max: MAX_DIFFICULTY }),
|
|
98
|
+
limit: int({ min: LIST_MIN_LIMIT, max: LIST_MAX_LIMIT }),
|
|
99
|
+
offset: int({ min: MIN_OFFSET }),
|
|
100
|
+
},
|
|
101
|
+
cli: productCli,
|
|
102
|
+
run: (input, ctx) =>
|
|
103
|
+
ctx.http({
|
|
104
|
+
method: 'GET',
|
|
105
|
+
path: '/keywords',
|
|
106
|
+
query: {
|
|
107
|
+
product_id: input.productId,
|
|
108
|
+
scope: input.scope,
|
|
109
|
+
status: input.status,
|
|
110
|
+
q: input.q,
|
|
111
|
+
article_type: input.articleType,
|
|
112
|
+
article_subtype: input.articleSubtype,
|
|
113
|
+
scheduled_from: input.scheduledFrom,
|
|
114
|
+
scheduled_to: input.scheduledTo,
|
|
115
|
+
search_volume_min: input.searchVolumeMin,
|
|
116
|
+
search_volume_max: input.searchVolumeMax,
|
|
117
|
+
difficulty_min: input.difficultyMin,
|
|
118
|
+
difficulty_max: input.difficultyMax,
|
|
119
|
+
limit: input.limit,
|
|
120
|
+
offset: input.offset,
|
|
183
121
|
},
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const source = getOption(parsed, 'source') || 'both';
|
|
188
|
-
if (!['competitors', 'audiences', 'both'].includes(source)) {
|
|
189
|
-
throw Object.assign(new Error('--source must be one of: competitors, audiences, both.'), {
|
|
190
|
-
cliCode: 'CLI_INVALID_USAGE',
|
|
191
|
-
guidance:
|
|
192
|
-
'Choose a dashboard generation source with `--source competitors`, `--source audiences`, or `--source both`.',
|
|
193
|
-
});
|
|
194
|
-
}
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
195
124
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
125
|
+
const getCommand = defineCommand({
|
|
126
|
+
id: 'keywords.get',
|
|
127
|
+
summary: 'Get one keyword by id.',
|
|
128
|
+
kind: CommandKind.READ,
|
|
129
|
+
toolsets: TOOLSETS,
|
|
130
|
+
input: { keywordId: uuid().required(), kind: string() },
|
|
131
|
+
cli: KEYWORD_ID_CLI,
|
|
132
|
+
run: (input, ctx) =>
|
|
133
|
+
ctx.http({
|
|
134
|
+
method: 'GET',
|
|
135
|
+
path: KEYWORD_PATH,
|
|
136
|
+
pathParams: { keywordId: input.keywordId },
|
|
137
|
+
query: { kind: input.kind },
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
199
140
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
141
|
+
const exportCommand = defineCommand({
|
|
142
|
+
id: 'keywords.export',
|
|
143
|
+
summary: 'Export the keyword plan for a product.',
|
|
144
|
+
kind: CommandKind.READ,
|
|
145
|
+
toolsets: TOOLSETS,
|
|
146
|
+
input: { productId: productOption() },
|
|
147
|
+
cli: productCli,
|
|
148
|
+
run: (input, ctx) => ctx.http({ method: 'GET', path: '/keywords/export', query: { product_id: input.productId } }),
|
|
149
|
+
});
|
|
207
150
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const response = await requestAgentApi(parsed, {
|
|
220
|
-
method: 'POST',
|
|
221
|
-
path: '/keywords/generate',
|
|
222
|
-
body,
|
|
223
|
-
});
|
|
224
|
-
return response.payload;
|
|
225
|
-
};
|
|
151
|
+
const generationStatusCommand = defineCommand({
|
|
152
|
+
id: 'keywords.generation-status',
|
|
153
|
+
summary: 'Show keyword generation status and scheduler slot capacity for a product.',
|
|
154
|
+
kind: CommandKind.READ,
|
|
155
|
+
toolsets: TOOLSETS,
|
|
156
|
+
input: { productId: productOption() },
|
|
157
|
+
cli: productCli,
|
|
158
|
+
run: (input, ctx) =>
|
|
159
|
+
ctx.http({ method: 'GET', path: '/keywords/generation-status', query: { product_id: input.productId } }),
|
|
160
|
+
});
|
|
226
161
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
body.
|
|
162
|
+
const addCommand = defineCommand({
|
|
163
|
+
id: 'keywords.add',
|
|
164
|
+
summary: 'Add a keyword to a product from a JSON body.',
|
|
165
|
+
kind: CommandKind.WRITE,
|
|
166
|
+
toolsets: TOOLSETS,
|
|
167
|
+
input: { productId: productOption() },
|
|
168
|
+
cli: productCli,
|
|
169
|
+
body: BodyMode.JSON,
|
|
170
|
+
bodyMerge: { product_id: 'productId' },
|
|
171
|
+
run: (input, ctx) => ctx.http({ method: 'POST', path: '/keywords', body: input.body }),
|
|
172
|
+
});
|
|
230
173
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
174
|
+
const updateCommand = defineCommand({
|
|
175
|
+
id: 'keywords.update',
|
|
176
|
+
summary: 'Update a keyword (instructions, schedule, quality questions) from a JSON body.',
|
|
177
|
+
kind: CommandKind.WRITE,
|
|
178
|
+
toolsets: TOOLSETS,
|
|
179
|
+
input: { keywordId: uuid().required() },
|
|
180
|
+
cli: KEYWORD_ID_CLI,
|
|
181
|
+
body: BodyMode.JSON,
|
|
182
|
+
run: (input, ctx) =>
|
|
183
|
+
ctx.http({ method: 'PATCH', path: KEYWORD_PATH, pathParams: { keywordId: input.keywordId }, body: input.body }),
|
|
184
|
+
});
|
|
235
185
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
186
|
+
const suggestCommand = defineCommand({
|
|
187
|
+
id: 'keywords.suggest',
|
|
188
|
+
summary: 'Suggest keywords for a product from a seed keyword or a theme.',
|
|
189
|
+
kind: CommandKind.READ,
|
|
190
|
+
toolsets: TOOLSETS,
|
|
191
|
+
input: {
|
|
192
|
+
keyword: string(),
|
|
193
|
+
theme: string(),
|
|
194
|
+
productId: productOption(),
|
|
195
|
+
keywordRegion: string(),
|
|
196
|
+
limit: int({ min: LIST_MIN_LIMIT, max: KEYWORDS_SUGGEST_MAX_LIMIT }),
|
|
197
|
+
},
|
|
198
|
+
cli: productCli,
|
|
199
|
+
refine: (input, ctx) => {
|
|
200
|
+
if (input.keyword || input.theme) return;
|
|
201
|
+
if (ctx.surface === Surface.MCP) {
|
|
202
|
+
throw usageError('Missing required argument: keyword or theme.', MCP_USAGE_GUIDANCE);
|
|
203
|
+
}
|
|
204
|
+
throw usageError('Missing required option: --keyword <text> or --theme <text>.', SUGGEST_TERM_GUIDANCE);
|
|
205
|
+
},
|
|
206
|
+
run: (input, ctx) =>
|
|
207
|
+
ctx.http({
|
|
208
|
+
method: 'POST',
|
|
209
|
+
path: '/keywords/suggest',
|
|
210
|
+
body: {
|
|
211
|
+
product_id: input.productId,
|
|
212
|
+
...(input.keyword ? { keyword: input.keyword } : {}),
|
|
213
|
+
...(input.theme ? { theme: input.theme } : {}),
|
|
214
|
+
...(input.keywordRegion ? { keyword_region: input.keywordRegion } : {}),
|
|
215
|
+
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
253
219
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
220
|
+
const generateCommand = defineCommand({
|
|
221
|
+
id: 'keywords.generate',
|
|
222
|
+
summary: 'Generate keywords for a product from competitors and/or target audiences.',
|
|
223
|
+
kind: CommandKind.WRITE,
|
|
224
|
+
toolsets: TOOLSETS,
|
|
225
|
+
input: {
|
|
226
|
+
source: oneOf(GENERATE_SOURCES).default(GenerateSource.BOTH).guidance(SOURCE_GUIDANCE),
|
|
227
|
+
count: int({ min: KEYWORDS_GENERATE_MIN_COUNT, max: KEYWORDS_GENERATE_MAX_COUNT }).required(COUNT_REQUIRED),
|
|
228
|
+
competitors: list({ split: null }),
|
|
229
|
+
audiences: list({ split: null }),
|
|
230
|
+
productId: productOption(),
|
|
231
|
+
},
|
|
232
|
+
cli: {
|
|
233
|
+
options: { product: 'productId', competitor: 'competitors', audience: 'audiences' },
|
|
234
|
+
placeholders: { productId: '<product-id>', count: '<n>' },
|
|
235
|
+
rejectedOptions: { theme: LEGACY_GENERATE_OPTION, country: LEGACY_GENERATE_OPTION },
|
|
236
|
+
},
|
|
237
|
+
longRunning: { pollTool: 'keywords.generation-status' },
|
|
238
|
+
run: (input, ctx) =>
|
|
239
|
+
ctx.http({
|
|
240
|
+
method: 'POST',
|
|
241
|
+
path: '/keywords/generate',
|
|
242
|
+
body: {
|
|
243
|
+
product_id: input.productId,
|
|
244
|
+
keywords_count: input.count,
|
|
245
|
+
filters: {
|
|
246
|
+
isGenerateByCompetitors: input.source !== GenerateSource.AUDIENCES,
|
|
247
|
+
isGenerateByAudiences: input.source !== GenerateSource.COMPETITORS,
|
|
248
|
+
...(input.competitors?.length ? { competitors: input.competitors } : {}),
|
|
249
|
+
...(input.audiences?.length ? { target_audiences: input.audiences } : {}),
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
}),
|
|
253
|
+
});
|
|
262
254
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
255
|
+
const bulkProcessCommand = defineCommand({
|
|
256
|
+
id: 'keywords.bulk-process',
|
|
257
|
+
summary: 'Bulk-process a list of keywords for a product from a JSON body.',
|
|
258
|
+
kind: CommandKind.READ,
|
|
259
|
+
toolsets: TOOLSETS,
|
|
260
|
+
input: { productId: productOption(), keywordRegion: string() },
|
|
261
|
+
cli: productCli,
|
|
262
|
+
body: BodyMode.JSON,
|
|
263
|
+
bodyMerge: { product_id: 'productId', keyword_region: 'keywordRegion' },
|
|
264
|
+
run: (input, ctx) => ctx.http({ method: 'POST', path: '/keywords/bulk-process', body: input.body }),
|
|
265
|
+
});
|
|
272
266
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
267
|
+
const bulkRescheduleCommand = defineCommand({
|
|
268
|
+
id: 'keywords.bulk-reschedule',
|
|
269
|
+
summary: 'Reschedule several keywords from a JSON body.',
|
|
270
|
+
kind: CommandKind.WRITE,
|
|
271
|
+
toolsets: TOOLSETS,
|
|
272
|
+
body: BodyMode.JSON,
|
|
273
|
+
run: (input, ctx) => ctx.http({ method: 'POST', path: '/keywords/bulk-reschedule', body: input.body }),
|
|
274
|
+
});
|
|
277
275
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
276
|
+
const deleteCommand = defineCommand({
|
|
277
|
+
id: 'keywords.delete',
|
|
278
|
+
summary: 'Delete one keyword.',
|
|
279
|
+
kind: CommandKind.DESTRUCTIVE,
|
|
280
|
+
toolsets: TOOLSETS,
|
|
281
|
+
input: { keywordId: uuid().required() },
|
|
282
|
+
cli: KEYWORD_ID_CLI,
|
|
283
|
+
run: (input, ctx) => ctx.http({ method: 'DELETE', path: KEYWORD_PATH, pathParams: { keywordId: input.keywordId } }),
|
|
284
|
+
});
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (subcommand === 'bulk-process') return handleKeywordsBulkProcess(parsed);
|
|
296
|
-
if (subcommand === 'bulk-reschedule') return handleKeywordsBulkReschedule(parsed);
|
|
297
|
-
if (subcommand === 'delete') return handleKeywordsDelete(parsed);
|
|
298
|
-
if (subcommand === 'bulk-delete') return handleKeywordsBulkDelete(parsed);
|
|
286
|
+
const bulkDeleteCommand = defineCommand({
|
|
287
|
+
id: 'keywords.bulk-delete',
|
|
288
|
+
summary: 'Delete several keywords from a JSON body of ids.',
|
|
289
|
+
kind: CommandKind.DESTRUCTIVE,
|
|
290
|
+
toolsets: TOOLSETS,
|
|
291
|
+
body: BodyMode.JSON,
|
|
292
|
+
run: (input, ctx) => ctx.http({ method: 'POST', path: '/keywords/bulk-delete', body: input.body }),
|
|
293
|
+
});
|
|
299
294
|
|
|
300
|
-
|
|
301
|
-
|
|
295
|
+
export const keywordsGroup = defineGroup({
|
|
296
|
+
id: 'keywords',
|
|
297
|
+
help: {
|
|
298
|
+
usage: KEYWORDS_HELP,
|
|
299
|
+
guidance:
|
|
300
|
+
'Choose one of: list, get, export, generation-status, add, update, suggest, generate, bulk-process, ' +
|
|
301
|
+
'bulk-reschedule, delete, bulk-delete.',
|
|
302
|
+
},
|
|
303
|
+
unknown: (word) => ({
|
|
304
|
+
message: `Unknown keywords subcommand: ${word}.`,
|
|
302
305
|
guidance: 'Run `outrank-cli keywords --help` to see available keyword commands.',
|
|
303
|
-
})
|
|
304
|
-
|
|
306
|
+
}),
|
|
307
|
+
children: [
|
|
308
|
+
listCommand,
|
|
309
|
+
getCommand,
|
|
310
|
+
exportCommand,
|
|
311
|
+
generationStatusCommand,
|
|
312
|
+
addCommand,
|
|
313
|
+
updateCommand,
|
|
314
|
+
suggestCommand,
|
|
315
|
+
generateCommand,
|
|
316
|
+
bulkProcessCommand,
|
|
317
|
+
bulkRescheduleCommand,
|
|
318
|
+
deleteCommand,
|
|
319
|
+
bulkDeleteCommand,
|
|
320
|
+
],
|
|
321
|
+
});
|