outrank-cli 0.10.0 → 0.11.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 +57 -8
- package/SKILL.md +38 -3
- package/package.json +1 -1
- package/src/adapters/mcp/descriptions.js +4 -1
- package/src/adapters/mcp/help.js +1 -1
- package/src/adapters/mcp/prompts.js +33 -0
- package/src/adapters/mcp/routes.js +1 -1
- package/src/adapters/mcp/toolNotes.js +65 -0
- package/src/commands/aiCitedLists.js +760 -0
- package/src/commands/improvements.js +1 -11
- package/src/commands/index.js +2 -0
- package/src/commands/products/integrations.js +14 -9
- package/src/commands/products/shared.js +6 -1
- package/src/commands/registry.js +1 -0
- package/src/commands/subGroup.js +15 -0
- package/src/core/constants.js +4 -0
- package/src/core/execute.js +2 -1
- package/src/core/schema.js +21 -11
- package/src/index.js +3 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IMPROVEMENTS_CANDIDATES_MAX_LIMIT, LIST_MAX_LIMIT, LIST_MIN_LIMIT, MIN_OFFSET } from '../core/constants.js';
|
|
2
2
|
import { BoolSerialize, bool, int, list, object, oneOf, string, uuid } from '../core/schema.js';
|
|
3
3
|
import { BodyMode, CommandKind, Toolset, defineCommand, defineGroup } from './registry.js';
|
|
4
|
+
import { subGroup } from './subGroup.js';
|
|
4
5
|
|
|
5
6
|
const WEEKDAY_CODES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
6
7
|
const ENABLED_HINT = 'true starts the unattended improvement loop.';
|
|
@@ -343,17 +344,6 @@ const executionsPush = defineCommand({
|
|
|
343
344
|
}),
|
|
344
345
|
});
|
|
345
346
|
|
|
346
|
-
const subGroup = ({ id, usage, actions, children }) =>
|
|
347
|
-
defineGroup({
|
|
348
|
-
id,
|
|
349
|
-
help: { usage, guidance: `Use: improvements ${id.slice(id.indexOf('.') + 1)} ${actions}.` },
|
|
350
|
-
unknown: (word) => ({
|
|
351
|
-
message: `Unknown ${id.slice(id.indexOf('.') + 1)} action: ${word}.`,
|
|
352
|
-
guidance: `Use: improvements ${id.slice(id.indexOf('.') + 1)} ${actions}.`,
|
|
353
|
-
}),
|
|
354
|
-
children,
|
|
355
|
-
});
|
|
356
|
-
|
|
357
347
|
export const improvementsGroup = defineGroup({
|
|
358
348
|
id: 'improvements',
|
|
359
349
|
help: {
|
package/src/commands/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { aiCitedListsGroup } from './aiCitedLists.js';
|
|
1
2
|
import { articlesGroup } from './articles.js';
|
|
2
3
|
import { authGroup } from './auth.js';
|
|
3
4
|
import { billingGroup } from './billing.js';
|
|
@@ -19,4 +20,5 @@ export const registry = createRegistry([
|
|
|
19
20
|
improvementsGroup,
|
|
20
21
|
productsGroup,
|
|
21
22
|
marketplaceGroup,
|
|
23
|
+
aiCitedListsGroup,
|
|
22
24
|
]);
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { usageError } from '../../core/errors.js';
|
|
2
2
|
import { oneOf, string, uuid } from '../../core/schema.js';
|
|
3
3
|
import { BodyMode, CommandKind, Toolset, defineCommand, defineGroup } from '../registry.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ATTEMPT_ID_PLACEHOLDERS,
|
|
6
|
+
PRODUCT_CLI,
|
|
7
|
+
attemptIdInput,
|
|
8
|
+
attemptIdQuery,
|
|
9
|
+
productInput,
|
|
10
|
+
productParams,
|
|
11
|
+
withProduct,
|
|
12
|
+
} from './shared.js';
|
|
5
13
|
|
|
6
14
|
const INTEGRATIONS_USAGE = `Usage:
|
|
7
15
|
outrank-cli products integrations providers
|
|
@@ -92,12 +100,9 @@ const INTEGRATION_PATH = '/products/{productId}/integrations/{integrationId}';
|
|
|
92
100
|
const CONNECT_PATH = '/products/{productId}/integrations/connect/{provider}';
|
|
93
101
|
const PROVIDER_PRODUCT_CLI = { positionals: ['provider', 'productId'] };
|
|
94
102
|
const INTEGRATION_CLI = withProduct(['integrationId']);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'Run `outrank-cli products integrations poll <provider> <product-id> --attempt-id <attempt-id>` after ' +
|
|
99
|
-
'connect starts.',
|
|
100
|
-
};
|
|
103
|
+
const ATTEMPT_ID_GUIDANCE =
|
|
104
|
+
'Run `outrank-cli products integrations poll <provider> <product-id> --attempt-id <attempt-id>` after ' +
|
|
105
|
+
'connect starts.';
|
|
101
106
|
const SENSITIVE_BODY = { sensitiveBody: true };
|
|
102
107
|
|
|
103
108
|
const providerInput = (providers) => ({ provider: oneOf(providers).required(), ...productInput() });
|
|
@@ -147,14 +152,14 @@ const poll = defineCommand({
|
|
|
147
152
|
summary: 'Poll an OAuth connect attempt until the handoff completes.',
|
|
148
153
|
kind: CommandKind.READ,
|
|
149
154
|
toolsets: TOOLSETS,
|
|
150
|
-
input: { ...providerInput(CONNECT_PROVIDERS),
|
|
155
|
+
input: { ...providerInput(CONNECT_PROVIDERS), ...attemptIdInput(ATTEMPT_ID_GUIDANCE) },
|
|
151
156
|
cli: { ...PROVIDER_PRODUCT_CLI, placeholders: ATTEMPT_ID_PLACEHOLDERS },
|
|
152
157
|
run: (input, ctx) =>
|
|
153
158
|
ctx.http({
|
|
154
159
|
method: 'GET',
|
|
155
160
|
path: CONNECT_PATH,
|
|
156
161
|
pathParams: providerParams(input),
|
|
157
|
-
query:
|
|
162
|
+
query: attemptIdQuery(input),
|
|
158
163
|
}),
|
|
159
164
|
});
|
|
160
165
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { uuid } from '../../core/schema.js';
|
|
1
|
+
import { string, uuid } from '../../core/schema.js';
|
|
2
2
|
|
|
3
3
|
export const PRODUCT_CLI = { positionals: ['productId'] };
|
|
4
4
|
|
|
@@ -6,3 +6,8 @@ export const productInput = () => ({ productId: uuid().required() });
|
|
|
6
6
|
export const productParams = (input) => ({ productId: input.productId });
|
|
7
7
|
|
|
8
8
|
export const withProduct = (extraPositionals) => ({ positionals: ['productId', ...extraPositionals] });
|
|
9
|
+
export const productParamsWith = (input, name) => ({ productId: input.productId, [name]: input[name] });
|
|
10
|
+
|
|
11
|
+
export const ATTEMPT_ID_PLACEHOLDERS = { attemptId: '<attempt-id>' };
|
|
12
|
+
export const attemptIdInput = (guidance) => ({ attemptId: string().required({ guidance }) });
|
|
13
|
+
export const attemptIdQuery = (input) => ({ attempt_id: input.attemptId });
|
package/src/commands/registry.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cliWords, defineGroup, lastSegment } from './registry.js';
|
|
2
|
+
|
|
3
|
+
const WORD_SEPARATOR = ' ';
|
|
4
|
+
|
|
5
|
+
export const subGroup = ({ id, usage, actions, children }) => {
|
|
6
|
+
const noun = lastSegment(id);
|
|
7
|
+
const group = cliWords(id).slice(0, -1).join(WORD_SEPARATOR);
|
|
8
|
+
const guidance = `Use: ${group} ${noun} ${actions}.`;
|
|
9
|
+
return defineGroup({
|
|
10
|
+
id,
|
|
11
|
+
help: { usage, guidance },
|
|
12
|
+
unknown: (word) => ({ message: `Unknown ${noun} action: ${word}.`, guidance }),
|
|
13
|
+
children,
|
|
14
|
+
});
|
|
15
|
+
};
|
package/src/core/constants.js
CHANGED
|
@@ -66,6 +66,10 @@ export const BLOG_REDIRECT_GONE_STATUS_CODE = 410;
|
|
|
66
66
|
export const BLOG_REDIRECT_STATUS_CODES = Object.freeze([301, 302, BLOG_REDIRECT_GONE_STATUS_CODE]);
|
|
67
67
|
export const BLOG_WEBHOOK_EVENTS = Object.freeze(['subscriber.created']);
|
|
68
68
|
|
|
69
|
+
export const AI_CITATIONS_PERIOD_WEEKS = Object.freeze([4, 12]);
|
|
70
|
+
export const AI_CITED_LISTS_LIST_VIEWS = Object.freeze(['all', 'to_send', 'skipped']);
|
|
71
|
+
export const AI_CITED_LISTS_ENTRY_MODES = Object.freeze(['generated', 'custom']);
|
|
72
|
+
|
|
69
73
|
export const BILLING_MIN_ADDITIONAL_PRODUCTS = 1;
|
|
70
74
|
export const BILLING_MAX_ADDITIONAL_PRODUCTS = 30;
|
|
71
75
|
export const BILLING_MIN_CONFIRM_CHARGE_CENTS = 0;
|
package/src/core/execute.js
CHANGED
|
@@ -50,7 +50,8 @@ const mergeBody = (spec, input, body) => {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const executeCommand = async ({ spec, raw, loadBody = async () => undefined, ctx, labels = {} }) => {
|
|
53
|
-
const
|
|
53
|
+
const fromText = ctx.surface !== Surface.MCP;
|
|
54
|
+
const validation = { ...labels, splitLists: fromText, coerceEnumText: fromText };
|
|
54
55
|
try {
|
|
55
56
|
const { bodyField, rest } = splitBodyField(spec.input);
|
|
56
57
|
const input = validateInput(rest, raw, validation);
|
package/src/core/schema.js
CHANGED
|
@@ -83,7 +83,7 @@ const parseInteger = (value, label) => {
|
|
|
83
83
|
return Number(value);
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
const parseScalar = (field, value, label) => {
|
|
86
|
+
const parseScalar = (field, value, label, { coerceEnumText = false } = {}) => {
|
|
87
87
|
if (field.kind === FieldKind.STRING) {
|
|
88
88
|
if (typeof value !== 'string') throw usageError(`${label} must be a string.`);
|
|
89
89
|
return value;
|
|
@@ -111,8 +111,11 @@ const parseScalar = (field, value, label) => {
|
|
|
111
111
|
throw usageError(`${label} must be true or false.`);
|
|
112
112
|
}
|
|
113
113
|
if (field.kind === FieldKind.ENUM) {
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
const match = field.values.find(
|
|
115
|
+
(candidate) => candidate === value || (coerceEnumText && String(candidate) === value),
|
|
116
|
+
);
|
|
117
|
+
if (match === undefined) throw usageError(`${label} must be one of: ${field.values.join(', ')}.`);
|
|
118
|
+
return match;
|
|
116
119
|
}
|
|
117
120
|
if (field.kind === FieldKind.OBJECT) {
|
|
118
121
|
if (!isPlainObject(value)) throw usageError(`${label} must be a JSON object.`);
|
|
@@ -139,11 +142,11 @@ const splitListValues = (field, value, splitLists) => {
|
|
|
139
142
|
return rawItems.flatMap((item) => (typeof item === 'string' ? item.split(field.split) : [item]));
|
|
140
143
|
};
|
|
141
144
|
|
|
142
|
-
const parseList = (field, value, label, splitLists) => {
|
|
145
|
+
const parseList = (field, value, label, splitLists, coerceEnumText) => {
|
|
143
146
|
const items = splitListValues(field, value, splitLists)
|
|
144
147
|
.map((item) => (typeof item === 'string' ? item.trim() : item))
|
|
145
148
|
.filter((item) => item !== '');
|
|
146
|
-
return items.map((item) => parseScalar(field.item, item, label));
|
|
149
|
+
return items.map((item) => parseScalar(field.item, item, label, { coerceEnumText }));
|
|
147
150
|
};
|
|
148
151
|
|
|
149
152
|
const withFieldGuidance = (field, run) => {
|
|
@@ -157,11 +160,11 @@ const withFieldGuidance = (field, run) => {
|
|
|
157
160
|
}
|
|
158
161
|
};
|
|
159
162
|
|
|
160
|
-
export const parseFieldValue = (field, value, label, { splitLists = true } = {}) =>
|
|
163
|
+
export const parseFieldValue = (field, value, label, { splitLists = true, coerceEnumText = false } = {}) =>
|
|
161
164
|
withFieldGuidance(field, () => {
|
|
162
165
|
if (value === '') throw usageError(`${label} requires a value.`);
|
|
163
|
-
if (field.kind === FieldKind.LIST) return parseList(field, value, label, splitLists);
|
|
164
|
-
return parseScalar(field, value, label);
|
|
166
|
+
if (field.kind === FieldKind.LIST) return parseList(field, value, label, splitLists, coerceEnumText);
|
|
167
|
+
return parseScalar(field, value, label, { coerceEnumText });
|
|
165
168
|
});
|
|
166
169
|
|
|
167
170
|
const missingPropertyError = (property, label) => {
|
|
@@ -202,7 +205,11 @@ const missingFieldError = (field, name, missingMessage) => {
|
|
|
202
205
|
|
|
203
206
|
const cloneDefault = (value) => (value !== null && typeof value === 'object' ? structuredClone(value) : value);
|
|
204
207
|
|
|
205
|
-
export const validateInput = (
|
|
208
|
+
export const validateInput = (
|
|
209
|
+
fields,
|
|
210
|
+
raw,
|
|
211
|
+
{ labelOf = (name) => name, missingOf, splitLists = true, coerceEnumText = false } = {},
|
|
212
|
+
) => {
|
|
206
213
|
const missingMessage = missingOf ?? ((name) => `Missing required input: ${labelOf(name)}.`);
|
|
207
214
|
const result = {};
|
|
208
215
|
for (const [name, field] of Object.entries(fields)) {
|
|
@@ -212,7 +219,7 @@ export const validateInput = (fields, raw, { labelOf = (name) => name, missingOf
|
|
|
212
219
|
if (field.defaultValue !== undefined) result[name] = cloneDefault(field.defaultValue);
|
|
213
220
|
continue;
|
|
214
221
|
}
|
|
215
|
-
const parsed = parseFieldValue(field, value, labelOf(name), { splitLists });
|
|
222
|
+
const parsed = parseFieldValue(field, value, labelOf(name), { splitLists, coerceEnumText });
|
|
216
223
|
if (field.kind === FieldKind.LIST && field.isRequired && parsed.length === 0) {
|
|
217
224
|
throw missingFieldError(field, name, missingMessage);
|
|
218
225
|
}
|
|
@@ -239,6 +246,9 @@ const boundedNumber = (schema, field) => {
|
|
|
239
246
|
return bounded;
|
|
240
247
|
};
|
|
241
248
|
|
|
249
|
+
const zodEnum = (z, values) =>
|
|
250
|
+
values.every((value) => typeof value === 'string') ? z.enum(values) : z.literal(values);
|
|
251
|
+
|
|
242
252
|
const zodScalar = (z, field, options = {}) => {
|
|
243
253
|
const { jsonAsObject = false, inBody = false } = options;
|
|
244
254
|
if (field.kind === FieldKind.STRING) return inBody ? z.string() : z.string().min(1);
|
|
@@ -246,7 +256,7 @@ const zodScalar = (z, field, options = {}) => {
|
|
|
246
256
|
if (field.kind === FieldKind.INT) return boundedNumber(z.number().int(), field);
|
|
247
257
|
if (field.kind === FieldKind.NUMBER) return boundedNumber(z.number(), field);
|
|
248
258
|
if (field.kind === FieldKind.BOOL) return z.boolean();
|
|
249
|
-
if (field.kind === FieldKind.ENUM) return z
|
|
259
|
+
if (field.kind === FieldKind.ENUM) return zodEnum(z, field.values);
|
|
250
260
|
if (field.kind === FieldKind.OBJECT) {
|
|
251
261
|
if (!field.properties) return z.object({}).passthrough();
|
|
252
262
|
const propertyOptions = { ...options, inBody: true, jsonAsObject: false };
|
package/src/index.js
CHANGED
|
@@ -26,6 +26,7 @@ Groups:
|
|
|
26
26
|
articles Inspect article metadata and bodies, edit content, retry publish, precreate, and generate.
|
|
27
27
|
improvements Discover, brief, schedule, run, review, and publish article improvements for existing posts.
|
|
28
28
|
marketplace Seller onboarding, eligibility, placements, earnings, Stripe payouts, and buyer setup for the Outrank Marketplace.
|
|
29
|
+
ai-cited-lists AI-Cited Lists (Backlinks Enterprise): ChatGPT-cited lists, offers to their owners, won spots, outreach setup.
|
|
29
30
|
subscription Inspect plan, trial, quota, and add-on status.
|
|
30
31
|
usage Inspect current usage and quota consumption.
|
|
31
32
|
billing Product-slot purchase flows.
|
|
@@ -68,6 +69,8 @@ Examples:
|
|
|
68
69
|
outrank-cli marketplace seller status <product-id>
|
|
69
70
|
outrank-cli marketplace seller onboard <product-id> --country US
|
|
70
71
|
outrank-cli marketplace seller placements list <product-id> --status needs_approval
|
|
72
|
+
outrank-cli ai-cited-lists status <product-id>
|
|
73
|
+
outrank-cli ai-cited-lists lists list <product-id> --view to_send
|
|
71
74
|
`;
|
|
72
75
|
|
|
73
76
|
const main = async () => {
|