@datadive-tools/mcp 0.16.1 → 0.18.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 +12 -1
- package/dist/index.js +598 -188
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +6 -0
- package/dist/lib.js +598 -188
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
package/dist/lib.js
CHANGED
|
@@ -139,7 +139,7 @@ function extractMessage(body) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// package.json
|
|
142
|
-
var version = "0.
|
|
142
|
+
var version = "0.18.0";
|
|
143
143
|
|
|
144
144
|
// src/http/client.ts
|
|
145
145
|
var PKG_VERSION = version;
|
|
@@ -179,6 +179,7 @@ async function httpGet(ctx, path, query) {
|
|
|
179
179
|
method: "GET",
|
|
180
180
|
headers: {
|
|
181
181
|
...authHeaders(ctx.config),
|
|
182
|
+
...forwardedForHeader(ctx.config),
|
|
182
183
|
accept: "application/json",
|
|
183
184
|
"user-agent": userAgent(ctx.toolName)
|
|
184
185
|
}
|
|
@@ -196,6 +197,27 @@ async function httpPost(ctx, path, body) {
|
|
|
196
197
|
method: "POST",
|
|
197
198
|
headers: {
|
|
198
199
|
...authHeaders(ctx.config),
|
|
200
|
+
...forwardedForHeader(ctx.config),
|
|
201
|
+
accept: "application/json",
|
|
202
|
+
"content-type": "application/json",
|
|
203
|
+
"user-agent": userAgent(ctx.toolName)
|
|
204
|
+
},
|
|
205
|
+
body: JSON.stringify(body)
|
|
206
|
+
});
|
|
207
|
+
} catch (e) {
|
|
208
|
+
throw networkError(url, e);
|
|
209
|
+
}
|
|
210
|
+
return handleResponse(res);
|
|
211
|
+
}
|
|
212
|
+
async function httpPatch(ctx, path, body) {
|
|
213
|
+
const url = buildUrl(ctx.config.baseUrl, path);
|
|
214
|
+
let res;
|
|
215
|
+
try {
|
|
216
|
+
res = await fetch(url, {
|
|
217
|
+
method: "PATCH",
|
|
218
|
+
headers: {
|
|
219
|
+
...authHeaders(ctx.config),
|
|
220
|
+
...forwardedForHeader(ctx.config),
|
|
199
221
|
accept: "application/json",
|
|
200
222
|
"content-type": "application/json",
|
|
201
223
|
"user-agent": userAgent(ctx.toolName)
|
|
@@ -215,6 +237,7 @@ async function httpDelete(ctx, path) {
|
|
|
215
237
|
method: "DELETE",
|
|
216
238
|
headers: {
|
|
217
239
|
...authHeaders(ctx.config),
|
|
240
|
+
...forwardedForHeader(ctx.config),
|
|
218
241
|
accept: "application/json",
|
|
219
242
|
"user-agent": userAgent(ctx.toolName)
|
|
220
243
|
}
|
|
@@ -260,6 +283,9 @@ function authHeaders(config) {
|
|
|
260
283
|
const c = config.credentials;
|
|
261
284
|
return c.kind === "bearer" ? { authorization: `Bearer ${c.token}` } : { "x-api-key": c.apiKey };
|
|
262
285
|
}
|
|
286
|
+
function forwardedForHeader(config) {
|
|
287
|
+
return config.forwardedFor ? { "x-forwarded-for": config.forwardedFor } : {};
|
|
288
|
+
}
|
|
263
289
|
function unwrap(body) {
|
|
264
290
|
if (!isObject(body)) return body;
|
|
265
291
|
if (!("data" in body)) return body;
|
|
@@ -316,7 +342,7 @@ var inputSchema2 = {
|
|
|
316
342
|
var getNicheKeywordsTool = {
|
|
317
343
|
name: "get_niche_keywords",
|
|
318
344
|
title: "Get Master Keyword List for a Niche",
|
|
319
|
-
description: 'Use this when the user asks about keywords, search terms, or search volume for a niche. Retrieves the master keyword list for the specified Niche \u2014 the relevant search terms monitored for ranking and performance metrics. For each keyword, returns search volume, relevancy (numeric score or "Outlier"), and competitor ASIN organic ranks (asinRanks: { ASIN -> rank | null }). Also returns the latestResearchDate.',
|
|
345
|
+
description: 'Use this when the user asks about keywords, search terms, or search volume for a niche. Retrieves the master keyword list for the specified Niche \u2014 the relevant search terms monitored for ranking and performance metrics. For each keyword, returns search volume, relevancy (numeric score or "Outlier"), and competitor ASIN organic ranks (asinRanks: { ASIN -> rank | null }), plus `origin`: `mkl` (found by the dive), `manual` (added by hand with `add_niche_keywords`), `outlier` / `residue` (moved in from those lists). Only `manual`, `outlier` and `residue` keywords can be taken out with `remove_niche_keywords`. Keywords hidden by the niche\'s keyword exclusions are not listed; see `list_keyword_exclusions`. Also returns the latestResearchDate.',
|
|
320
346
|
inputSchema: inputSchema2,
|
|
321
347
|
annotations: { readOnlyHint: true },
|
|
322
348
|
handler: async (args, ctx) => {
|
|
@@ -327,16 +353,35 @@ var getNicheKeywordsTool = {
|
|
|
327
353
|
}
|
|
328
354
|
};
|
|
329
355
|
|
|
330
|
-
// src/tools/
|
|
356
|
+
// src/tools/list-keyword-exclusions.ts
|
|
331
357
|
import { z as z3 } from "zod";
|
|
332
358
|
var inputSchema3 = {
|
|
333
359
|
nicheId: z3.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
334
360
|
};
|
|
361
|
+
var listKeywordExclusionsTool = {
|
|
362
|
+
name: "list_keyword_exclusions",
|
|
363
|
+
title: "List a Niche's Keyword Exclusions",
|
|
364
|
+
description: "Use this to see which keywords are hidden from a niche's Master Keyword List, e.g. when a keyword the user expects is missing from `get_niche_keywords`. Returns the same lists the DataDive web app shows, oldest first: `exact` \u2014 each entry hides the one keyword that matches it exactly, with that keyword's `searchVolume` (0 when the niche has no such keyword); `phrase` \u2014 each entry hides every keyword that contains the phrase, with `excludedKeywordsCount` and the total `searchVolume` of the keywords it hides (not counting ones already excluded exactly). Exclusions hide keywords wherever they come from: the dive, the Outliers and Residue lists, and hand-added ones. Change them with `add_keyword_exclusions` and `remove_keyword_exclusions`.",
|
|
365
|
+
inputSchema: inputSchema3,
|
|
366
|
+
annotations: { readOnlyHint: true },
|
|
367
|
+
handler: async (args, ctx) => {
|
|
368
|
+
return await httpGet(
|
|
369
|
+
{ config: ctx.config, toolName: "list_keyword_exclusions" },
|
|
370
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/tools/get-niche-roots.ts
|
|
376
|
+
import { z as z4 } from "zod";
|
|
377
|
+
var inputSchema4 = {
|
|
378
|
+
nicheId: z4.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
379
|
+
};
|
|
335
380
|
var getNicheRootsTool = {
|
|
336
381
|
name: "get_niche_roots",
|
|
337
382
|
title: "Get Keyword Roots for a Niche",
|
|
338
383
|
description: "Use this when the user wants to find the highest-impact words across a niche's keywords, asks about 'keyword roots', or wants to prioritize terms for a listing. Retrieves the keyword lexical roots for the Niche \u2014 individual words or word-combinations extracted from the Master Keyword List (e.g. 'bluetooth headphones' yields roots 'bluetooth', 'headphones', 'bluetooth headphones'). Returns `roots` and `normalizedRoots` tables, each item with root, frequency (how many keywords contain it), broadSearchVolume (summed search volume of those keywords), and broadSearchVolumeRatio (0-1, relative to the top root), plus the per-keyword breakdowns and latestResearchDate.",
|
|
339
|
-
inputSchema:
|
|
384
|
+
inputSchema: inputSchema4,
|
|
340
385
|
annotations: { readOnlyHint: true },
|
|
341
386
|
handler: async (args, ctx) => {
|
|
342
387
|
return await httpGet(
|
|
@@ -347,15 +392,15 @@ var getNicheRootsTool = {
|
|
|
347
392
|
};
|
|
348
393
|
|
|
349
394
|
// src/tools/get-niche-competitors.ts
|
|
350
|
-
import { z as
|
|
351
|
-
var
|
|
352
|
-
nicheId:
|
|
395
|
+
import { z as z5 } from "zod";
|
|
396
|
+
var inputSchema5 = {
|
|
397
|
+
nicheId: z5.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
353
398
|
};
|
|
354
399
|
var getNicheCompetitorsTool = {
|
|
355
400
|
name: "get_niche_competitors",
|
|
356
401
|
title: "Get Competitors and Niche Statistics",
|
|
357
402
|
description: "Use this for competitor ASINs, product titles, BSR, sales/revenue benchmarks, or niche opportunity scoring. Retrieves the list of Competitors within the specified Niche along with Niche statistics. For the Niche: keyword statistics, opportunity evaluation, benchmark median values, and overall competitor strength assessment. For each Competitor: ASIN, product title, BSR, category and full category tree, sales, revenue, ratings, reviews, price, number of variations, image URL, total Ranking Juice, and ranking data. BSR supports ranking Competitors by sales position within mentioned category. For the Ranking Juice breakdown by title, bullets and description, use `get_ranking_juice`.",
|
|
358
|
-
inputSchema:
|
|
403
|
+
inputSchema: inputSchema5,
|
|
359
404
|
annotations: { readOnlyHint: true },
|
|
360
405
|
handler: async (args, ctx) => {
|
|
361
406
|
return await httpGet(
|
|
@@ -366,15 +411,15 @@ var getNicheCompetitorsTool = {
|
|
|
366
411
|
};
|
|
367
412
|
|
|
368
413
|
// src/tools/get-ranking-juice.ts
|
|
369
|
-
import { z as
|
|
370
|
-
var
|
|
371
|
-
nicheId:
|
|
414
|
+
import { z as z6 } from "zod";
|
|
415
|
+
var inputSchema6 = {
|
|
416
|
+
nicheId: z6.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
372
417
|
};
|
|
373
418
|
var getRankingJuiceTool = {
|
|
374
419
|
name: "get_ranking_juice",
|
|
375
420
|
title: "Get Ranking Juices for a Niche",
|
|
376
421
|
description: "Use this when the user asks 'how do I rank higher', 'what's my ranking juice', or wants listing-optimization guidance. Retrieves the Ranking Juices for each Competitor within the specified Niche. Ranking Juice is DataDive's proprietary metric identifying the key factors driving search-ranking success for each Competitor's listing. Returns currentListing and optimizedListing breakdowns (rankingJuice + per-property contributions: title, bullets, description) plus per-competitor listing scores and the latestResearchDate.",
|
|
377
|
-
inputSchema:
|
|
422
|
+
inputSchema: inputSchema6,
|
|
378
423
|
annotations: { readOnlyHint: true },
|
|
379
424
|
handler: async (args, ctx) => {
|
|
380
425
|
return await httpGet(
|
|
@@ -385,7 +430,7 @@ var getRankingJuiceTool = {
|
|
|
385
430
|
};
|
|
386
431
|
|
|
387
432
|
// src/tools/list-rank-radars.ts
|
|
388
|
-
import { z as
|
|
433
|
+
import { z as z7 } from "zod";
|
|
389
434
|
|
|
390
435
|
// src/types/api.ts
|
|
391
436
|
var ISO_8601 = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -401,8 +446,16 @@ var SUPPORTED_MARKETPLACES = [
|
|
|
401
446
|
"it",
|
|
402
447
|
"co.jp"
|
|
403
448
|
];
|
|
449
|
+
var NICHE_KEYWORDS_MAX_PER_CALL = 100;
|
|
450
|
+
var NICHE_KEYWORD_MAX_LENGTH = 255;
|
|
451
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
|
|
452
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
|
|
404
453
|
var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
|
|
405
454
|
var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
|
|
455
|
+
var TAG_TITLE_MAX_LENGTH = 100;
|
|
456
|
+
var TAG_KEYWORD_IDS_MAX = 1e3;
|
|
457
|
+
var TAG_RESERVED_COLOR = "#EB5757";
|
|
458
|
+
var TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
406
459
|
var PPC_CAMPAIGN_BASE_SORT_BY = [
|
|
407
460
|
"name",
|
|
408
461
|
"state",
|
|
@@ -446,14 +499,14 @@ var LISTING_COPY_STRATEGIES = ["cosmo", "ranking-juice", "nlp", "cosmo-rufus"];
|
|
|
446
499
|
|
|
447
500
|
// src/tools/list-rank-radars.ts
|
|
448
501
|
var LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;
|
|
449
|
-
var
|
|
450
|
-
currentPage:
|
|
451
|
-
pageSize:
|
|
452
|
-
nicheId:
|
|
453
|
-
status:
|
|
502
|
+
var inputSchema7 = {
|
|
503
|
+
currentPage: z7.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
504
|
+
pageSize: z7.number().int().min(1).max(LIST_RANK_RADARS_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_RANK_RADARS_MAX_PAGE_SIZE}). Defaults to 20.`),
|
|
505
|
+
nicheId: z7.string().min(1).optional().describe("Filter Rank Radars by Niche identifier. Use after `list_niches`."),
|
|
506
|
+
status: z7.enum(RANK_RADAR_STATUSES).optional().describe(
|
|
454
507
|
"Which Rank Radars to return. ACTIVE (the default) are tracking; PAUSED have been stopped but keep their history and can be resumed; ARCHIVED have been deleted; ALL means active and paused together. Archived Rank Radars are returned only by an explicit ARCHIVED."
|
|
455
508
|
),
|
|
456
|
-
searchText:
|
|
509
|
+
searchText: z7.string().optional().describe("Filter Rank Radars by ASIN or product title.")
|
|
457
510
|
};
|
|
458
511
|
function minimize(item) {
|
|
459
512
|
const { asin, ...rest } = item;
|
|
@@ -463,7 +516,7 @@ var listRankRadarsTool = {
|
|
|
463
516
|
name: "list_rank_radars",
|
|
464
517
|
title: "List Rank Radars",
|
|
465
518
|
description: `Use this to find a \`rankRadarId\` before calling \`get_rank_radar_data\`. Filter by \`nicheId\` if the user has already identified a niche. Retrieves a paginated list of Rank Radars \u2014 keyword-rank trackers monitoring organic and sponsored positions for specific ASINs over time. Each item includes id, status (ACTIVE, PAUSED or ARCHIVED), asin, marketplace, keywordCount, title, imageUrl, and summary metrics: top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADARS_MAX_PAGE_SIZE} Rank Radars, so a large account needs several calls.`,
|
|
466
|
-
inputSchema:
|
|
519
|
+
inputSchema: inputSchema7,
|
|
467
520
|
annotations: { readOnlyHint: true },
|
|
468
521
|
handler: async (args, ctx) => {
|
|
469
522
|
const page = await httpGet(
|
|
@@ -482,18 +535,18 @@ var listRankRadarsTool = {
|
|
|
482
535
|
};
|
|
483
536
|
|
|
484
537
|
// src/tools/rank-radar-keyword-query.ts
|
|
485
|
-
import { z as
|
|
538
|
+
import { z as z8 } from "zod";
|
|
486
539
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
487
540
|
var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
|
|
488
541
|
var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
|
|
489
542
|
var rankRadarKeywordQueryInputSchema = {
|
|
490
|
-
rankRadarId:
|
|
491
|
-
startDate:
|
|
492
|
-
endDate:
|
|
543
|
+
rankRadarId: z8.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
|
|
544
|
+
startDate: z8.string().regex(ISO_DATE, "startDate must be in yyyy-mm-dd format").describe("Start date of the range, yyyy-mm-dd (e.g. 2024-03-26)."),
|
|
545
|
+
endDate: z8.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
|
|
493
546
|
`End date of the range, yyyy-mm-dd (e.g. 2024-04-26). Must be on or after startDate, and at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days after it.`
|
|
494
547
|
),
|
|
495
|
-
currentPage:
|
|
496
|
-
pageSize:
|
|
548
|
+
currentPage: z8.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
|
|
549
|
+
pageSize: z8.number().int().min(1).max(RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE).optional().describe(`Keywords per page (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
497
550
|
};
|
|
498
551
|
var RANK_RADAR_KEYWORD_PAGING_NOTE = `Results are paged by keyword: \`data\` holds one page, and the response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev. \`total\` is the Rank Radar's active keyword count; to read every keyword, call again with \`currentPage\` + 1 while \`hasNext\` is true, using \`pageSize\` ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE} for large Rank Radars. Paused keywords are not included.`;
|
|
499
552
|
function rankRadarPath(rankRadarId, suffix = "") {
|
|
@@ -509,7 +562,7 @@ function rankRadarKeywordQuery(args) {
|
|
|
509
562
|
}
|
|
510
563
|
|
|
511
564
|
// src/tools/get-rank-radar-data.ts
|
|
512
|
-
var
|
|
565
|
+
var inputSchema8 = rankRadarKeywordQueryInputSchema;
|
|
513
566
|
async function readAllKeywords(args, ctx) {
|
|
514
567
|
const request = { config: ctx.config, toolName: "get_rank_radar_data" };
|
|
515
568
|
const path = rankRadarPath(args.rankRadarId);
|
|
@@ -538,8 +591,8 @@ async function readAllKeywords(args, ctx) {
|
|
|
538
591
|
var getRankRadarDataTool = {
|
|
539
592
|
name: "get_rank_radar_data",
|
|
540
593
|
title: "Get Keyword Rankings for a Rank Radar",
|
|
541
|
-
description: `Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. Retrieves historical keyword ranking data for the specified Rank Radar within the date range. Without \`currentPage\` and \`pageSize\` it returns every active keyword in one result. To read one page instead \u2014 faster for large Rank Radars \u2014 pass \`currentPage\` and/or \`pageSize\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) and continue with \`currentPage\` + 1 while \`hasNext\` is true. The response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev; \`total\` is the Rank Radar's active keyword count. Paused keywords are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and
|
|
542
|
-
inputSchema:
|
|
594
|
+
description: `Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. Retrieves historical keyword ranking data for the specified Rank Radar within the date range. Without \`currentPage\` and \`pageSize\` it returns every active keyword in one result. To read one page instead \u2014 faster for large Rank Radars \u2014 pass \`currentPage\` and/or \`pageSize\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) and continue with \`currentPage\` + 1 while \`hasNext\` is true. The response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev; \`total\` is the Rank Radar's active keyword count. Paused keywords are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and \`highlights\` \u2014 the user tags on this keyword. Each \`highlights\` entry is the same object \`list_rank_radar_tags\` returns (id, title, color, description, startAt, endAt, type, krtId, krtKeywordId); its \`id\` is the \`tagId\` the tag-management tools take. A rank of 101 means the product was not in the top 100 results that day. For PPC metrics use \`get_rank_radar_ppc_data\`; for Search Query Performance use \`get_rank_radar_sqp_data\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
|
|
595
|
+
inputSchema: inputSchema8,
|
|
543
596
|
annotations: { readOnlyHint: true },
|
|
544
597
|
handler: async (args, ctx) => {
|
|
545
598
|
if (args.currentPage === void 0 && args.pageSize === void 0) {
|
|
@@ -554,12 +607,12 @@ var getRankRadarDataTool = {
|
|
|
554
607
|
};
|
|
555
608
|
|
|
556
609
|
// src/tools/get-rank-radar-sqp-data.ts
|
|
557
|
-
var
|
|
610
|
+
var inputSchema9 = rankRadarKeywordQueryInputSchema;
|
|
558
611
|
var getRankRadarSqpDataTool = {
|
|
559
612
|
name: "get_rank_radar_sqp_data",
|
|
560
613
|
title: "Get Search Query Performance for a Rank Radar",
|
|
561
614
|
description: `Use this when the user asks how shoppers search, click, add to cart and buy for the keywords of a Rank Radar \u2014 Amazon's Search Query Performance (SQP) data, from Brand Analytics. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are aggregated over the range: searchQueryVolume and searchQueryScore; impressions, clicks, cart adds and purchases, each as the total across all sellers (\`*TotalCount\`), this ASIN family's count (\`*AsinCount\`) and its share (\`*AsinShare\`); click, cart-add and purchase rates; and ctr/cvr for the market (\`*Total\`) and for this ASIN family (\`*Asin\`). \`numberOfDaysWithData\` says how many days of the range had SQP data; metrics are null when there is none, which is normal for low-volume keywords, recent dates (Amazon publishes SQP with a delay) and sellers without Brand Analytics. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited (about 60 requests/minute). Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
|
|
562
|
-
inputSchema:
|
|
615
|
+
inputSchema: inputSchema9,
|
|
563
616
|
annotations: { readOnlyHint: true },
|
|
564
617
|
handler: async (args, ctx) => {
|
|
565
618
|
return await httpGet(
|
|
@@ -571,10 +624,10 @@ var getRankRadarSqpDataTool = {
|
|
|
571
624
|
};
|
|
572
625
|
|
|
573
626
|
// src/tools/get-rank-radar-ppc-data.ts
|
|
574
|
-
import { z as
|
|
575
|
-
var
|
|
627
|
+
import { z as z9 } from "zod";
|
|
628
|
+
var inputSchema10 = {
|
|
576
629
|
...rankRadarKeywordQueryInputSchema,
|
|
577
|
-
includeCampaigns:
|
|
630
|
+
includeCampaigns: z9.boolean().optional().describe(
|
|
578
631
|
"When true, each keyword also carries `campaigns`: the per-campaign/ad-group breakdown (campaign and ad group name, match type, targeting, spend, sales, clicks, impressions, orders, CPC, CTR, CVR, ACOS). Defaults to false; leave it off unless the user asks which campaigns drive a keyword."
|
|
579
632
|
)
|
|
580
633
|
};
|
|
@@ -582,7 +635,7 @@ var getRankRadarPpcDataTool = {
|
|
|
582
635
|
name: "get_rank_radar_ppc_data",
|
|
583
636
|
title: "Get PPC Data for a Rank Radar",
|
|
584
637
|
description: `Use this when the user asks about advertising (Sponsored Products) performance for the keywords of a Rank Radar. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are aggregated over the range: sponsoredRank (median; 101 means not in the top 100 sponsored results), impressionRank and impressionRankShare; how many exact, phrase, broad and auto campaigns target it; organicSales, ppcSales, ppcSpend, costPerClicks, clickThroughRate, conversionRate, totalClicks, totalImpressions, totalOrders and acos. Metrics are null or 0 when the seller runs no ads on a keyword. Set \`includeCampaigns\` for the per-campaign breakdown. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited (about 60 requests/minute). For the seller's campaigns as a whole, use \`list_ppc_campaigns\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
|
|
585
|
-
inputSchema:
|
|
638
|
+
inputSchema: inputSchema10,
|
|
586
639
|
annotations: { readOnlyHint: true },
|
|
587
640
|
handler: async (args, ctx) => {
|
|
588
641
|
return await httpGet(
|
|
@@ -594,7 +647,7 @@ var getRankRadarPpcDataTool = {
|
|
|
594
647
|
};
|
|
595
648
|
|
|
596
649
|
// src/tools/create-rank-radar.ts
|
|
597
|
-
import { z as
|
|
650
|
+
import { z as z10 } from "zod";
|
|
598
651
|
|
|
599
652
|
// src/tools/confirm.ts
|
|
600
653
|
function needsConfirmation(confirm, ctx) {
|
|
@@ -615,11 +668,11 @@ function requireConfirmation(confirm, ctx, costNote) {
|
|
|
615
668
|
|
|
616
669
|
// src/tools/create-rank-radar.ts
|
|
617
670
|
var PATH = "/v1/niches/rank-radars";
|
|
618
|
-
var
|
|
619
|
-
asin:
|
|
620
|
-
numberOfKeywords:
|
|
621
|
-
nicheId:
|
|
622
|
-
confirm:
|
|
671
|
+
var inputSchema11 = {
|
|
672
|
+
asin: z10.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
673
|
+
numberOfKeywords: z10.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
674
|
+
nicheId: z10.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
|
|
675
|
+
confirm: z10.boolean().optional().describe(
|
|
623
676
|
"Must be true to proceed \u2014 creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first."
|
|
624
677
|
)
|
|
625
678
|
};
|
|
@@ -627,7 +680,7 @@ var createRankRadarTool = {
|
|
|
627
680
|
name: "create_rank_radar",
|
|
628
681
|
title: "Create a Rank Radar",
|
|
629
682
|
description: "Use this to start tracking organic and sponsored keyword rankings for an ASIN within a niche. \u26A0\uFE0F Spends Search Term tokens (cost scales with `numberOfKeywords`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and creates a separate Rank Radar \u2014 if a call errors or times out, check `list_rank_radars` instead of re-calling. The first call previews the creation and may return `warnings` alongside the cost note \u2014 for example that this product family is already tracked by another Rank Radar. Show every warning `message` to the user verbatim, before asking them to confirm; do not summarise it or decide on their behalf. A warning never blocks creation. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
|
|
630
|
-
inputSchema:
|
|
683
|
+
inputSchema: inputSchema11,
|
|
631
684
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
632
685
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
633
686
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -652,20 +705,20 @@ var createRankRadarTool = {
|
|
|
652
705
|
};
|
|
653
706
|
|
|
654
707
|
// src/tools/create-niche-dive.ts
|
|
655
|
-
import { z as
|
|
656
|
-
var
|
|
657
|
-
marketplace:
|
|
708
|
+
import { z as z11 } from "zod";
|
|
709
|
+
var inputSchema12 = {
|
|
710
|
+
marketplace: z11.enum(SUPPORTED_MARKETPLACES).describe(
|
|
658
711
|
"Amazon marketplace of the seed ASIN. Note these are full domain suffixes: com, ca, co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`)."
|
|
659
712
|
),
|
|
660
|
-
asin:
|
|
661
|
-
numberOfCompetitors:
|
|
662
|
-
confirm:
|
|
713
|
+
asin: z11.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
|
|
714
|
+
numberOfCompetitors: z11.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
|
|
715
|
+
confirm: z11.boolean().optional().describe("Must be true to proceed \u2014 creating a dive spends dive tokens. Confirm the cost with the user first.")
|
|
663
716
|
};
|
|
664
717
|
var createNicheDiveTool = {
|
|
665
718
|
name: "create_niche_dive",
|
|
666
719
|
title: "Create a Niche Dive",
|
|
667
720
|
description: "Use this to start new niche research from a seed ASIN. \u26A0\uFE0F Spends dive tokens (cost scales with `numberOfCompetitors`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate dive \u2014 if a call errors or times out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. The dive runs asynchronously: this returns immediately with a `diveId` and an `estimatedCompletionDate`. Poll `get_dive_status` with that `diveId` until it reports `success` (which carries the new `nicheId` for use with `list_niches`, `get_niche_keywords`, etc.). Requires `marketplace`, `asin`, and `numberOfCompetitors` (min 2).",
|
|
668
|
-
inputSchema:
|
|
721
|
+
inputSchema: inputSchema12,
|
|
669
722
|
// destructiveHint is technically "additive" here (a dive creates a new niche, it
|
|
670
723
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
671
724
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -690,20 +743,20 @@ var createNicheDiveTool = {
|
|
|
690
743
|
};
|
|
691
744
|
|
|
692
745
|
// src/tools/create-niche-dive-from-competitors-list.ts
|
|
693
|
-
import { z as
|
|
746
|
+
import { z as z12 } from "zod";
|
|
694
747
|
var DIVE_FROM_COMPETITORS_MIN_ASINS = 2;
|
|
695
748
|
var DIVE_FROM_COMPETITORS_MAX_ASINS = 200;
|
|
696
|
-
var normalizedAsins =
|
|
749
|
+
var normalizedAsins = z12.array(z12.string().trim().min(1).toUpperCase()).min(DIVE_FROM_COMPETITORS_MIN_ASINS).max(DIVE_FROM_COMPETITORS_MAX_ASINS).refine((asins) => new Set(asins).size === asins.length, {
|
|
697
750
|
message: "asins must not contain the same ASIN twice"
|
|
698
751
|
});
|
|
699
|
-
var
|
|
700
|
-
marketplace:
|
|
752
|
+
var inputSchema13 = {
|
|
753
|
+
marketplace: z12.enum(SUPPORTED_MARKETPLACES).describe(
|
|
701
754
|
"Amazon marketplace where the ASINs are listed. Full domain suffixes: com, ca, co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`)."
|
|
702
755
|
),
|
|
703
756
|
asins: normalizedAsins.describe(
|
|
704
757
|
`The exact competitor ASINs to build the niche from, ${DIVE_FROM_COMPETITORS_MIN_ASINS} to ${DIVE_FROM_COMPETITORS_MAX_ASINS}, each once. No other competitors are added.`
|
|
705
758
|
),
|
|
706
|
-
confirm:
|
|
759
|
+
confirm: z12.boolean().optional().describe(
|
|
707
760
|
"Must be true to proceed \u2014 this dive spends dive tokens for every ASIN. Confirm the cost with the user first."
|
|
708
761
|
)
|
|
709
762
|
};
|
|
@@ -711,7 +764,7 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
711
764
|
name: "create_niche_dive_from_competitors_list",
|
|
712
765
|
title: "Create a Niche Dive from a Competitor List",
|
|
713
766
|
description: `Use this when the user already knows which products belong in the niche \u2014 e.g. they list competitor ASINs, or picked them from search results \u2014 and wants niche research on exactly those. Unlike \`create_niche_dive\`, which discovers competitors around one seed ASIN, this builds the niche from the given ASINs only (${DIVE_FROM_COMPETITORS_MIN_ASINS}-${DIVE_FROM_COMPETITORS_MAX_ASINS}). \u26A0\uFE0F Spends dive tokens for every ASIN and cannot be undone \u2014 set \`confirm: true\` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate dive \u2014 if a call errors or times out, poll \`get_dive_status\` (or check \`list_niches\`) instead of re-calling. Fails without spending tokens when the ASIN count is over the plan's limit, when the account is at its niche limit, or when Amazon product data cannot be found for enough of the ASINs. The dive runs asynchronously: this returns a \`diveId\` and an \`estimatedCompletionDate\`; poll \`get_dive_status\` with the \`diveId\` until it reports \`success\`, which carries the new \`nicheId\`.`,
|
|
714
|
-
inputSchema:
|
|
767
|
+
inputSchema: inputSchema13,
|
|
715
768
|
// Additive rather than destructive, but flagged destructive so clients prompt before the
|
|
716
769
|
// irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.
|
|
717
770
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -731,23 +784,23 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
731
784
|
};
|
|
732
785
|
|
|
733
786
|
// src/tools/redive-niche.ts
|
|
734
|
-
import { z as
|
|
735
|
-
var
|
|
736
|
-
nicheId:
|
|
737
|
-
mode:
|
|
787
|
+
import { z as z13 } from "zod";
|
|
788
|
+
var inputSchema14 = {
|
|
789
|
+
nicheId: z13.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
|
|
790
|
+
mode: z13.enum(["same_competitors", "discover"]).describe(
|
|
738
791
|
"How to pick the competitors for the refreshed dive. `same_competitors` re-runs the niche's current competitor set and takes no other argument \u2014 use it to refresh stale data. `discover` searches for a fresh competitor set, which is what you want when the niche has changed."
|
|
739
792
|
),
|
|
740
|
-
numberOfCompetitors:
|
|
793
|
+
numberOfCompetitors: z13.number().int().min(2).optional().describe(
|
|
741
794
|
"Total ASINs the refreshed dive should contain, including `lockedAsins`. Required in `discover` mode and rejected in `same_competitors` mode. Dive tokens are spent per ASIN."
|
|
742
795
|
),
|
|
743
|
-
heroAsin:
|
|
796
|
+
heroAsin: z13.string().min(1).optional().describe(
|
|
744
797
|
"`discover` mode only. Seed product the competitor discovery starts from. Defaults to the niche's highest-selling competitor, preferring one the user's own connected seller account owns."
|
|
745
798
|
),
|
|
746
|
-
lockedAsins:
|
|
799
|
+
lockedAsins: z13.array(z13.string().min(1)).optional().describe(
|
|
747
800
|
"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
|
|
748
801
|
),
|
|
749
|
-
excludedAsins:
|
|
750
|
-
confirm:
|
|
802
|
+
excludedAsins: z13.array(z13.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
|
|
803
|
+
confirm: z13.boolean().optional().describe(
|
|
751
804
|
"Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
|
|
752
805
|
)
|
|
753
806
|
};
|
|
@@ -755,7 +808,7 @@ var rediveNicheTool = {
|
|
|
755
808
|
name: "redive_niche",
|
|
756
809
|
title: "Re-dive an Existing Niche",
|
|
757
810
|
description: "Use this to refresh an existing niche's research with current Amazon data, instead of creating a new niche with `create_niche_dive`. \u26A0\uFE0F Spends dive tokens (one batch per ASIN dived) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate re-dive \u2014 if a call errors or times out, poll `get_dive_status` instead of re-calling. Two modes: `same_competitors` re-dives the niche's current competitor set (no other argument needed) and `discover` finds a fresh set, sized by `numberOfCompetitors` and steerable with `heroAsin` / `lockedAsins` / `excludedAsins`. Runs asynchronously: returns a `diveId` and an `estimatedCompletionDate` \u2014 poll `get_dive_status` with that `diveId` until it reports `success`. The niche keeps its `nicheId`, so existing rank radars and reports follow the refreshed data.",
|
|
758
|
-
inputSchema:
|
|
811
|
+
inputSchema: inputSchema14,
|
|
759
812
|
// Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
|
|
760
813
|
// irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
|
|
761
814
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -788,17 +841,17 @@ var rediveNicheTool = {
|
|
|
788
841
|
};
|
|
789
842
|
|
|
790
843
|
// src/tools/get-dive-status.ts
|
|
791
|
-
import { z as
|
|
792
|
-
var
|
|
793
|
-
diveId:
|
|
794
|
-
"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`."
|
|
844
|
+
import { z as z14 } from "zod";
|
|
845
|
+
var inputSchema15 = {
|
|
846
|
+
diveId: z14.string().min(1).describe(
|
|
847
|
+
"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`, or the `refresh.diveId` returned by `add_niche_keywords`."
|
|
795
848
|
)
|
|
796
849
|
};
|
|
797
850
|
var getDiveStatusTool = {
|
|
798
851
|
name: "get_dive_status",
|
|
799
852
|
title: "Get Niche Dive Status",
|
|
800
|
-
description: "Use this to poll a niche dive started with `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche` until it finishes. Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), `success` (with the `nicheId` plus `tokensUsed`/`tokensLeft`), or `error` (with an `error` message). On `success`, use the `nicheId` with `list_niches`, `get_niche_keywords`, `get_niche_competitors`, etc. (for a re-dive it is the same niche you passed in, now carrying refreshed data). Re-poll periodically \u2014 dives can take minutes; the `estimatedCompletionDate` hints at when to check.",
|
|
801
|
-
inputSchema:
|
|
853
|
+
description: "Use this to poll a niche dive started with `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`, or the re-dive `add_niche_keywords` started on a stale niche, until it finishes. Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), `success` (with the `nicheId` plus `tokensUsed`/`tokensLeft`), or `error` (with an `error` message). On `success`, use the `nicheId` with `list_niches`, `get_niche_keywords`, `get_niche_competitors`, etc. (for a re-dive it is the same niche you passed in, now carrying refreshed data). Re-poll periodically \u2014 dives can take minutes; the `estimatedCompletionDate` hints at when to check.",
|
|
854
|
+
inputSchema: inputSchema15,
|
|
802
855
|
annotations: { readOnlyHint: true },
|
|
803
856
|
handler: async (args, ctx) => {
|
|
804
857
|
return await httpGet(
|
|
@@ -808,11 +861,166 @@ var getDiveStatusTool = {
|
|
|
808
861
|
}
|
|
809
862
|
};
|
|
810
863
|
|
|
864
|
+
// src/tools/add-niche-keywords.ts
|
|
865
|
+
import { z as z16 } from "zod";
|
|
866
|
+
|
|
867
|
+
// src/tools/niche-keyword-list.ts
|
|
868
|
+
import { z as z15 } from "zod";
|
|
869
|
+
var nicheKeyword = () => z15.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);
|
|
870
|
+
function uniqueKeywords(schema, field) {
|
|
871
|
+
return schema.refine((keywords) => new Set(keywords).size === keywords.length, {
|
|
872
|
+
message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
var nicheKeywordList = uniqueKeywords(
|
|
876
|
+
z15.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),
|
|
877
|
+
"keywords"
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
// src/tools/add-niche-keywords.ts
|
|
881
|
+
var inputSchema16 = {
|
|
882
|
+
nicheId: z16.string().min(1).describe("The niche whose Master Keyword List to add to. Get one from `list_niches`."),
|
|
883
|
+
keywords: nicheKeywordList.describe(
|
|
884
|
+
`The keywords to add, 1\u2013${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool allows only 2 calls per minute per niche.`
|
|
885
|
+
),
|
|
886
|
+
confirm: z16.boolean().optional().describe(
|
|
887
|
+
"Leave unset on the first call. Set to true only after the user approved the Dive token cost from a `confirmation_required` response \u2014 it lets the add re-dive a stale niche."
|
|
888
|
+
),
|
|
889
|
+
maxTokensUsed: z16.number().int().min(0).optional().describe(
|
|
890
|
+
"With `confirm: true`: the `tokenCost` the user approved. The call then fails with nothing saved or charged rather than spend more. Ignored without `confirm`, where the tool never spends anything."
|
|
891
|
+
)
|
|
892
|
+
};
|
|
893
|
+
function isMaxTokensExceededBody(body) {
|
|
894
|
+
if (!body || typeof body !== "object") return false;
|
|
895
|
+
const b = body;
|
|
896
|
+
return b.error === "MAX_TOKENS_EXCEEDED" && typeof b.tokensUsed === "number";
|
|
897
|
+
}
|
|
898
|
+
var addNicheKeywordsTool = {
|
|
899
|
+
name: "add_niche_keywords",
|
|
900
|
+
title: "Add Keywords to a Niche's Master Keyword List",
|
|
901
|
+
description: "Use this to add keywords to a niche's Master Keyword List (MKL): the user's own search terms, or keywords from the niche's Outliers or Residue lists. Free while the niche is fresh (dived in the last 24 hours, or a dive is running): the first call adds the keywords at once and they are readable with `get_niche_keywords`. On a stale niche, any keyword new to the niche makes the add re-dive the whole niche with its current competitors, which spends one Dive token per competitor. The first call then saves and spends nothing and returns `confirmation_required` with `tokenCost`; after the user approves, call again with `confirm: true` and `maxTokensUsed` set to that `tokenCost`. Keywords moved in from Outliers or Residue never cost anything. Returns `originBreakdown` (`alreadyInMkl`, `takenFromOutliers`, `takenFromResidue`, `addedManually`) and `tokensUsed`. When `refresh` is present a re-dive is running: poll `get_dive_status` with `refresh.diveId`; the `addedManually` keywords show in the MKL once it succeeds. A confirmed call is not safe to retry: if it errors or times out, poll `get_dive_status` or re-read `get_niche_keywords` instead of re-calling. Limited to 2 calls per minute per niche, so batch keywords into one call. Undo with `remove_niche_keywords`. A keyword hidden by the niche's keyword exclusions stays hidden after an add: check `list_keyword_exclusions` and remove the exclusion with `remove_keyword_exclusions` first.",
|
|
902
|
+
inputSchema: inputSchema16,
|
|
903
|
+
// Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients
|
|
904
|
+
// need to prompt on. The confirm gate covers the paid path regardless.
|
|
905
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
906
|
+
handler: async (args, ctx) => {
|
|
907
|
+
const request = { config: ctx.config, toolName: "add_niche_keywords" };
|
|
908
|
+
const path = `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`;
|
|
909
|
+
if (needsConfirmation(args.confirm, ctx)) {
|
|
910
|
+
try {
|
|
911
|
+
return await httpPost(request, path, {
|
|
912
|
+
keywords: args.keywords,
|
|
913
|
+
maxTokensUsed: 0
|
|
914
|
+
});
|
|
915
|
+
} catch (err) {
|
|
916
|
+
if (err instanceof ApiError && isMaxTokensExceededBody(err.body)) {
|
|
917
|
+
const tokenCost = err.body.tokensUsed;
|
|
918
|
+
return {
|
|
919
|
+
...confirmationRequired(
|
|
920
|
+
`This niche was last dived more than 24 hours ago, so adding keywords that are new to it re-dives the whole niche with its current competitors: up to ${tokenCost} Dive tokens, one per competitor (less if some can no longer be found). Nothing was added or charged yet.`
|
|
921
|
+
),
|
|
922
|
+
tokenCost
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
throw err;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return await httpPost(request, path, {
|
|
929
|
+
keywords: args.keywords,
|
|
930
|
+
maxTokensUsed: args.maxTokensUsed
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// src/tools/remove-niche-keywords.ts
|
|
936
|
+
import { z as z17 } from "zod";
|
|
937
|
+
var inputSchema17 = {
|
|
938
|
+
nicheId: z17.string().min(1).describe("The niche whose Master Keyword List to remove from. Get one from `list_niches`."),
|
|
939
|
+
keywords: nicheKeywordList.describe(
|
|
940
|
+
`The keywords to take out, 1\u2013${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool allows only 2 calls per minute per niche.`
|
|
941
|
+
)
|
|
942
|
+
};
|
|
943
|
+
var removeNicheKeywordsTool = {
|
|
944
|
+
name: "remove_niche_keywords",
|
|
945
|
+
title: "Remove Keywords from a Niche's Master Keyword List",
|
|
946
|
+
description: "Use this to undo `add_niche_keywords`: take keywords back out of a niche's Master Keyword List (MKL). Only keywords that were added to the MKL can be removed \u2014 `get_niche_keywords` shows each keyword's `origin`. What happens depends on that origin, and the response reports it per keyword: `removedManual` \u2014 added by hand (`manual`), deleted from the niche; `sentBackToOutliers` / `sentBackToResidue` \u2014 moved in from those lists (`outlier` / `residue`), listed there again with their data kept; `notRemovable` \u2014 found by the dive itself (`mkl`) or not in the niche, left unchanged. To hide a keyword the dive found, use `add_keyword_exclusions` instead. Free, and safe to repeat: a keyword already removed comes back under `notRemovable`. Adding a hand-added keyword back later re-dives the niche and costs Dive tokens if the niche is stale by then. Limited to 2 calls per minute per niche.",
|
|
947
|
+
inputSchema: inputSchema17,
|
|
948
|
+
// Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.
|
|
949
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
950
|
+
handler: async (args, ctx) => {
|
|
951
|
+
return await httpPost(
|
|
952
|
+
{ config: ctx.config, toolName: "remove_niche_keywords" },
|
|
953
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keywords/remove`,
|
|
954
|
+
{ keywords: args.keywords }
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/tools/keyword-exclusions-input.ts
|
|
960
|
+
import { z as z18 } from "zod";
|
|
961
|
+
var entries = (field) => uniqueKeywords(z18.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();
|
|
962
|
+
var keywordExclusionsInputSchema = (verb) => ({
|
|
963
|
+
nicheId: z18.string().min(1).describe("The niche whose keyword exclusions to change. Get one from `list_niches`."),
|
|
964
|
+
exact: entries("exact").describe(
|
|
965
|
+
`Exact-match entries to ${verb}: each one hides the single keyword that matches it exactly. At most 255 characters each; lower-cased and trimmed, and must be unique after that.`
|
|
966
|
+
),
|
|
967
|
+
phrase: entries("phrase").describe(
|
|
968
|
+
`Phrase-match entries to ${verb}: each one hides every keyword that contains the phrase (e.g. "puppy" hides "puppy hat" and "hat for puppy"). At most 255 characters each; lower-cased and trimmed, and must be unique after that.`
|
|
969
|
+
)
|
|
970
|
+
});
|
|
971
|
+
function assertExclusionCount(exact, phrase) {
|
|
972
|
+
const total = (exact?.length ?? 0) + (phrase?.length ?? 0);
|
|
973
|
+
if (total === 0 || total > KEYWORD_EXCLUSIONS_MAX_PER_CALL) {
|
|
974
|
+
throw new ApiError(
|
|
975
|
+
"bad_request",
|
|
976
|
+
400,
|
|
977
|
+
`Send 1 to ${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\` (got ${total}). Split a longer list over several calls.`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/tools/add-keyword-exclusions.ts
|
|
983
|
+
var inputSchema18 = keywordExclusionsInputSchema("exclude");
|
|
984
|
+
var addKeywordExclusionsTool = {
|
|
985
|
+
name: "add_keyword_exclusions",
|
|
986
|
+
title: "Exclude Keywords from a Niche",
|
|
987
|
+
description: `Use this to hide irrelevant keywords from a niche's Master Keyword List \u2014 e.g. other brands, wrong product types. \`exact\` entries hide the one keyword that matches exactly; \`phrase\` entries hide every keyword containing the phrase, so prefer \`exact\` unless the user wants a whole family of keywords gone. The excluded keywords drop out of \`get_niche_keywords\` at once, wherever they come from: the dive, Outliers, Residue, or ones added with \`add_niche_keywords\`. Nothing is deleted: \`remove_keyword_exclusions\` brings them back. Only the exclusions change \u2014 every other niche setting is kept. Send 1\u2013${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\`; a niche can hold at most ${KEYWORD_EXCLUSIONS_MAX_PER_NICHE} exclusions. Free, no tokens, and safe to repeat: entries already excluded come back under \`unchanged\`. Returns \`added\`, \`unchanged\` and the full \`exact\` / \`phrase\` lists after the change. Limited to 30 calls per minute per niche, so batch entries into one call.`,
|
|
988
|
+
inputSchema: inputSchema18,
|
|
989
|
+
// Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.
|
|
990
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
991
|
+
handler: async (args, ctx) => {
|
|
992
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
993
|
+
return await httpPost(
|
|
994
|
+
{ config: ctx.config, toolName: "add_keyword_exclusions" },
|
|
995
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,
|
|
996
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
// src/tools/remove-keyword-exclusions.ts
|
|
1002
|
+
var inputSchema19 = keywordExclusionsInputSchema("stop excluding");
|
|
1003
|
+
var removeKeywordExclusionsTool = {
|
|
1004
|
+
name: "remove_keyword_exclusions",
|
|
1005
|
+
title: "Remove Keyword Exclusions from a Niche",
|
|
1006
|
+
description: `Use this to undo \`add_keyword_exclusions\`, so the keywords an exclusion hid show in the niche's Master Keyword List (\`get_niche_keywords\`) again. Pass the entries exactly as \`list_keyword_exclusions\` shows them, \`exact\` and \`phrase\` separately: removing a phrase does not touch an exact entry for the same text, and a keyword also hidden by another remaining exclusion stays hidden. Only the exclusions change \u2014 every other niche setting is kept. Send 1\u2013${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\`. Free, and safe to repeat: entries that are not excluded come back under \`unchanged\`. Returns \`removed\`, \`unchanged\` and the full \`exact\` / \`phrase\` lists after the change. Limited to 30 calls per minute per niche.`,
|
|
1007
|
+
inputSchema: inputSchema19,
|
|
1008
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1009
|
+
handler: async (args, ctx) => {
|
|
1010
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
1011
|
+
return await httpPost(
|
|
1012
|
+
{ config: ctx.config, toolName: "remove_keyword_exclusions" },
|
|
1013
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions/remove`,
|
|
1014
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
|
|
811
1019
|
// src/tools/delete-niche.ts
|
|
812
|
-
import { z as
|
|
813
|
-
var
|
|
814
|
-
nicheId:
|
|
815
|
-
confirm:
|
|
1020
|
+
import { z as z19 } from "zod";
|
|
1021
|
+
var inputSchema20 = {
|
|
1022
|
+
nicheId: z19.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
|
|
1023
|
+
confirm: z19.boolean().optional().describe(
|
|
816
1024
|
"Must be true to proceed \u2014 deleting a niche destroys its keywords, competitors and dive history permanently. Confirm with the user first."
|
|
817
1025
|
)
|
|
818
1026
|
};
|
|
@@ -820,7 +1028,7 @@ var deleteNicheTool = {
|
|
|
820
1028
|
name: "delete_niche",
|
|
821
1029
|
title: "Delete a Niche",
|
|
822
1030
|
description: "Use this to permanently remove a niche the user no longer needs. \u26A0\uFE0F Deletes the niche and everything attached to it \u2014 keywords, competitors, all dives and diveboxes \u2014 and cannot be undone; the dive tokens already spent on it are NOT refunded. Set `confirm: true` only after the user approves. A niche used by any Rank Radar cannot be deleted: the call fails with a conflict, so delete or archive those rank radars first (`list_rank_radars` with the `nicheId` filter shows them).",
|
|
823
|
-
inputSchema:
|
|
1031
|
+
inputSchema: inputSchema20,
|
|
824
1032
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
825
1033
|
handler: async (args, ctx) => {
|
|
826
1034
|
const pending = requireConfirmation(
|
|
@@ -838,15 +1046,15 @@ var deleteNicheTool = {
|
|
|
838
1046
|
};
|
|
839
1047
|
|
|
840
1048
|
// src/tools/pause-rank-radar.ts
|
|
841
|
-
import { z as
|
|
842
|
-
var
|
|
843
|
-
rankRadarId:
|
|
1049
|
+
import { z as z20 } from "zod";
|
|
1050
|
+
var inputSchema21 = {
|
|
1051
|
+
rankRadarId: z20.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
|
|
844
1052
|
};
|
|
845
1053
|
var pauseRankRadarTool = {
|
|
846
1054
|
name: "pause_rank_radar",
|
|
847
1055
|
title: "Pause a Rank Radar",
|
|
848
1056
|
description: "Use this to pause tracking on a Rank Radar without losing anything \u2014 the action the DataDive API calls 'archiving', so when a user asks to archive a Rank Radar, this is the tool they mean. Tracking stops for all of its keywords and the Daily Tracked Keywords quota they held is freed for other Rank Radars, while the Rank Radar and its ranking history are kept. Fully reversible with `resume_rank_radar`, so it needs no `confirm` \u2014 prefer it over `delete_rank_radar` whenever the user may want the data back. Paused Rank Radars show up in `list_rank_radars` under `status: PAUSED` \u2014 not `ARCHIVED`, which covers Rank Radars removed with `delete_rank_radar`. Safe to repeat: pausing an already paused Rank Radar succeeds and changes nothing.",
|
|
849
|
-
inputSchema:
|
|
1057
|
+
inputSchema: inputSchema21,
|
|
850
1058
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
851
1059
|
handler: async (args, ctx) => {
|
|
852
1060
|
await httpPost(
|
|
@@ -859,15 +1067,15 @@ var pauseRankRadarTool = {
|
|
|
859
1067
|
};
|
|
860
1068
|
|
|
861
1069
|
// src/tools/resume-rank-radar.ts
|
|
862
|
-
import { z as
|
|
863
|
-
var
|
|
864
|
-
rankRadarId:
|
|
1070
|
+
import { z as z21 } from "zod";
|
|
1071
|
+
var inputSchema22 = {
|
|
1072
|
+
rankRadarId: z21.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
|
|
865
1073
|
};
|
|
866
1074
|
var resumeRankRadarTool = {
|
|
867
1075
|
name: "resume_rank_radar",
|
|
868
1076
|
title: "Resume a Paused Rank Radar",
|
|
869
1077
|
description: "Use this to restart tracking on a Rank Radar that was paused with `pause_rank_radar` (shown by `list_rank_radars` under `status: PAUSED`; a deleted, `ARCHIVED` one cannot be resumed). Keywords are resumed as far as the available Daily Tracked Keywords quota allows, most relevant first \u2014 so with a tight quota only part of the original keyword set comes back; check `get_quota` first if that matters. Reversible with `pause_rank_radar`, so it needs no `confirm`. Fails with a bad-request error when there is no quota left at all. Safe to repeat: resuming an already active Rank Radar changes nothing.",
|
|
870
|
-
inputSchema:
|
|
1078
|
+
inputSchema: inputSchema22,
|
|
871
1079
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
872
1080
|
handler: async (args, ctx) => {
|
|
873
1081
|
await httpPost(
|
|
@@ -880,10 +1088,10 @@ var resumeRankRadarTool = {
|
|
|
880
1088
|
};
|
|
881
1089
|
|
|
882
1090
|
// src/tools/delete-rank-radar.ts
|
|
883
|
-
import { z as
|
|
884
|
-
var
|
|
885
|
-
rankRadarId:
|
|
886
|
-
confirm:
|
|
1091
|
+
import { z as z22 } from "zod";
|
|
1092
|
+
var inputSchema23 = {
|
|
1093
|
+
rankRadarId: z22.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
|
|
1094
|
+
confirm: z22.boolean().optional().describe(
|
|
887
1095
|
"Must be true to proceed \u2014 deleting a Rank Radar destroys its ranking history permanently. Confirm with the user first."
|
|
888
1096
|
)
|
|
889
1097
|
};
|
|
@@ -891,7 +1099,7 @@ var deleteRankRadarTool = {
|
|
|
891
1099
|
name: "delete_rank_radar",
|
|
892
1100
|
title: "Delete a Rank Radar",
|
|
893
1101
|
description: "Use this to permanently remove a Rank Radar and all of its keyword ranking history. \u26A0\uFE0F Cannot be undone \u2014 there is no restore endpoint; set `confirm: true` only after the user approves. It does free the Daily Tracked Keywords quota those keywords held. Deleted Rank Radars are what `list_rank_radars` returns for `status: ARCHIVED`. If the user only wants to stop tracking for a while and keep the history \u2014 or asks to 'archive' the Rank Radar, which is what the DataDive API calls pausing \u2014 use `pause_rank_radar` instead: it frees the same quota and is reversible with `resume_rank_radar`.",
|
|
894
|
-
inputSchema:
|
|
1102
|
+
inputSchema: inputSchema23,
|
|
895
1103
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
896
1104
|
handler: async (args, ctx) => {
|
|
897
1105
|
const pending = requireConfirmation(
|
|
@@ -909,10 +1117,10 @@ var deleteRankRadarTool = {
|
|
|
909
1117
|
};
|
|
910
1118
|
|
|
911
1119
|
// src/tools/add-rank-radar-search-terms.ts
|
|
912
|
-
import { z as
|
|
913
|
-
var
|
|
914
|
-
rankRadarId:
|
|
915
|
-
searchTerms:
|
|
1120
|
+
import { z as z23 } from "zod";
|
|
1121
|
+
var inputSchema24 = {
|
|
1122
|
+
rankRadarId: z23.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
|
|
1123
|
+
searchTerms: z23.array(z23.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
|
|
916
1124
|
`The keywords to start tracking, 1\u2013${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored special characters (@, #, _, parentheses, \u2026) are rejected.`
|
|
917
1125
|
)
|
|
918
1126
|
};
|
|
@@ -920,7 +1128,7 @@ var addRankRadarSearchTermsTool = {
|
|
|
920
1128
|
name: "add_rank_radar_search_terms",
|
|
921
1129
|
title: "Add Search Terms to a Rank Radar",
|
|
922
1130
|
description: "Use this to start tracking extra keywords on an existing Rank Radar, instead of creating a new one with `create_rank_radar`. New keywords are added to the niche and tracked from now on; any matching keyword that was previously paused is resumed. Each newly tracked keyword takes one Daily Tracked Keywords slot, and the call fails if the quota is exhausted \u2014 check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, which frees the slots again, so it needs no `confirm`. Returns `originBreakdown` (where each submitted term ended up) and `keywordToRankRadarKeywordIdMap`, whose ids are what the pause/resume search-term tools take.",
|
|
923
|
-
inputSchema:
|
|
1131
|
+
inputSchema: inputSchema24,
|
|
924
1132
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
925
1133
|
handler: async (args, ctx) => {
|
|
926
1134
|
return await httpPost(
|
|
@@ -932,10 +1140,10 @@ var addRankRadarSearchTermsTool = {
|
|
|
932
1140
|
};
|
|
933
1141
|
|
|
934
1142
|
// src/tools/pause-rank-radar-search-terms.ts
|
|
935
|
-
import { z as
|
|
936
|
-
var
|
|
937
|
-
rankRadarId:
|
|
938
|
-
rankRadarKeywordIds:
|
|
1143
|
+
import { z as z24 } from "zod";
|
|
1144
|
+
var inputSchema25 = {
|
|
1145
|
+
rankRadarId: z24.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1146
|
+
rankRadarKeywordIds: z24.array(z24.string().uuid()).min(1).describe(
|
|
939
1147
|
"The Rank Radar keyword ids to pause. These are the `id` values from `get_rank_radar_data`, or the values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` \u2014 not the keyword text."
|
|
940
1148
|
)
|
|
941
1149
|
};
|
|
@@ -943,7 +1151,7 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
943
1151
|
name: "pause_rank_radar_search_terms",
|
|
944
1152
|
title: "Pause Search Terms of a Rank Radar",
|
|
945
1153
|
description: "Use this to stop tracking individual keywords on a Rank Radar while keeping the Rank Radar itself active \u2014 the way to trim a keyword set the user finds too broad. The DataDive API calls this 'archiving' the search terms; nothing is lost. The paused keywords keep their history and their Daily Tracked Keywords slots are freed for other keywords. Reversible with `resume_rank_radar_search_terms`, so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: already-paused terms are left unchanged.",
|
|
946
|
-
inputSchema:
|
|
1154
|
+
inputSchema: inputSchema25,
|
|
947
1155
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
948
1156
|
handler: async (args, ctx) => {
|
|
949
1157
|
await httpPost(
|
|
@@ -960,10 +1168,10 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
960
1168
|
};
|
|
961
1169
|
|
|
962
1170
|
// src/tools/resume-rank-radar-search-terms.ts
|
|
963
|
-
import { z as
|
|
964
|
-
var
|
|
965
|
-
rankRadarId:
|
|
966
|
-
rankRadarKeywordIds:
|
|
1171
|
+
import { z as z25 } from "zod";
|
|
1172
|
+
var inputSchema26 = {
|
|
1173
|
+
rankRadarId: z25.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1174
|
+
rankRadarKeywordIds: z25.array(z25.string().uuid()).min(1).describe(
|
|
967
1175
|
"The Rank Radar keyword ids to resume. These are the `id` values from `get_rank_radar_data`, or the values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` \u2014 not the keyword text."
|
|
968
1176
|
)
|
|
969
1177
|
};
|
|
@@ -971,7 +1179,7 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
971
1179
|
name: "resume_rank_radar_search_terms",
|
|
972
1180
|
title: "Resume Paused Search Terms of a Rank Radar",
|
|
973
1181
|
description: "Use this to start tracking keywords again that were paused with `pause_rank_radar_search_terms`. Each resumed keyword takes back one Daily Tracked Keywords slot, and the call fails if the quota is exhausted \u2014 check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: already-active terms are left unchanged.",
|
|
974
|
-
inputSchema:
|
|
1182
|
+
inputSchema: inputSchema26,
|
|
975
1183
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
976
1184
|
handler: async (args, ctx) => {
|
|
977
1185
|
await httpPost(
|
|
@@ -987,17 +1195,193 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
987
1195
|
}
|
|
988
1196
|
};
|
|
989
1197
|
|
|
1198
|
+
// src/tools/list-rank-radar-tags.ts
|
|
1199
|
+
import { z as z26 } from "zod";
|
|
1200
|
+
var LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;
|
|
1201
|
+
var inputSchema27 = {
|
|
1202
|
+
rankRadarId: z26.string().uuid().describe("The Rank Radar UUID whose tags to list (from `list_rank_radars`)."),
|
|
1203
|
+
currentPage: z26.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1204
|
+
pageSize: z26.number().int().min(1).max(LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
1205
|
+
};
|
|
1206
|
+
var listRankRadarTagsTool = {
|
|
1207
|
+
name: "list_rank_radar_tags",
|
|
1208
|
+
title: "List Tags of a Rank Radar",
|
|
1209
|
+
description: `Use this to find a \`tagId\` before calling \`update_rank_radar_tag\`, \`delete_rank_radar_tag\`, \`add_rank_radar_tag_search_terms\` or \`remove_rank_radar_tag_search_terms\`. Returns the user tags ("highlights") on the specified Rank Radar, ordered by title (case-insensitive, ascending). Each row carries \`id\`, \`title\`, \`color\`, \`description\`, \`startAt\`, \`endAt\`, \`createdAt\`, and \`keywordCount\` \u2014 the number of Rank Radar keywords this tag is applied to. The system "paused" tag (the one the quota pauser writes with a reserved colour) is not listed and cannot be addressed from these tools. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE} tags.`,
|
|
1210
|
+
inputSchema: inputSchema27,
|
|
1211
|
+
annotations: { readOnlyHint: true },
|
|
1212
|
+
handler: async (args, ctx) => {
|
|
1213
|
+
return await httpGet(
|
|
1214
|
+
{ config: ctx.config, toolName: "list_rank_radar_tags" },
|
|
1215
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
|
|
1216
|
+
{
|
|
1217
|
+
currentPage: args.currentPage,
|
|
1218
|
+
pageSize: args.pageSize
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// src/tools/create-rank-radar-tag.ts
|
|
1225
|
+
import { z as z27 } from "zod";
|
|
1226
|
+
var inputSchema28 = {
|
|
1227
|
+
rankRadarId: z27.string().uuid().describe("The Rank Radar UUID to create the tag on (from `list_rank_radars`)."),
|
|
1228
|
+
title: z27.string().min(1).max(TAG_TITLE_MAX_LENGTH).describe(
|
|
1229
|
+
`Display title (1\u2013${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this Rank Radar, case-insensitive. A clash returns 409 with \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\` \u2014 reuse that id instead of retrying.`
|
|
1230
|
+
),
|
|
1231
|
+
color: z27.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(
|
|
1232
|
+
`Hex colour in \`#RRGGBB\` form. Defaults to the UI grey when omitted. The reserved colour \`${TAG_RESERVED_COLOR}\` (used by the system "paused" tag) is rejected with 400.`
|
|
1233
|
+
),
|
|
1234
|
+
description: z27.string().optional().describe("Free-form description, e.g. 'Prime Day run-up'."),
|
|
1235
|
+
startAt: z27.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`."),
|
|
1236
|
+
endAt: z27.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`."),
|
|
1237
|
+
rankRadarKeywordIds: z27.array(z27.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).optional().describe(
|
|
1238
|
+
`Optional: Rank Radar keyword ids to tag on creation (1\u2013${TAG_KEYWORD_IDS_MAX}). These are the \`id\` values from \`get_rank_radar_data\`; the ids must all belong to this Rank Radar. To attach more later, use \`add_rank_radar_tag_search_terms\`.`
|
|
1239
|
+
)
|
|
1240
|
+
};
|
|
1241
|
+
var createRankRadarTagTool = {
|
|
1242
|
+
name: "create_rank_radar_tag",
|
|
1243
|
+
title: "Create a Rank Radar Tag",
|
|
1244
|
+
description: `Use this to create a new user tag ("highlight") on a Rank Radar \u2014 the way the UI marks a window on the ranking chart. Writing a tag costs nothing and is reversible with \`delete_rank_radar_tag\`, so no confirmation is required. Returns the new tag row (\`id\`, \`title\`, \`color\`, \`description\`, \`startAt\`, \`endAt\`, \`keywordCount\`, \`createdAt\`); the \`id\` is the \`tagId\` the other tag tools take. If the title clashes with an existing tag on this Rank Radar, the API answers 409 \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\` so callers can reuse the existing tag without a follow-up \`list_rank_radar_tags\`. The reserved colour \`${TAG_RESERVED_COLOR}\` is rejected with 400. Pass \`rankRadarKeywordIds\` to tag a batch of keywords in the same call; otherwise create the tag and attach keywords later with \`add_rank_radar_tag_search_terms\`.`,
|
|
1245
|
+
inputSchema: inputSchema28,
|
|
1246
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1247
|
+
handler: async (args, ctx) => {
|
|
1248
|
+
const body = { title: args.title };
|
|
1249
|
+
if (args.color !== void 0) body.color = args.color;
|
|
1250
|
+
if (args.description !== void 0) body.description = args.description;
|
|
1251
|
+
if (args.startAt !== void 0) body.startAt = args.startAt;
|
|
1252
|
+
if (args.endAt !== void 0) body.endAt = args.endAt;
|
|
1253
|
+
if (args.rankRadarKeywordIds !== void 0) body.rankRadarKeywordIds = args.rankRadarKeywordIds;
|
|
1254
|
+
return await httpPost(
|
|
1255
|
+
{ config: ctx.config, toolName: "create_rank_radar_tag" },
|
|
1256
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
|
|
1257
|
+
body
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// src/tools/update-rank-radar-tag.ts
|
|
1263
|
+
import { z as z28 } from "zod";
|
|
1264
|
+
var inputSchema29 = {
|
|
1265
|
+
rankRadarId: z28.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1266
|
+
tagId: z28.string().uuid().describe("The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`)."),
|
|
1267
|
+
title: z28.string().min(1).max(TAG_TITLE_MAX_LENGTH).optional().describe(
|
|
1268
|
+
`New title (1\u2013${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this Rank Radar, case-insensitive. A clash returns 409 with \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\`.`
|
|
1269
|
+
),
|
|
1270
|
+
color: z28.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(`Hex colour in \`#RRGGBB\` form. The reserved colour \`${TAG_RESERVED_COLOR}\` is rejected with 400.`),
|
|
1271
|
+
description: z28.string().optional().describe("Free-form description. Pass an empty string to clear."),
|
|
1272
|
+
startAt: z28.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`."),
|
|
1273
|
+
endAt: z28.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.")
|
|
1274
|
+
};
|
|
1275
|
+
var updateRankRadarTagTool = {
|
|
1276
|
+
name: "update_rank_radar_tag",
|
|
1277
|
+
title: "Update a Rank Radar Tag",
|
|
1278
|
+
description: "Use this to change a tag's title, colour, description, or date window. Partial update: only the fields you pass are modified, the rest are left as they are. The system \"paused\" tag (reserved colour, written by the quota pauser) cannot be addressed from here \u2014 the API answers 404. Writing a tag costs nothing and is reversible, so no confirmation is required. Returns the updated tag row. Title clashes return 409 `{ error: 'TAG_TITLE_EXISTS', existingTagId }`; the reserved colour returns 400; and `endAt < startAt` returns 400 (for whichever side the body omits, the comparison uses the stored value).",
|
|
1279
|
+
inputSchema: inputSchema29,
|
|
1280
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1281
|
+
handler: async (args, ctx) => {
|
|
1282
|
+
const body = {};
|
|
1283
|
+
if (args.title !== void 0) body.title = args.title;
|
|
1284
|
+
if (args.color !== void 0) body.color = args.color;
|
|
1285
|
+
if (args.description !== void 0) body.description = args.description;
|
|
1286
|
+
if (args.startAt !== void 0) body.startAt = args.startAt;
|
|
1287
|
+
if (args.endAt !== void 0) body.endAt = args.endAt;
|
|
1288
|
+
return await httpPatch(
|
|
1289
|
+
{ config: ctx.config, toolName: "update_rank_radar_tag" },
|
|
1290
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,
|
|
1291
|
+
body
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
// src/tools/delete-rank-radar-tag.ts
|
|
1297
|
+
import { z as z29 } from "zod";
|
|
1298
|
+
var inputSchema30 = {
|
|
1299
|
+
rankRadarId: z29.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1300
|
+
tagId: z29.string().uuid().describe("The tag UUID to delete (the `id` from `list_rank_radar_tags`)."),
|
|
1301
|
+
confirm: z29.boolean().optional().describe(
|
|
1302
|
+
"Must be true to proceed. Confirm with the user first \u2014 the tag's keyword assignments are removed too and the tag cannot be recovered."
|
|
1303
|
+
)
|
|
1304
|
+
};
|
|
1305
|
+
var deleteRankRadarTagTool = {
|
|
1306
|
+
name: "delete_rank_radar_tag",
|
|
1307
|
+
title: "Delete a Rank Radar Tag",
|
|
1308
|
+
description: 'Use this to permanently remove a tag from a Rank Radar. \u26A0\uFE0F The tag itself is gone and the assignments linking it to each tagged keyword are removed with it; the keywords themselves keep tracking. The deletion cannot be undone from this tool, so set `confirm: true` only after the user approves. The system "paused" tag (reserved colour, written by the quota pauser) cannot be addressed from here \u2014 the API answers 404. To remove just the assignments and keep the tag, use `remove_rank_radar_tag_search_terms` instead.',
|
|
1309
|
+
inputSchema: inputSchema30,
|
|
1310
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1311
|
+
handler: async (args, ctx) => {
|
|
1312
|
+
const pending = requireConfirmation(
|
|
1313
|
+
args.confirm,
|
|
1314
|
+
ctx,
|
|
1315
|
+
"Deleting this tag removes it from every keyword it was applied to. The tag cannot be recovered \u2014 if the user only wants to clear its keyword list, use `remove_rank_radar_tag_search_terms`."
|
|
1316
|
+
);
|
|
1317
|
+
if (pending) return pending;
|
|
1318
|
+
await httpDelete(
|
|
1319
|
+
{ config: ctx.config, toolName: "delete_rank_radar_tag" },
|
|
1320
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`
|
|
1321
|
+
);
|
|
1322
|
+
return { status: "deleted", rankRadarId: args.rankRadarId, tagId: args.tagId };
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
// src/tools/add-rank-radar-tag-search-terms.ts
|
|
1327
|
+
import { z as z30 } from "zod";
|
|
1328
|
+
var inputSchema31 = {
|
|
1329
|
+
rankRadarId: z30.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1330
|
+
tagId: z30.string().uuid().describe("The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`)."),
|
|
1331
|
+
rankRadarKeywordIds: z30.array(z30.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
|
|
1332
|
+
`Rank Radar keyword ids to attach to this tag (1\u2013${TAG_KEYWORD_IDS_MAX} per call, unique). These are the \`id\` values from \`get_rank_radar_data\`; all ids must belong to this Rank Radar. Safe to repeat: keywords already attached are left unchanged.`
|
|
1333
|
+
)
|
|
1334
|
+
};
|
|
1335
|
+
var addRankRadarTagSearchTermsTool = {
|
|
1336
|
+
name: "add_rank_radar_tag_search_terms",
|
|
1337
|
+
title: "Attach Search Terms to a Rank Radar Tag",
|
|
1338
|
+
description: `Use this to tag a batch of keywords on a Rank Radar \u2014 the same relation \`create_rank_radar_tag\` writes when \`rankRadarKeywordIds\` is passed on creation, so this is how you keep extending a tag's keyword set over time. Reversible with \`remove_rank_radar_tag_search_terms\`, so no confirmation is required. Idempotent: already-attached keywords are left unchanged, so re-sending the same ids after a timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call \u2014 split larger batches into multiple calls. The system "paused" tag (reserved colour, written by the quota pauser) is not addressable: the API answers 404.`,
|
|
1339
|
+
inputSchema: inputSchema31,
|
|
1340
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1341
|
+
handler: async (args, ctx) => {
|
|
1342
|
+
return await httpPost(
|
|
1343
|
+
{ config: ctx.config, toolName: "add_rank_radar_tag_search_terms" },
|
|
1344
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms`,
|
|
1345
|
+
{ rankRadarKeywordIds: args.rankRadarKeywordIds }
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
// src/tools/remove-rank-radar-tag-search-terms.ts
|
|
1351
|
+
import { z as z31 } from "zod";
|
|
1352
|
+
var inputSchema32 = {
|
|
1353
|
+
rankRadarId: z31.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1354
|
+
tagId: z31.string().uuid().describe("The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`)."),
|
|
1355
|
+
rankRadarKeywordIds: z31.array(z31.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
|
|
1356
|
+
`Rank Radar keyword ids to detach from this tag (1\u2013${TAG_KEYWORD_IDS_MAX} per call, unique). These are the \`id\` values from \`get_rank_radar_data\`. Safe to repeat: keywords that are not currently attached are left unchanged.`
|
|
1357
|
+
)
|
|
1358
|
+
};
|
|
1359
|
+
var removeRankRadarTagSearchTermsTool = {
|
|
1360
|
+
name: "remove_rank_radar_tag_search_terms",
|
|
1361
|
+
title: "Detach Search Terms from a Rank Radar Tag",
|
|
1362
|
+
description: `Use this to remove keywords from a tag without deleting the tag itself. The tag keeps its title, colour and date window; only the keyword assignments listed are cleared. Reversible with \`add_rank_radar_tag_search_terms\`, so no confirmation is required. Idempotent: ids that are not currently attached are left unchanged, so re-sending the same ids after a timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call \u2014 split larger batches into multiple calls. To remove the tag itself, use \`delete_rank_radar_tag\`. The system "paused" tag (reserved colour, written by the quota pauser) is not addressable: the API answers 404.`,
|
|
1363
|
+
inputSchema: inputSchema32,
|
|
1364
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1365
|
+
handler: async (args, ctx) => {
|
|
1366
|
+
return await httpPost(
|
|
1367
|
+
{ config: ctx.config, toolName: "remove_rank_radar_tag_search_terms" },
|
|
1368
|
+
`/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms/remove`,
|
|
1369
|
+
{ rankRadarKeywordIds: args.rankRadarKeywordIds }
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
|
|
990
1374
|
// src/tools/generate-listing-copy.ts
|
|
991
|
-
import { z as
|
|
992
|
-
var
|
|
993
|
-
nicheId:
|
|
994
|
-
strategy:
|
|
1375
|
+
import { z as z32 } from "zod";
|
|
1376
|
+
var inputSchema33 = {
|
|
1377
|
+
nicheId: z32.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
|
|
1378
|
+
strategy: z32.enum(LISTING_COPY_STRATEGIES).describe(
|
|
995
1379
|
"Which generation strategy to use. `cosmo` targets Amazon's COSMO relevance model, `ranking-juice` optimises for DataDive's ranking-juice score, `nlp` writes for keyword coverage, and `cosmo-rufus` targets COSMO plus the Rufus shopping assistant. Ask the user if they have no preference."
|
|
996
1380
|
),
|
|
997
|
-
currentListing:
|
|
1381
|
+
currentListing: z32.record(z32.union([z32.string(), z32.array(z32.string())])).describe(
|
|
998
1382
|
'The seller\'s existing listing to rewrite, as an object \u2014 e.g. `{ "title": "...", "description": "...", "bullets": ["...", "..."] }`. Pass what the user has; it is the starting point the generated copy builds on.'
|
|
999
1383
|
),
|
|
1000
|
-
confirm:
|
|
1384
|
+
confirm: z32.boolean().optional().describe(
|
|
1001
1385
|
"Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
|
|
1002
1386
|
)
|
|
1003
1387
|
};
|
|
@@ -1005,7 +1389,7 @@ var generateListingCopyTool = {
|
|
|
1005
1389
|
name: "generate_listing_copy",
|
|
1006
1390
|
title: "Generate Listing Copy for a Niche",
|
|
1007
1391
|
description: "Use this to draft an optimised Amazon listing \u2014 title, bullets and description \u2014 from a niche's keyword research and the seller's current listing. It writes text only: nothing is published to Amazon, and the user still has to paste the result into Seller Central. \u26A0\uFE0F Spends one AI Copywriter prompt from the quota per call and cannot be undone \u2014 set `confirm: true` only after the user approves. Not safe to retry: each call spends another prompt, so if a call errors or times out, poll `get_listing_copy_generation_status` instead of re-calling. Runs asynchronously: returns a `generationId` \u2014 poll `get_listing_copy_generation_status` with it until the status is `complete`.",
|
|
1008
|
-
inputSchema:
|
|
1392
|
+
inputSchema: inputSchema33,
|
|
1009
1393
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1010
1394
|
handler: async (args, ctx) => {
|
|
1011
1395
|
const pending = requireConfirmation(
|
|
@@ -1023,16 +1407,16 @@ var generateListingCopyTool = {
|
|
|
1023
1407
|
};
|
|
1024
1408
|
|
|
1025
1409
|
// src/tools/get-listing-copy-generation-status.ts
|
|
1026
|
-
import { z as
|
|
1027
|
-
var
|
|
1028
|
-
nicheId:
|
|
1029
|
-
generationId:
|
|
1410
|
+
import { z as z33 } from "zod";
|
|
1411
|
+
var inputSchema34 = {
|
|
1412
|
+
nicheId: z33.string().min(1).describe("The niche the generation was started for."),
|
|
1413
|
+
generationId: z33.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
|
|
1030
1414
|
};
|
|
1031
1415
|
var getListingCopyGenerationStatusTool = {
|
|
1032
1416
|
name: "get_listing_copy_generation_status",
|
|
1033
1417
|
title: "Get Listing Copy Generation Status",
|
|
1034
1418
|
description: "Use this to poll a listing-copy draft started with `generate_listing_copy` until it finishes. Returns one of three shapes keyed by `status`: `generating` (still running \u2014 poll again in a few seconds), `complete` (with `result`, carrying the generated `title`, `bullets`, `description`, `itemHighlights` and its `rankingJuice` score), or `failed` (with an `error` message). Polling is free \u2014 it does not spend another AI Copywriter prompt, so always poll rather than re-calling `generate_listing_copy`.",
|
|
1035
|
-
inputSchema:
|
|
1419
|
+
inputSchema: inputSchema34,
|
|
1036
1420
|
annotations: { readOnlyHint: true },
|
|
1037
1421
|
handler: async (args, ctx) => {
|
|
1038
1422
|
return await httpGet(
|
|
@@ -1043,16 +1427,16 @@ var getListingCopyGenerationStatusTool = {
|
|
|
1043
1427
|
};
|
|
1044
1428
|
|
|
1045
1429
|
// src/tools/list-seller-profiles.ts
|
|
1046
|
-
import { z as
|
|
1047
|
-
var
|
|
1048
|
-
currentPage:
|
|
1049
|
-
pageSize:
|
|
1430
|
+
import { z as z34 } from "zod";
|
|
1431
|
+
var inputSchema35 = {
|
|
1432
|
+
currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1433
|
+
pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1050
1434
|
};
|
|
1051
1435
|
var listSellerProfilesTool = {
|
|
1052
1436
|
name: "list_seller_profiles",
|
|
1053
1437
|
title: "List Connected Amazon Seller Profiles",
|
|
1054
1438
|
description: 'Use this when the user asks which Amazon seller accounts are connected, or as the discovery step to find the `sellerId` and `marketplace` required by `get_asin_inventory_distribution`, `get_seller_catalog`, `get_seller_listing_changes`, and the alert tools. Returns a paginated list of the organization\'s connected seller profiles \u2014 each item has sellerId, sellerName, marketplace (e.g. "com", "co.uk"), hasAdApi (whether Advertising API credentials are connected), and createdAt \u2014 plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).',
|
|
1055
|
-
inputSchema:
|
|
1439
|
+
inputSchema: inputSchema35,
|
|
1056
1440
|
annotations: { readOnlyHint: true },
|
|
1057
1441
|
handler: async (args, ctx) => {
|
|
1058
1442
|
return await httpGet(
|
|
@@ -1067,15 +1451,15 @@ var listSellerProfilesTool = {
|
|
|
1067
1451
|
};
|
|
1068
1452
|
|
|
1069
1453
|
// src/tools/get-seller-catalog.ts
|
|
1070
|
-
import { z as
|
|
1454
|
+
import { z as z36 } from "zod";
|
|
1071
1455
|
|
|
1072
1456
|
// src/tools/seller-scoped.ts
|
|
1073
|
-
import { z as
|
|
1457
|
+
import { z as z35 } from "zod";
|
|
1074
1458
|
var sellerScopedInputSchema = {
|
|
1075
|
-
sellerId:
|
|
1459
|
+
sellerId: z35.string().min(1).describe(
|
|
1076
1460
|
"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
1077
1461
|
),
|
|
1078
|
-
marketplace:
|
|
1462
|
+
marketplace: z35.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
|
|
1079
1463
|
};
|
|
1080
1464
|
function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
1081
1465
|
return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
|
|
@@ -1084,19 +1468,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
|
1084
1468
|
}
|
|
1085
1469
|
|
|
1086
1470
|
// src/tools/get-seller-catalog.ts
|
|
1087
|
-
var
|
|
1471
|
+
var inputSchema36 = {
|
|
1088
1472
|
...sellerScopedInputSchema,
|
|
1089
|
-
search:
|
|
1090
|
-
brand:
|
|
1091
|
-
status:
|
|
1092
|
-
currentPage:
|
|
1093
|
-
pageSize:
|
|
1473
|
+
search: z36.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1474
|
+
brand: z36.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1475
|
+
status: z36.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
|
|
1476
|
+
currentPage: z36.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1477
|
+
pageSize: z36.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1094
1478
|
};
|
|
1095
1479
|
var getSellerCatalogTool = {
|
|
1096
1480
|
name: "get_seller_catalog",
|
|
1097
1481
|
title: "List Catalog ASINs for a Seller",
|
|
1098
1482
|
description: "Use this when the user wants to browse or search a seller's own Amazon catalog \u2014 the products they sell on a given marketplace. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` to discover them). Filter by `search` (title/brand), `brand`, and `status` (Active by default, or all). Returns a paginated list where each item has asin, title, parentAsin, brand, status, imageUrl, and hasVariations, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
1099
|
-
inputSchema:
|
|
1483
|
+
inputSchema: inputSchema36,
|
|
1100
1484
|
annotations: { readOnlyHint: true },
|
|
1101
1485
|
handler: async (args, ctx) => {
|
|
1102
1486
|
return await httpGet(
|
|
@@ -1114,28 +1498,28 @@ var getSellerCatalogTool = {
|
|
|
1114
1498
|
};
|
|
1115
1499
|
|
|
1116
1500
|
// src/tools/get-seller-listing-changes.ts
|
|
1117
|
-
import { z as
|
|
1118
|
-
var
|
|
1501
|
+
import { z as z37 } from "zod";
|
|
1502
|
+
var inputSchema37 = {
|
|
1119
1503
|
...sellerScopedInputSchema,
|
|
1120
|
-
types:
|
|
1121
|
-
asin:
|
|
1122
|
-
brand:
|
|
1123
|
-
search:
|
|
1124
|
-
startDate:
|
|
1125
|
-
endDate:
|
|
1126
|
-
sortBy:
|
|
1127
|
-
sortOrder:
|
|
1128
|
-
includeCorrelations:
|
|
1504
|
+
types: z37.array(z37.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
|
|
1505
|
+
asin: z37.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
|
|
1506
|
+
brand: z37.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1507
|
+
search: z37.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1508
|
+
startDate: z37.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or after this time."),
|
|
1509
|
+
endDate: z37.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or before this time."),
|
|
1510
|
+
sortBy: z37.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
|
|
1511
|
+
sortOrder: z37.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
|
|
1512
|
+
includeCorrelations: z37.boolean().optional().describe(
|
|
1129
1513
|
"When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
|
|
1130
1514
|
),
|
|
1131
|
-
currentPage:
|
|
1132
|
-
pageSize:
|
|
1515
|
+
currentPage: z37.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1516
|
+
pageSize: z37.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1133
1517
|
};
|
|
1134
1518
|
var getSellerListingChangesTool = {
|
|
1135
1519
|
name: "get_seller_listing_changes",
|
|
1136
1520
|
title: "List Listing Changes for a Seller",
|
|
1137
1521
|
description: "Use this when the user asks what changed on their Amazon listings \u2014 price, content (title/bullets/description), or image edits \u2014 for a seller account. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` to discover them). Filter by `types`, `asin`/parent ASIN, `brand`, `search`, and a `startDate`/`endDate` range; sort with `sortBy`/`sortOrder`. Set `includeCorrelations: true` to attach the ranking/conversion impact per change. Returns a paginated list where each item has asin, title, imageUrl, date, type, contentType, description, previousValue, newValue, and (optionally) correlation, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
1138
|
-
inputSchema:
|
|
1522
|
+
inputSchema: inputSchema37,
|
|
1139
1523
|
annotations: { readOnlyHint: true },
|
|
1140
1524
|
handler: async (args, ctx) => {
|
|
1141
1525
|
return await httpGet(
|
|
@@ -1160,40 +1544,40 @@ var getSellerListingChangesTool = {
|
|
|
1160
1544
|
};
|
|
1161
1545
|
|
|
1162
1546
|
// src/tools/list-ppc-campaigns.ts
|
|
1163
|
-
import { z as
|
|
1547
|
+
import { z as z38 } from "zod";
|
|
1164
1548
|
var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
|
|
1165
1549
|
var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
|
|
1166
|
-
var
|
|
1550
|
+
var inputSchema38 = {
|
|
1167
1551
|
...sellerScopedInputSchema,
|
|
1168
|
-
startDate:
|
|
1552
|
+
startDate: z38.string().regex(
|
|
1169
1553
|
ISO_8601,
|
|
1170
1554
|
"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
|
|
1171
1555
|
).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
|
|
1172
|
-
endDate:
|
|
1556
|
+
endDate: z38.string().regex(
|
|
1173
1557
|
ISO_8601,
|
|
1174
1558
|
"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
|
|
1175
1559
|
).optional().describe(
|
|
1176
1560
|
`End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
|
|
1177
1561
|
),
|
|
1178
|
-
asin:
|
|
1179
|
-
parentAsin:
|
|
1180
|
-
search:
|
|
1181
|
-
includePlacements:
|
|
1562
|
+
asin: z38.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
|
|
1563
|
+
parentAsin: z38.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
|
|
1564
|
+
search: z38.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
|
|
1565
|
+
includePlacements: z38.boolean().optional().describe(
|
|
1182
1566
|
"Include the per-placement breakdown (topOfSearch, restOfSearch, productPage, offAmazon, each with its bid adjustment). Defaults to true; set false for a smaller response when placements are not needed."
|
|
1183
1567
|
),
|
|
1184
|
-
sortBy:
|
|
1568
|
+
sortBy: z38.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
|
|
1185
1569
|
`Sort field. Defaults to "name". Any of ${PPC_CAMPAIGN_BASE_SORT_BY.join(", ")}. The placement fields (e.g. "tosSpend", "tosAcos", "ppBidAdjustment") are only accepted when includePlacements is not false.`
|
|
1186
1570
|
),
|
|
1187
|
-
sortOrder:
|
|
1188
|
-
state:
|
|
1189
|
-
currentPage:
|
|
1190
|
-
pageSize:
|
|
1571
|
+
sortOrder: z38.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
|
|
1572
|
+
state: z38.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
|
|
1573
|
+
currentPage: z38.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1574
|
+
pageSize: z38.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
1191
1575
|
};
|
|
1192
1576
|
var listPpcCampaignsTool = {
|
|
1193
1577
|
name: "list_ppc_campaigns",
|
|
1194
1578
|
title: "List PPC Campaigns for a Seller",
|
|
1195
1579
|
description: `Use this when the user asks about their Sponsored Products campaigns \u2014 which campaigns spend the most, which have a high ACOS, how placements (top of search, rest of search, product pages) perform, or which campaigns advertise a given product. Requires a \`sellerId\` + \`marketplace\` (use \`list_seller_profiles\` to discover them). Narrow to one product with \`asin\`, or to a variation family with \`parentAsin\` (not both). The reporting window defaults to the last 30 days and cannot exceed ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days. Returns a paginated list where each campaign has campaignId, name, type, state, targetingType, bidStrategy, budget { type, amount, currencyCode }, window totals (impressions, clicks, ctr, cpc, cvr, spend, unitsSold, orders, sales, tosImpressionShare, acos, roas, tacos), asinCount and, unless includePlacements is false, \`placements\`; plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). To find the biggest spenders, sort with \`sortBy: "spend", sortOrder: "DESC"\` rather than paging through every campaign. Each call counts toward API usage and is rate limited (about 60 requests/minute). For per-keyword ad data of a Rank Radar, use \`get_rank_radar_ppc_data\`.`,
|
|
1196
|
-
inputSchema:
|
|
1580
|
+
inputSchema: inputSchema38,
|
|
1197
1581
|
annotations: { readOnlyHint: true },
|
|
1198
1582
|
handler: async (args, ctx) => {
|
|
1199
1583
|
if (args.asin && args.parentAsin) {
|
|
@@ -1224,21 +1608,21 @@ var listPpcCampaignsTool = {
|
|
|
1224
1608
|
};
|
|
1225
1609
|
|
|
1226
1610
|
// src/tools/get-asin-inventory-distribution.ts
|
|
1227
|
-
import { z as
|
|
1228
|
-
var
|
|
1229
|
-
sellerId:
|
|
1611
|
+
import { z as z39 } from "zod";
|
|
1612
|
+
var inputSchema39 = {
|
|
1613
|
+
sellerId: z39.string().min(1).describe(
|
|
1230
1614
|
"Amazon seller account ID as connected to DataDive. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
1231
1615
|
),
|
|
1232
|
-
marketplace:
|
|
1233
|
-
asin:
|
|
1616
|
+
marketplace: z39.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
1617
|
+
asin: z39.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
|
|
1234
1618
|
"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
|
|
1235
1619
|
)
|
|
1236
1620
|
};
|
|
1237
1621
|
var getAsinInventoryDistributionTool = {
|
|
1238
1622
|
name: "get_asin_inventory_distribution",
|
|
1239
|
-
title: "Get
|
|
1240
|
-
description: "Use this when the user asks about inventory levels, stock
|
|
1241
|
-
inputSchema:
|
|
1623
|
+
title: "Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN",
|
|
1624
|
+
description: "Use this when the user asks about an ASIN's inventory: stock levels, days of stock or when it will run out, low-inventory or aged-inventory risk, low-inventory-level (LILF) or long-term storage (LTSF) fees, or where their units sit across Amazon's fulfillment network. Requires the Amazon sellerId (from `list_seller_profiles` or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. Returns the metrics the DataDive Inventory page shows \u2014 fulfillableStock, totalStock, reservedStock, inboundStock, researchingStock, unfulfillableStock, daysOfStock (with daysOfStockSource AMAZON or ESTIMATE), daysOfStock30, daysOfStock90, inLowInventoryState, estWeeklyLilf, estLtsfNextAssessment, ltsfLastMonthActual, agedUnits, oldestUnitAgeDays, with fees in `currency` \u2014 plus totalSellableUnits and a per-FC `distribution` array (fc, state, availableStock, availableStockPercentage). A metric is null, never 0, when Amazon has no data (e.g. merchant-fulfilled); metrics the user's plan does not include are null and listed in `lockedFields` (say they need a plan upgrade, do not report them as zero). `lastUpdatedAt` dates the stock and metrics; `distributionLastUpdatedAt` dates the distribution, which is usually up to 2 days behind. Either may be null when no successful refresh happened in the last 30 days.",
|
|
1625
|
+
inputSchema: inputSchema39,
|
|
1242
1626
|
annotations: { readOnlyHint: true },
|
|
1243
1627
|
handler: async (args, ctx) => {
|
|
1244
1628
|
return await httpGet(
|
|
@@ -1251,18 +1635,18 @@ var getAsinInventoryDistributionTool = {
|
|
|
1251
1635
|
};
|
|
1252
1636
|
|
|
1253
1637
|
// src/tools/alert-query.ts
|
|
1254
|
-
import { z as
|
|
1638
|
+
import { z as z40 } from "zod";
|
|
1255
1639
|
var alertsQueryInputSchema = {
|
|
1256
|
-
sellerId:
|
|
1257
|
-
marketplace:
|
|
1258
|
-
status:
|
|
1640
|
+
sellerId: z40.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
|
|
1641
|
+
marketplace: z40.enum(SUPPORTED_MARKETPLACES).optional().describe('Filter to a single Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
1642
|
+
status: z40.enum(ALERT_STATUSES).optional().describe(
|
|
1259
1643
|
"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
|
|
1260
1644
|
),
|
|
1261
|
-
updatedSince:
|
|
1645
|
+
updatedSince: z40.string().regex(ISO_8601, "updatedSince must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe(
|
|
1262
1646
|
"ISO-8601 timestamp; return only alerts surfaced at or after this time (filters on `lastAlertedAt`). Use it to incrementally sync since your last poll. Defaults to the last 30 days when omitted."
|
|
1263
1647
|
),
|
|
1264
|
-
currentPage:
|
|
1265
|
-
pageSize:
|
|
1648
|
+
currentPage: z40.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1649
|
+
pageSize: z40.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1266
1650
|
};
|
|
1267
1651
|
|
|
1268
1652
|
// src/tools/list-indexing-issue-alerts.ts
|
|
@@ -1312,12 +1696,12 @@ var listBlindSpendAlertsTool = {
|
|
|
1312
1696
|
};
|
|
1313
1697
|
|
|
1314
1698
|
// src/tools/get-quota.ts
|
|
1315
|
-
var
|
|
1699
|
+
var inputSchema40 = {};
|
|
1316
1700
|
var getQuotaTool = {
|
|
1317
1701
|
name: "get_quota",
|
|
1318
1702
|
title: "Get Quota Usage",
|
|
1319
1703
|
description: "Use this when the user asks how much quota or how many tokens they have left, or before creating a Niche Dive or Rank Radar to check remaining headroom. Returns current quota usage and capacity for each billable feature \u2014 DIVED_ASINS (Dive tokens), RANK_RADAR_KEYWORDS (tracked keywords), PRODUCT_BRIEF_ASINS, and AI_COPYWRITER_PROMPTS \u2014 each with `used` and `capacity` (null when unlimited / not applicable), plus `nextRefreshDate` (ISO-8601 timestamp of the next quota reset, or null). Takes no arguments.",
|
|
1320
|
-
inputSchema:
|
|
1704
|
+
inputSchema: inputSchema40,
|
|
1321
1705
|
annotations: { readOnlyHint: true },
|
|
1322
1706
|
handler: async (_args, ctx) => {
|
|
1323
1707
|
return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
|
|
@@ -1325,20 +1709,20 @@ var getQuotaTool = {
|
|
|
1325
1709
|
};
|
|
1326
1710
|
|
|
1327
1711
|
// src/tools/list-usage.ts
|
|
1328
|
-
import { z as
|
|
1329
|
-
var
|
|
1330
|
-
type:
|
|
1331
|
-
search:
|
|
1332
|
-
startDate:
|
|
1333
|
-
endDate:
|
|
1334
|
-
currentPage:
|
|
1335
|
-
pageSize:
|
|
1712
|
+
import { z as z41 } from "zod";
|
|
1713
|
+
var inputSchema41 = {
|
|
1714
|
+
type: z41.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
|
|
1715
|
+
search: z41.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
|
|
1716
|
+
startDate: z41.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or after this time."),
|
|
1717
|
+
endDate: z41.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or before this time."),
|
|
1718
|
+
currentPage: z41.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1719
|
+
pageSize: z41.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
|
|
1336
1720
|
};
|
|
1337
1721
|
var listUsageTool = {
|
|
1338
1722
|
name: "list_usage",
|
|
1339
1723
|
title: "List Billable Usage Logs",
|
|
1340
1724
|
description: "Use this when the user asks who consumed tokens, how their quota was spent, or wants an audit of billable activity over a date range. Retrieves a paginated list of billable feature usage logs for the organization \u2014 each entry is a token-consumption event (a dive, rank-radar creation, AI copywriter prompt, etc.). Filter by `type` (billable feature), `search` (user name/email), and `startDate`/`endDate`. Each item includes name, email, qty (tokens consumed), type, action (specific operation), nicheId/nicheName, rankRadarId, and date, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
1341
|
-
inputSchema:
|
|
1725
|
+
inputSchema: inputSchema41,
|
|
1342
1726
|
annotations: { readOnlyHint: true },
|
|
1343
1727
|
handler: async (args, ctx) => {
|
|
1344
1728
|
return await httpGet(
|
|
@@ -1360,6 +1744,7 @@ var listUsageTool = {
|
|
|
1360
1744
|
var allTools = [
|
|
1361
1745
|
listNichesTool,
|
|
1362
1746
|
getNicheKeywordsTool,
|
|
1747
|
+
listKeywordExclusionsTool,
|
|
1363
1748
|
getNicheRootsTool,
|
|
1364
1749
|
getNicheCompetitorsTool,
|
|
1365
1750
|
getRankingJuiceTool,
|
|
@@ -1367,14 +1752,24 @@ var allTools = [
|
|
|
1367
1752
|
getRankRadarDataTool,
|
|
1368
1753
|
getRankRadarSqpDataTool,
|
|
1369
1754
|
getRankRadarPpcDataTool,
|
|
1755
|
+
listRankRadarTagsTool,
|
|
1370
1756
|
createRankRadarTool,
|
|
1371
1757
|
createNicheDiveTool,
|
|
1372
1758
|
createNicheDiveFromCompetitorsListTool,
|
|
1373
1759
|
rediveNicheTool,
|
|
1374
1760
|
getDiveStatusTool,
|
|
1761
|
+
addNicheKeywordsTool,
|
|
1762
|
+
removeNicheKeywordsTool,
|
|
1763
|
+
addKeywordExclusionsTool,
|
|
1764
|
+
removeKeywordExclusionsTool,
|
|
1375
1765
|
addRankRadarSearchTermsTool,
|
|
1376
1766
|
pauseRankRadarSearchTermsTool,
|
|
1377
1767
|
resumeRankRadarSearchTermsTool,
|
|
1768
|
+
createRankRadarTagTool,
|
|
1769
|
+
updateRankRadarTagTool,
|
|
1770
|
+
addRankRadarTagSearchTermsTool,
|
|
1771
|
+
removeRankRadarTagSearchTermsTool,
|
|
1772
|
+
deleteRankRadarTagTool,
|
|
1378
1773
|
pauseRankRadarTool,
|
|
1379
1774
|
resumeRankRadarTool,
|
|
1380
1775
|
deleteRankRadarTool,
|
|
@@ -1394,6 +1789,21 @@ var allTools = [
|
|
|
1394
1789
|
|
|
1395
1790
|
// src/whats-new.ts
|
|
1396
1791
|
var WHATS_NEW = [
|
|
1792
|
+
{
|
|
1793
|
+
version: "0.17.1",
|
|
1794
|
+
released: "2026-10-13",
|
|
1795
|
+
items: [
|
|
1796
|
+
"Rank Radar tags end-to-end: list, create, update, delete tags, and attach or detach up to 1000 keywords per call (`list_rank_radar_tags`, `create_rank_radar_tag`, `update_rank_radar_tag`, `delete_rank_radar_tag`, `add_rank_radar_tag_search_terms`, `remove_rank_radar_tag_search_terms`). The highlights on each keyword in `get_rank_radar_data` are the same objects, so you can go from a ranking view to editing its tags without a round trip."
|
|
1797
|
+
]
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
version: "0.17.0",
|
|
1801
|
+
released: "2026-10-12",
|
|
1802
|
+
items: [
|
|
1803
|
+
"Add your own keywords to a niche's Master Keyword List, or take them back out (`add_niche_keywords`, `remove_niche_keywords`). Free while the niche is fresh; on an older niche you are asked to confirm the Dive tokens first. `get_niche_keywords` now shows where each keyword came from.",
|
|
1804
|
+
"See and change which keywords a niche hides (`list_keyword_exclusions`, `add_keyword_exclusions`, `remove_keyword_exclusions`): exclude one exact keyword, or every keyword containing a phrase. Free."
|
|
1805
|
+
]
|
|
1806
|
+
},
|
|
1397
1807
|
{
|
|
1398
1808
|
version: "0.16.1",
|
|
1399
1809
|
released: "2026-09-29",
|
|
@@ -1438,8 +1848,8 @@ var WHATS_NEW = [
|
|
|
1438
1848
|
}
|
|
1439
1849
|
];
|
|
1440
1850
|
var INSTRUCTIONS_RELEASES = 2;
|
|
1441
|
-
function formatEntries(
|
|
1442
|
-
return
|
|
1851
|
+
function formatEntries(entries2) {
|
|
1852
|
+
return entries2.map((e) => `${e.released} (v${e.version}):
|
|
1443
1853
|
${e.items.map((i) => `- ${i}`).join("\n")}`).join("\n\n");
|
|
1444
1854
|
}
|
|
1445
1855
|
var SERVER_INSTRUCTIONS = `DataDive tools for Amazon sellers: niche research (niches, keywords, roots, competitors, Ranking Juice), Rank Radar keyword rank tracking with SQP and PPC data, the seller's catalog, listing changes, inventory, PPC campaigns and alerts. Start with a discovery tool: \`list_niches\`, \`list_rank_radars\` or \`list_seller_profiles\`. Tools that spend tokens ask for confirmation first.
|