@datadive-tools/mcp 0.16.0 → 0.17.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 +412 -186
- package/dist/index.js.map +1 -1
- package/dist/lib.js +412 -186
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ function extractMessage(body) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// package.json
|
|
147
|
-
var version = "0.
|
|
147
|
+
var version = "0.17.0";
|
|
148
148
|
|
|
149
149
|
// src/http/client.ts
|
|
150
150
|
var PKG_VERSION = version;
|
|
@@ -321,7 +321,7 @@ var inputSchema2 = {
|
|
|
321
321
|
var getNicheKeywordsTool = {
|
|
322
322
|
name: "get_niche_keywords",
|
|
323
323
|
title: "Get Master Keyword List for a Niche",
|
|
324
|
-
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.',
|
|
324
|
+
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.',
|
|
325
325
|
inputSchema: inputSchema2,
|
|
326
326
|
annotations: { readOnlyHint: true },
|
|
327
327
|
handler: async (args, ctx) => {
|
|
@@ -332,16 +332,35 @@ var getNicheKeywordsTool = {
|
|
|
332
332
|
}
|
|
333
333
|
};
|
|
334
334
|
|
|
335
|
-
// src/tools/
|
|
335
|
+
// src/tools/list-keyword-exclusions.ts
|
|
336
336
|
import { z as z3 } from "zod";
|
|
337
337
|
var inputSchema3 = {
|
|
338
338
|
nicheId: z3.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
339
339
|
};
|
|
340
|
+
var listKeywordExclusionsTool = {
|
|
341
|
+
name: "list_keyword_exclusions",
|
|
342
|
+
title: "List a Niche's Keyword Exclusions",
|
|
343
|
+
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`.",
|
|
344
|
+
inputSchema: inputSchema3,
|
|
345
|
+
annotations: { readOnlyHint: true },
|
|
346
|
+
handler: async (args, ctx) => {
|
|
347
|
+
return await httpGet(
|
|
348
|
+
{ config: ctx.config, toolName: "list_keyword_exclusions" },
|
|
349
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/tools/get-niche-roots.ts
|
|
355
|
+
import { z as z4 } from "zod";
|
|
356
|
+
var inputSchema4 = {
|
|
357
|
+
nicheId: z4.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
358
|
+
};
|
|
340
359
|
var getNicheRootsTool = {
|
|
341
360
|
name: "get_niche_roots",
|
|
342
361
|
title: "Get Keyword Roots for a Niche",
|
|
343
362
|
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.",
|
|
344
|
-
inputSchema:
|
|
363
|
+
inputSchema: inputSchema4,
|
|
345
364
|
annotations: { readOnlyHint: true },
|
|
346
365
|
handler: async (args, ctx) => {
|
|
347
366
|
return await httpGet(
|
|
@@ -352,15 +371,15 @@ var getNicheRootsTool = {
|
|
|
352
371
|
};
|
|
353
372
|
|
|
354
373
|
// src/tools/get-niche-competitors.ts
|
|
355
|
-
import { z as
|
|
356
|
-
var
|
|
357
|
-
nicheId:
|
|
374
|
+
import { z as z5 } from "zod";
|
|
375
|
+
var inputSchema5 = {
|
|
376
|
+
nicheId: z5.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
358
377
|
};
|
|
359
378
|
var getNicheCompetitorsTool = {
|
|
360
379
|
name: "get_niche_competitors",
|
|
361
380
|
title: "Get Competitors and Niche Statistics",
|
|
362
381
|
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`.",
|
|
363
|
-
inputSchema:
|
|
382
|
+
inputSchema: inputSchema5,
|
|
364
383
|
annotations: { readOnlyHint: true },
|
|
365
384
|
handler: async (args, ctx) => {
|
|
366
385
|
return await httpGet(
|
|
@@ -371,15 +390,15 @@ var getNicheCompetitorsTool = {
|
|
|
371
390
|
};
|
|
372
391
|
|
|
373
392
|
// src/tools/get-ranking-juice.ts
|
|
374
|
-
import { z as
|
|
375
|
-
var
|
|
376
|
-
nicheId:
|
|
393
|
+
import { z as z6 } from "zod";
|
|
394
|
+
var inputSchema6 = {
|
|
395
|
+
nicheId: z6.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
377
396
|
};
|
|
378
397
|
var getRankingJuiceTool = {
|
|
379
398
|
name: "get_ranking_juice",
|
|
380
399
|
title: "Get Ranking Juices for a Niche",
|
|
381
400
|
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.",
|
|
382
|
-
inputSchema:
|
|
401
|
+
inputSchema: inputSchema6,
|
|
383
402
|
annotations: { readOnlyHint: true },
|
|
384
403
|
handler: async (args, ctx) => {
|
|
385
404
|
return await httpGet(
|
|
@@ -390,7 +409,7 @@ var getRankingJuiceTool = {
|
|
|
390
409
|
};
|
|
391
410
|
|
|
392
411
|
// src/tools/list-rank-radars.ts
|
|
393
|
-
import { z as
|
|
412
|
+
import { z as z7 } from "zod";
|
|
394
413
|
|
|
395
414
|
// src/types/api.ts
|
|
396
415
|
var ISO_8601 = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -406,6 +425,10 @@ var SUPPORTED_MARKETPLACES = [
|
|
|
406
425
|
"it",
|
|
407
426
|
"co.jp"
|
|
408
427
|
];
|
|
428
|
+
var NICHE_KEYWORDS_MAX_PER_CALL = 100;
|
|
429
|
+
var NICHE_KEYWORD_MAX_LENGTH = 255;
|
|
430
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
|
|
431
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
|
|
409
432
|
var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
|
|
410
433
|
var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
|
|
411
434
|
var PPC_CAMPAIGN_BASE_SORT_BY = [
|
|
@@ -451,14 +474,14 @@ var LISTING_COPY_STRATEGIES = ["cosmo", "ranking-juice", "nlp", "cosmo-rufus"];
|
|
|
451
474
|
|
|
452
475
|
// src/tools/list-rank-radars.ts
|
|
453
476
|
var LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;
|
|
454
|
-
var
|
|
455
|
-
currentPage:
|
|
456
|
-
pageSize:
|
|
457
|
-
nicheId:
|
|
458
|
-
status:
|
|
477
|
+
var inputSchema7 = {
|
|
478
|
+
currentPage: z7.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
479
|
+
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.`),
|
|
480
|
+
nicheId: z7.string().min(1).optional().describe("Filter Rank Radars by Niche identifier. Use after `list_niches`."),
|
|
481
|
+
status: z7.enum(RANK_RADAR_STATUSES).optional().describe(
|
|
459
482
|
"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."
|
|
460
483
|
),
|
|
461
|
-
searchText:
|
|
484
|
+
searchText: z7.string().optional().describe("Filter Rank Radars by ASIN or product title.")
|
|
462
485
|
};
|
|
463
486
|
function minimize(item) {
|
|
464
487
|
const { asin, ...rest } = item;
|
|
@@ -468,7 +491,7 @@ var listRankRadarsTool = {
|
|
|
468
491
|
name: "list_rank_radars",
|
|
469
492
|
title: "List Rank Radars",
|
|
470
493
|
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.`,
|
|
471
|
-
inputSchema:
|
|
494
|
+
inputSchema: inputSchema7,
|
|
472
495
|
annotations: { readOnlyHint: true },
|
|
473
496
|
handler: async (args, ctx) => {
|
|
474
497
|
const page = await httpGet(
|
|
@@ -487,18 +510,18 @@ var listRankRadarsTool = {
|
|
|
487
510
|
};
|
|
488
511
|
|
|
489
512
|
// src/tools/rank-radar-keyword-query.ts
|
|
490
|
-
import { z as
|
|
513
|
+
import { z as z8 } from "zod";
|
|
491
514
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
492
515
|
var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
|
|
493
516
|
var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
|
|
494
517
|
var rankRadarKeywordQueryInputSchema = {
|
|
495
|
-
rankRadarId:
|
|
496
|
-
startDate:
|
|
497
|
-
endDate:
|
|
518
|
+
rankRadarId: z8.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
|
|
519
|
+
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)."),
|
|
520
|
+
endDate: z8.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
|
|
498
521
|
`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.`
|
|
499
522
|
),
|
|
500
|
-
currentPage:
|
|
501
|
-
pageSize:
|
|
523
|
+
currentPage: z8.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
|
|
524
|
+
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.`)
|
|
502
525
|
};
|
|
503
526
|
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.`;
|
|
504
527
|
function rankRadarPath(rankRadarId, suffix = "") {
|
|
@@ -514,14 +537,42 @@ function rankRadarKeywordQuery(args) {
|
|
|
514
537
|
}
|
|
515
538
|
|
|
516
539
|
// src/tools/get-rank-radar-data.ts
|
|
517
|
-
var
|
|
540
|
+
var inputSchema8 = rankRadarKeywordQueryInputSchema;
|
|
541
|
+
async function readAllKeywords(args, ctx) {
|
|
542
|
+
const request = { config: ctx.config, toolName: "get_rank_radar_data" };
|
|
543
|
+
const path = rankRadarPath(args.rankRadarId);
|
|
544
|
+
const keywords = [];
|
|
545
|
+
let page;
|
|
546
|
+
let currentPage = 1;
|
|
547
|
+
do {
|
|
548
|
+
page = await httpGet(
|
|
549
|
+
request,
|
|
550
|
+
path,
|
|
551
|
+
rankRadarKeywordQuery({ ...args, currentPage, pageSize: RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE })
|
|
552
|
+
);
|
|
553
|
+
keywords.push(...page.data ?? []);
|
|
554
|
+
currentPage++;
|
|
555
|
+
} while (page.hasNext && currentPage <= page.lastPage && (page.data?.length ?? 0) > 0);
|
|
556
|
+
return {
|
|
557
|
+
data: keywords,
|
|
558
|
+
currentPage: 1,
|
|
559
|
+
pageSize: keywords.length,
|
|
560
|
+
total: page.total,
|
|
561
|
+
lastPage: 1,
|
|
562
|
+
hasNext: false,
|
|
563
|
+
hasPrev: false
|
|
564
|
+
};
|
|
565
|
+
}
|
|
518
566
|
var getRankRadarDataTool = {
|
|
519
567
|
name: "get_rank_radar_data",
|
|
520
568
|
title: "Get Keyword Rankings for a Rank Radar",
|
|
521
|
-
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. ${
|
|
522
|
-
inputSchema:
|
|
569
|
+
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 any highlight annotations. 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\`.`,
|
|
570
|
+
inputSchema: inputSchema8,
|
|
523
571
|
annotations: { readOnlyHint: true },
|
|
524
572
|
handler: async (args, ctx) => {
|
|
573
|
+
if (args.currentPage === void 0 && args.pageSize === void 0) {
|
|
574
|
+
return await readAllKeywords(args, ctx);
|
|
575
|
+
}
|
|
525
576
|
return await httpGet(
|
|
526
577
|
{ config: ctx.config, toolName: "get_rank_radar_data" },
|
|
527
578
|
rankRadarPath(args.rankRadarId),
|
|
@@ -531,12 +582,12 @@ var getRankRadarDataTool = {
|
|
|
531
582
|
};
|
|
532
583
|
|
|
533
584
|
// src/tools/get-rank-radar-sqp-data.ts
|
|
534
|
-
var
|
|
585
|
+
var inputSchema9 = rankRadarKeywordQueryInputSchema;
|
|
535
586
|
var getRankRadarSqpDataTool = {
|
|
536
587
|
name: "get_rank_radar_sqp_data",
|
|
537
588
|
title: "Get Search Query Performance for a Rank Radar",
|
|
538
589
|
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\`.`,
|
|
539
|
-
inputSchema:
|
|
590
|
+
inputSchema: inputSchema9,
|
|
540
591
|
annotations: { readOnlyHint: true },
|
|
541
592
|
handler: async (args, ctx) => {
|
|
542
593
|
return await httpGet(
|
|
@@ -548,10 +599,10 @@ var getRankRadarSqpDataTool = {
|
|
|
548
599
|
};
|
|
549
600
|
|
|
550
601
|
// src/tools/get-rank-radar-ppc-data.ts
|
|
551
|
-
import { z as
|
|
552
|
-
var
|
|
602
|
+
import { z as z9 } from "zod";
|
|
603
|
+
var inputSchema10 = {
|
|
553
604
|
...rankRadarKeywordQueryInputSchema,
|
|
554
|
-
includeCampaigns:
|
|
605
|
+
includeCampaigns: z9.boolean().optional().describe(
|
|
555
606
|
"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."
|
|
556
607
|
)
|
|
557
608
|
};
|
|
@@ -559,7 +610,7 @@ var getRankRadarPpcDataTool = {
|
|
|
559
610
|
name: "get_rank_radar_ppc_data",
|
|
560
611
|
title: "Get PPC Data for a Rank Radar",
|
|
561
612
|
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\`.`,
|
|
562
|
-
inputSchema:
|
|
613
|
+
inputSchema: inputSchema10,
|
|
563
614
|
annotations: { readOnlyHint: true },
|
|
564
615
|
handler: async (args, ctx) => {
|
|
565
616
|
return await httpGet(
|
|
@@ -571,7 +622,7 @@ var getRankRadarPpcDataTool = {
|
|
|
571
622
|
};
|
|
572
623
|
|
|
573
624
|
// src/tools/create-rank-radar.ts
|
|
574
|
-
import { z as
|
|
625
|
+
import { z as z10 } from "zod";
|
|
575
626
|
|
|
576
627
|
// src/tools/confirm.ts
|
|
577
628
|
function needsConfirmation(confirm, ctx) {
|
|
@@ -592,11 +643,11 @@ function requireConfirmation(confirm, ctx, costNote) {
|
|
|
592
643
|
|
|
593
644
|
// src/tools/create-rank-radar.ts
|
|
594
645
|
var PATH = "/v1/niches/rank-radars";
|
|
595
|
-
var
|
|
596
|
-
asin:
|
|
597
|
-
numberOfKeywords:
|
|
598
|
-
nicheId:
|
|
599
|
-
confirm:
|
|
646
|
+
var inputSchema11 = {
|
|
647
|
+
asin: z10.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
648
|
+
numberOfKeywords: z10.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
649
|
+
nicheId: z10.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
|
|
650
|
+
confirm: z10.boolean().optional().describe(
|
|
600
651
|
"Must be true to proceed \u2014 creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first."
|
|
601
652
|
)
|
|
602
653
|
};
|
|
@@ -604,7 +655,7 @@ var createRankRadarTool = {
|
|
|
604
655
|
name: "create_rank_radar",
|
|
605
656
|
title: "Create a Rank Radar",
|
|
606
657
|
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`.",
|
|
607
|
-
inputSchema:
|
|
658
|
+
inputSchema: inputSchema11,
|
|
608
659
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
609
660
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
610
661
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -629,20 +680,20 @@ var createRankRadarTool = {
|
|
|
629
680
|
};
|
|
630
681
|
|
|
631
682
|
// src/tools/create-niche-dive.ts
|
|
632
|
-
import { z as
|
|
633
|
-
var
|
|
634
|
-
marketplace:
|
|
683
|
+
import { z as z11 } from "zod";
|
|
684
|
+
var inputSchema12 = {
|
|
685
|
+
marketplace: z11.enum(SUPPORTED_MARKETPLACES).describe(
|
|
635
686
|
"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`)."
|
|
636
687
|
),
|
|
637
|
-
asin:
|
|
638
|
-
numberOfCompetitors:
|
|
639
|
-
confirm:
|
|
688
|
+
asin: z11.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
|
|
689
|
+
numberOfCompetitors: z11.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
|
|
690
|
+
confirm: z11.boolean().optional().describe("Must be true to proceed \u2014 creating a dive spends dive tokens. Confirm the cost with the user first.")
|
|
640
691
|
};
|
|
641
692
|
var createNicheDiveTool = {
|
|
642
693
|
name: "create_niche_dive",
|
|
643
694
|
title: "Create a Niche Dive",
|
|
644
695
|
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).",
|
|
645
|
-
inputSchema:
|
|
696
|
+
inputSchema: inputSchema12,
|
|
646
697
|
// destructiveHint is technically "additive" here (a dive creates a new niche, it
|
|
647
698
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
648
699
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -667,20 +718,20 @@ var createNicheDiveTool = {
|
|
|
667
718
|
};
|
|
668
719
|
|
|
669
720
|
// src/tools/create-niche-dive-from-competitors-list.ts
|
|
670
|
-
import { z as
|
|
721
|
+
import { z as z12 } from "zod";
|
|
671
722
|
var DIVE_FROM_COMPETITORS_MIN_ASINS = 2;
|
|
672
723
|
var DIVE_FROM_COMPETITORS_MAX_ASINS = 200;
|
|
673
|
-
var normalizedAsins =
|
|
724
|
+
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, {
|
|
674
725
|
message: "asins must not contain the same ASIN twice"
|
|
675
726
|
});
|
|
676
|
-
var
|
|
677
|
-
marketplace:
|
|
727
|
+
var inputSchema13 = {
|
|
728
|
+
marketplace: z12.enum(SUPPORTED_MARKETPLACES).describe(
|
|
678
729
|
"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`)."
|
|
679
730
|
),
|
|
680
731
|
asins: normalizedAsins.describe(
|
|
681
732
|
`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.`
|
|
682
733
|
),
|
|
683
|
-
confirm:
|
|
734
|
+
confirm: z12.boolean().optional().describe(
|
|
684
735
|
"Must be true to proceed \u2014 this dive spends dive tokens for every ASIN. Confirm the cost with the user first."
|
|
685
736
|
)
|
|
686
737
|
};
|
|
@@ -688,7 +739,7 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
688
739
|
name: "create_niche_dive_from_competitors_list",
|
|
689
740
|
title: "Create a Niche Dive from a Competitor List",
|
|
690
741
|
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\`.`,
|
|
691
|
-
inputSchema:
|
|
742
|
+
inputSchema: inputSchema13,
|
|
692
743
|
// Additive rather than destructive, but flagged destructive so clients prompt before the
|
|
693
744
|
// irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.
|
|
694
745
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -708,23 +759,23 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
708
759
|
};
|
|
709
760
|
|
|
710
761
|
// src/tools/redive-niche.ts
|
|
711
|
-
import { z as
|
|
712
|
-
var
|
|
713
|
-
nicheId:
|
|
714
|
-
mode:
|
|
762
|
+
import { z as z13 } from "zod";
|
|
763
|
+
var inputSchema14 = {
|
|
764
|
+
nicheId: z13.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
|
|
765
|
+
mode: z13.enum(["same_competitors", "discover"]).describe(
|
|
715
766
|
"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."
|
|
716
767
|
),
|
|
717
|
-
numberOfCompetitors:
|
|
768
|
+
numberOfCompetitors: z13.number().int().min(2).optional().describe(
|
|
718
769
|
"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."
|
|
719
770
|
),
|
|
720
|
-
heroAsin:
|
|
771
|
+
heroAsin: z13.string().min(1).optional().describe(
|
|
721
772
|
"`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."
|
|
722
773
|
),
|
|
723
|
-
lockedAsins:
|
|
774
|
+
lockedAsins: z13.array(z13.string().min(1)).optional().describe(
|
|
724
775
|
"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
|
|
725
776
|
),
|
|
726
|
-
excludedAsins:
|
|
727
|
-
confirm:
|
|
777
|
+
excludedAsins: z13.array(z13.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
|
|
778
|
+
confirm: z13.boolean().optional().describe(
|
|
728
779
|
"Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
|
|
729
780
|
)
|
|
730
781
|
};
|
|
@@ -732,7 +783,7 @@ var rediveNicheTool = {
|
|
|
732
783
|
name: "redive_niche",
|
|
733
784
|
title: "Re-dive an Existing Niche",
|
|
734
785
|
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.",
|
|
735
|
-
inputSchema:
|
|
786
|
+
inputSchema: inputSchema14,
|
|
736
787
|
// Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
|
|
737
788
|
// irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
|
|
738
789
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -765,17 +816,17 @@ var rediveNicheTool = {
|
|
|
765
816
|
};
|
|
766
817
|
|
|
767
818
|
// src/tools/get-dive-status.ts
|
|
768
|
-
import { z as
|
|
769
|
-
var
|
|
770
|
-
diveId:
|
|
771
|
-
"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`."
|
|
819
|
+
import { z as z14 } from "zod";
|
|
820
|
+
var inputSchema15 = {
|
|
821
|
+
diveId: z14.string().min(1).describe(
|
|
822
|
+
"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`."
|
|
772
823
|
)
|
|
773
824
|
};
|
|
774
825
|
var getDiveStatusTool = {
|
|
775
826
|
name: "get_dive_status",
|
|
776
827
|
title: "Get Niche Dive Status",
|
|
777
|
-
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.",
|
|
778
|
-
inputSchema:
|
|
828
|
+
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.",
|
|
829
|
+
inputSchema: inputSchema15,
|
|
779
830
|
annotations: { readOnlyHint: true },
|
|
780
831
|
handler: async (args, ctx) => {
|
|
781
832
|
return await httpGet(
|
|
@@ -785,11 +836,166 @@ var getDiveStatusTool = {
|
|
|
785
836
|
}
|
|
786
837
|
};
|
|
787
838
|
|
|
839
|
+
// src/tools/add-niche-keywords.ts
|
|
840
|
+
import { z as z16 } from "zod";
|
|
841
|
+
|
|
842
|
+
// src/tools/niche-keyword-list.ts
|
|
843
|
+
import { z as z15 } from "zod";
|
|
844
|
+
var nicheKeyword = () => z15.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);
|
|
845
|
+
function uniqueKeywords(schema, field) {
|
|
846
|
+
return schema.refine((keywords) => new Set(keywords).size === keywords.length, {
|
|
847
|
+
message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
var nicheKeywordList = uniqueKeywords(
|
|
851
|
+
z15.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),
|
|
852
|
+
"keywords"
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
// src/tools/add-niche-keywords.ts
|
|
856
|
+
var inputSchema16 = {
|
|
857
|
+
nicheId: z16.string().min(1).describe("The niche whose Master Keyword List to add to. Get one from `list_niches`."),
|
|
858
|
+
keywords: nicheKeywordList.describe(
|
|
859
|
+
`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.`
|
|
860
|
+
),
|
|
861
|
+
confirm: z16.boolean().optional().describe(
|
|
862
|
+
"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."
|
|
863
|
+
),
|
|
864
|
+
maxTokensUsed: z16.number().int().min(0).optional().describe(
|
|
865
|
+
"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."
|
|
866
|
+
)
|
|
867
|
+
};
|
|
868
|
+
function isMaxTokensExceededBody(body) {
|
|
869
|
+
if (!body || typeof body !== "object") return false;
|
|
870
|
+
const b = body;
|
|
871
|
+
return b.error === "MAX_TOKENS_EXCEEDED" && typeof b.tokensUsed === "number";
|
|
872
|
+
}
|
|
873
|
+
var addNicheKeywordsTool = {
|
|
874
|
+
name: "add_niche_keywords",
|
|
875
|
+
title: "Add Keywords to a Niche's Master Keyword List",
|
|
876
|
+
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.",
|
|
877
|
+
inputSchema: inputSchema16,
|
|
878
|
+
// Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients
|
|
879
|
+
// need to prompt on. The confirm gate covers the paid path regardless.
|
|
880
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
881
|
+
handler: async (args, ctx) => {
|
|
882
|
+
const request = { config: ctx.config, toolName: "add_niche_keywords" };
|
|
883
|
+
const path = `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`;
|
|
884
|
+
if (needsConfirmation(args.confirm, ctx)) {
|
|
885
|
+
try {
|
|
886
|
+
return await httpPost(request, path, {
|
|
887
|
+
keywords: args.keywords,
|
|
888
|
+
maxTokensUsed: 0
|
|
889
|
+
});
|
|
890
|
+
} catch (err) {
|
|
891
|
+
if (err instanceof ApiError && isMaxTokensExceededBody(err.body)) {
|
|
892
|
+
const tokenCost = err.body.tokensUsed;
|
|
893
|
+
return {
|
|
894
|
+
...confirmationRequired(
|
|
895
|
+
`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.`
|
|
896
|
+
),
|
|
897
|
+
tokenCost
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
throw err;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return await httpPost(request, path, {
|
|
904
|
+
keywords: args.keywords,
|
|
905
|
+
maxTokensUsed: args.maxTokensUsed
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/tools/remove-niche-keywords.ts
|
|
911
|
+
import { z as z17 } from "zod";
|
|
912
|
+
var inputSchema17 = {
|
|
913
|
+
nicheId: z17.string().min(1).describe("The niche whose Master Keyword List to remove from. Get one from `list_niches`."),
|
|
914
|
+
keywords: nicheKeywordList.describe(
|
|
915
|
+
`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.`
|
|
916
|
+
)
|
|
917
|
+
};
|
|
918
|
+
var removeNicheKeywordsTool = {
|
|
919
|
+
name: "remove_niche_keywords",
|
|
920
|
+
title: "Remove Keywords from a Niche's Master Keyword List",
|
|
921
|
+
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.",
|
|
922
|
+
inputSchema: inputSchema17,
|
|
923
|
+
// Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.
|
|
924
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
925
|
+
handler: async (args, ctx) => {
|
|
926
|
+
return await httpPost(
|
|
927
|
+
{ config: ctx.config, toolName: "remove_niche_keywords" },
|
|
928
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keywords/remove`,
|
|
929
|
+
{ keywords: args.keywords }
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/tools/keyword-exclusions-input.ts
|
|
935
|
+
import { z as z18 } from "zod";
|
|
936
|
+
var entries = (field) => uniqueKeywords(z18.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();
|
|
937
|
+
var keywordExclusionsInputSchema = (verb) => ({
|
|
938
|
+
nicheId: z18.string().min(1).describe("The niche whose keyword exclusions to change. Get one from `list_niches`."),
|
|
939
|
+
exact: entries("exact").describe(
|
|
940
|
+
`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.`
|
|
941
|
+
),
|
|
942
|
+
phrase: entries("phrase").describe(
|
|
943
|
+
`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.`
|
|
944
|
+
)
|
|
945
|
+
});
|
|
946
|
+
function assertExclusionCount(exact, phrase) {
|
|
947
|
+
const total = (exact?.length ?? 0) + (phrase?.length ?? 0);
|
|
948
|
+
if (total === 0 || total > KEYWORD_EXCLUSIONS_MAX_PER_CALL) {
|
|
949
|
+
throw new ApiError(
|
|
950
|
+
"bad_request",
|
|
951
|
+
400,
|
|
952
|
+
`Send 1 to ${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\` (got ${total}). Split a longer list over several calls.`
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/tools/add-keyword-exclusions.ts
|
|
958
|
+
var inputSchema18 = keywordExclusionsInputSchema("exclude");
|
|
959
|
+
var addKeywordExclusionsTool = {
|
|
960
|
+
name: "add_keyword_exclusions",
|
|
961
|
+
title: "Exclude Keywords from a Niche",
|
|
962
|
+
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.`,
|
|
963
|
+
inputSchema: inputSchema18,
|
|
964
|
+
// Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.
|
|
965
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
966
|
+
handler: async (args, ctx) => {
|
|
967
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
968
|
+
return await httpPost(
|
|
969
|
+
{ config: ctx.config, toolName: "add_keyword_exclusions" },
|
|
970
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,
|
|
971
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
// src/tools/remove-keyword-exclusions.ts
|
|
977
|
+
var inputSchema19 = keywordExclusionsInputSchema("stop excluding");
|
|
978
|
+
var removeKeywordExclusionsTool = {
|
|
979
|
+
name: "remove_keyword_exclusions",
|
|
980
|
+
title: "Remove Keyword Exclusions from a Niche",
|
|
981
|
+
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.`,
|
|
982
|
+
inputSchema: inputSchema19,
|
|
983
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
984
|
+
handler: async (args, ctx) => {
|
|
985
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
986
|
+
return await httpPost(
|
|
987
|
+
{ config: ctx.config, toolName: "remove_keyword_exclusions" },
|
|
988
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions/remove`,
|
|
989
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
|
|
788
994
|
// src/tools/delete-niche.ts
|
|
789
|
-
import { z as
|
|
790
|
-
var
|
|
791
|
-
nicheId:
|
|
792
|
-
confirm:
|
|
995
|
+
import { z as z19 } from "zod";
|
|
996
|
+
var inputSchema20 = {
|
|
997
|
+
nicheId: z19.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
|
|
998
|
+
confirm: z19.boolean().optional().describe(
|
|
793
999
|
"Must be true to proceed \u2014 deleting a niche destroys its keywords, competitors and dive history permanently. Confirm with the user first."
|
|
794
1000
|
)
|
|
795
1001
|
};
|
|
@@ -797,7 +1003,7 @@ var deleteNicheTool = {
|
|
|
797
1003
|
name: "delete_niche",
|
|
798
1004
|
title: "Delete a Niche",
|
|
799
1005
|
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).",
|
|
800
|
-
inputSchema:
|
|
1006
|
+
inputSchema: inputSchema20,
|
|
801
1007
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
802
1008
|
handler: async (args, ctx) => {
|
|
803
1009
|
const pending = requireConfirmation(
|
|
@@ -815,15 +1021,15 @@ var deleteNicheTool = {
|
|
|
815
1021
|
};
|
|
816
1022
|
|
|
817
1023
|
// src/tools/pause-rank-radar.ts
|
|
818
|
-
import { z as
|
|
819
|
-
var
|
|
820
|
-
rankRadarId:
|
|
1024
|
+
import { z as z20 } from "zod";
|
|
1025
|
+
var inputSchema21 = {
|
|
1026
|
+
rankRadarId: z20.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
|
|
821
1027
|
};
|
|
822
1028
|
var pauseRankRadarTool = {
|
|
823
1029
|
name: "pause_rank_radar",
|
|
824
1030
|
title: "Pause a Rank Radar",
|
|
825
1031
|
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.",
|
|
826
|
-
inputSchema:
|
|
1032
|
+
inputSchema: inputSchema21,
|
|
827
1033
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
828
1034
|
handler: async (args, ctx) => {
|
|
829
1035
|
await httpPost(
|
|
@@ -836,15 +1042,15 @@ var pauseRankRadarTool = {
|
|
|
836
1042
|
};
|
|
837
1043
|
|
|
838
1044
|
// src/tools/resume-rank-radar.ts
|
|
839
|
-
import { z as
|
|
840
|
-
var
|
|
841
|
-
rankRadarId:
|
|
1045
|
+
import { z as z21 } from "zod";
|
|
1046
|
+
var inputSchema22 = {
|
|
1047
|
+
rankRadarId: z21.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
|
|
842
1048
|
};
|
|
843
1049
|
var resumeRankRadarTool = {
|
|
844
1050
|
name: "resume_rank_radar",
|
|
845
1051
|
title: "Resume a Paused Rank Radar",
|
|
846
1052
|
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.",
|
|
847
|
-
inputSchema:
|
|
1053
|
+
inputSchema: inputSchema22,
|
|
848
1054
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
849
1055
|
handler: async (args, ctx) => {
|
|
850
1056
|
await httpPost(
|
|
@@ -857,10 +1063,10 @@ var resumeRankRadarTool = {
|
|
|
857
1063
|
};
|
|
858
1064
|
|
|
859
1065
|
// src/tools/delete-rank-radar.ts
|
|
860
|
-
import { z as
|
|
861
|
-
var
|
|
862
|
-
rankRadarId:
|
|
863
|
-
confirm:
|
|
1066
|
+
import { z as z22 } from "zod";
|
|
1067
|
+
var inputSchema23 = {
|
|
1068
|
+
rankRadarId: z22.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
|
|
1069
|
+
confirm: z22.boolean().optional().describe(
|
|
864
1070
|
"Must be true to proceed \u2014 deleting a Rank Radar destroys its ranking history permanently. Confirm with the user first."
|
|
865
1071
|
)
|
|
866
1072
|
};
|
|
@@ -868,7 +1074,7 @@ var deleteRankRadarTool = {
|
|
|
868
1074
|
name: "delete_rank_radar",
|
|
869
1075
|
title: "Delete a Rank Radar",
|
|
870
1076
|
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`.",
|
|
871
|
-
inputSchema:
|
|
1077
|
+
inputSchema: inputSchema23,
|
|
872
1078
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
873
1079
|
handler: async (args, ctx) => {
|
|
874
1080
|
const pending = requireConfirmation(
|
|
@@ -886,10 +1092,10 @@ var deleteRankRadarTool = {
|
|
|
886
1092
|
};
|
|
887
1093
|
|
|
888
1094
|
// src/tools/add-rank-radar-search-terms.ts
|
|
889
|
-
import { z as
|
|
890
|
-
var
|
|
891
|
-
rankRadarId:
|
|
892
|
-
searchTerms:
|
|
1095
|
+
import { z as z23 } from "zod";
|
|
1096
|
+
var inputSchema24 = {
|
|
1097
|
+
rankRadarId: z23.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
|
|
1098
|
+
searchTerms: z23.array(z23.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
|
|
893
1099
|
`The keywords to start tracking, 1\u2013${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored special characters (@, #, _, parentheses, \u2026) are rejected.`
|
|
894
1100
|
)
|
|
895
1101
|
};
|
|
@@ -897,7 +1103,7 @@ var addRankRadarSearchTermsTool = {
|
|
|
897
1103
|
name: "add_rank_radar_search_terms",
|
|
898
1104
|
title: "Add Search Terms to a Rank Radar",
|
|
899
1105
|
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.",
|
|
900
|
-
inputSchema:
|
|
1106
|
+
inputSchema: inputSchema24,
|
|
901
1107
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
902
1108
|
handler: async (args, ctx) => {
|
|
903
1109
|
return await httpPost(
|
|
@@ -909,10 +1115,10 @@ var addRankRadarSearchTermsTool = {
|
|
|
909
1115
|
};
|
|
910
1116
|
|
|
911
1117
|
// src/tools/pause-rank-radar-search-terms.ts
|
|
912
|
-
import { z as
|
|
913
|
-
var
|
|
914
|
-
rankRadarId:
|
|
915
|
-
rankRadarKeywordIds:
|
|
1118
|
+
import { z as z24 } from "zod";
|
|
1119
|
+
var inputSchema25 = {
|
|
1120
|
+
rankRadarId: z24.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1121
|
+
rankRadarKeywordIds: z24.array(z24.string().uuid()).min(1).describe(
|
|
916
1122
|
"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."
|
|
917
1123
|
)
|
|
918
1124
|
};
|
|
@@ -920,7 +1126,7 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
920
1126
|
name: "pause_rank_radar_search_terms",
|
|
921
1127
|
title: "Pause Search Terms of a Rank Radar",
|
|
922
1128
|
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.",
|
|
923
|
-
inputSchema:
|
|
1129
|
+
inputSchema: inputSchema25,
|
|
924
1130
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
925
1131
|
handler: async (args, ctx) => {
|
|
926
1132
|
await httpPost(
|
|
@@ -937,10 +1143,10 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
937
1143
|
};
|
|
938
1144
|
|
|
939
1145
|
// src/tools/resume-rank-radar-search-terms.ts
|
|
940
|
-
import { z as
|
|
941
|
-
var
|
|
942
|
-
rankRadarId:
|
|
943
|
-
rankRadarKeywordIds:
|
|
1146
|
+
import { z as z25 } from "zod";
|
|
1147
|
+
var inputSchema26 = {
|
|
1148
|
+
rankRadarId: z25.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1149
|
+
rankRadarKeywordIds: z25.array(z25.string().uuid()).min(1).describe(
|
|
944
1150
|
"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."
|
|
945
1151
|
)
|
|
946
1152
|
};
|
|
@@ -948,7 +1154,7 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
948
1154
|
name: "resume_rank_radar_search_terms",
|
|
949
1155
|
title: "Resume Paused Search Terms of a Rank Radar",
|
|
950
1156
|
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.",
|
|
951
|
-
inputSchema:
|
|
1157
|
+
inputSchema: inputSchema26,
|
|
952
1158
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
953
1159
|
handler: async (args, ctx) => {
|
|
954
1160
|
await httpPost(
|
|
@@ -965,16 +1171,16 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
965
1171
|
};
|
|
966
1172
|
|
|
967
1173
|
// src/tools/generate-listing-copy.ts
|
|
968
|
-
import { z as
|
|
969
|
-
var
|
|
970
|
-
nicheId:
|
|
971
|
-
strategy:
|
|
1174
|
+
import { z as z26 } from "zod";
|
|
1175
|
+
var inputSchema27 = {
|
|
1176
|
+
nicheId: z26.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
|
|
1177
|
+
strategy: z26.enum(LISTING_COPY_STRATEGIES).describe(
|
|
972
1178
|
"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."
|
|
973
1179
|
),
|
|
974
|
-
currentListing:
|
|
1180
|
+
currentListing: z26.record(z26.union([z26.string(), z26.array(z26.string())])).describe(
|
|
975
1181
|
'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.'
|
|
976
1182
|
),
|
|
977
|
-
confirm:
|
|
1183
|
+
confirm: z26.boolean().optional().describe(
|
|
978
1184
|
"Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
|
|
979
1185
|
)
|
|
980
1186
|
};
|
|
@@ -982,7 +1188,7 @@ var generateListingCopyTool = {
|
|
|
982
1188
|
name: "generate_listing_copy",
|
|
983
1189
|
title: "Generate Listing Copy for a Niche",
|
|
984
1190
|
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`.",
|
|
985
|
-
inputSchema:
|
|
1191
|
+
inputSchema: inputSchema27,
|
|
986
1192
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
987
1193
|
handler: async (args, ctx) => {
|
|
988
1194
|
const pending = requireConfirmation(
|
|
@@ -1000,16 +1206,16 @@ var generateListingCopyTool = {
|
|
|
1000
1206
|
};
|
|
1001
1207
|
|
|
1002
1208
|
// src/tools/get-listing-copy-generation-status.ts
|
|
1003
|
-
import { z as
|
|
1004
|
-
var
|
|
1005
|
-
nicheId:
|
|
1006
|
-
generationId:
|
|
1209
|
+
import { z as z27 } from "zod";
|
|
1210
|
+
var inputSchema28 = {
|
|
1211
|
+
nicheId: z27.string().min(1).describe("The niche the generation was started for."),
|
|
1212
|
+
generationId: z27.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
|
|
1007
1213
|
};
|
|
1008
1214
|
var getListingCopyGenerationStatusTool = {
|
|
1009
1215
|
name: "get_listing_copy_generation_status",
|
|
1010
1216
|
title: "Get Listing Copy Generation Status",
|
|
1011
1217
|
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`.",
|
|
1012
|
-
inputSchema:
|
|
1218
|
+
inputSchema: inputSchema28,
|
|
1013
1219
|
annotations: { readOnlyHint: true },
|
|
1014
1220
|
handler: async (args, ctx) => {
|
|
1015
1221
|
return await httpGet(
|
|
@@ -1020,16 +1226,16 @@ var getListingCopyGenerationStatusTool = {
|
|
|
1020
1226
|
};
|
|
1021
1227
|
|
|
1022
1228
|
// src/tools/list-seller-profiles.ts
|
|
1023
|
-
import { z as
|
|
1024
|
-
var
|
|
1025
|
-
currentPage:
|
|
1026
|
-
pageSize:
|
|
1229
|
+
import { z as z28 } from "zod";
|
|
1230
|
+
var inputSchema29 = {
|
|
1231
|
+
currentPage: z28.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1232
|
+
pageSize: z28.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1027
1233
|
};
|
|
1028
1234
|
var listSellerProfilesTool = {
|
|
1029
1235
|
name: "list_seller_profiles",
|
|
1030
1236
|
title: "List Connected Amazon Seller Profiles",
|
|
1031
1237
|
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).',
|
|
1032
|
-
inputSchema:
|
|
1238
|
+
inputSchema: inputSchema29,
|
|
1033
1239
|
annotations: { readOnlyHint: true },
|
|
1034
1240
|
handler: async (args, ctx) => {
|
|
1035
1241
|
return await httpGet(
|
|
@@ -1044,15 +1250,15 @@ var listSellerProfilesTool = {
|
|
|
1044
1250
|
};
|
|
1045
1251
|
|
|
1046
1252
|
// src/tools/get-seller-catalog.ts
|
|
1047
|
-
import { z as
|
|
1253
|
+
import { z as z30 } from "zod";
|
|
1048
1254
|
|
|
1049
1255
|
// src/tools/seller-scoped.ts
|
|
1050
|
-
import { z as
|
|
1256
|
+
import { z as z29 } from "zod";
|
|
1051
1257
|
var sellerScopedInputSchema = {
|
|
1052
|
-
sellerId:
|
|
1258
|
+
sellerId: z29.string().min(1).describe(
|
|
1053
1259
|
"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
1054
1260
|
),
|
|
1055
|
-
marketplace:
|
|
1261
|
+
marketplace: z29.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
|
|
1056
1262
|
};
|
|
1057
1263
|
function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
1058
1264
|
return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
|
|
@@ -1061,19 +1267,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
|
1061
1267
|
}
|
|
1062
1268
|
|
|
1063
1269
|
// src/tools/get-seller-catalog.ts
|
|
1064
|
-
var
|
|
1270
|
+
var inputSchema30 = {
|
|
1065
1271
|
...sellerScopedInputSchema,
|
|
1066
|
-
search:
|
|
1067
|
-
brand:
|
|
1068
|
-
status:
|
|
1069
|
-
currentPage:
|
|
1070
|
-
pageSize:
|
|
1272
|
+
search: z30.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1273
|
+
brand: z30.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1274
|
+
status: z30.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
|
|
1275
|
+
currentPage: z30.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1276
|
+
pageSize: z30.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1071
1277
|
};
|
|
1072
1278
|
var getSellerCatalogTool = {
|
|
1073
1279
|
name: "get_seller_catalog",
|
|
1074
1280
|
title: "List Catalog ASINs for a Seller",
|
|
1075
1281
|
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).",
|
|
1076
|
-
inputSchema:
|
|
1282
|
+
inputSchema: inputSchema30,
|
|
1077
1283
|
annotations: { readOnlyHint: true },
|
|
1078
1284
|
handler: async (args, ctx) => {
|
|
1079
1285
|
return await httpGet(
|
|
@@ -1091,28 +1297,28 @@ var getSellerCatalogTool = {
|
|
|
1091
1297
|
};
|
|
1092
1298
|
|
|
1093
1299
|
// src/tools/get-seller-listing-changes.ts
|
|
1094
|
-
import { z as
|
|
1095
|
-
var
|
|
1300
|
+
import { z as z31 } from "zod";
|
|
1301
|
+
var inputSchema31 = {
|
|
1096
1302
|
...sellerScopedInputSchema,
|
|
1097
|
-
types:
|
|
1098
|
-
asin:
|
|
1099
|
-
brand:
|
|
1100
|
-
search:
|
|
1101
|
-
startDate:
|
|
1102
|
-
endDate:
|
|
1103
|
-
sortBy:
|
|
1104
|
-
sortOrder:
|
|
1105
|
-
includeCorrelations:
|
|
1303
|
+
types: z31.array(z31.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
|
|
1304
|
+
asin: z31.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
|
|
1305
|
+
brand: z31.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1306
|
+
search: z31.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1307
|
+
startDate: z31.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."),
|
|
1308
|
+
endDate: z31.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."),
|
|
1309
|
+
sortBy: z31.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
|
|
1310
|
+
sortOrder: z31.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
|
|
1311
|
+
includeCorrelations: z31.boolean().optional().describe(
|
|
1106
1312
|
"When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
|
|
1107
1313
|
),
|
|
1108
|
-
currentPage:
|
|
1109
|
-
pageSize:
|
|
1314
|
+
currentPage: z31.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1315
|
+
pageSize: z31.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1110
1316
|
};
|
|
1111
1317
|
var getSellerListingChangesTool = {
|
|
1112
1318
|
name: "get_seller_listing_changes",
|
|
1113
1319
|
title: "List Listing Changes for a Seller",
|
|
1114
1320
|
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).",
|
|
1115
|
-
inputSchema:
|
|
1321
|
+
inputSchema: inputSchema31,
|
|
1116
1322
|
annotations: { readOnlyHint: true },
|
|
1117
1323
|
handler: async (args, ctx) => {
|
|
1118
1324
|
return await httpGet(
|
|
@@ -1137,40 +1343,40 @@ var getSellerListingChangesTool = {
|
|
|
1137
1343
|
};
|
|
1138
1344
|
|
|
1139
1345
|
// src/tools/list-ppc-campaigns.ts
|
|
1140
|
-
import { z as
|
|
1346
|
+
import { z as z32 } from "zod";
|
|
1141
1347
|
var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
|
|
1142
1348
|
var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
|
|
1143
|
-
var
|
|
1349
|
+
var inputSchema32 = {
|
|
1144
1350
|
...sellerScopedInputSchema,
|
|
1145
|
-
startDate:
|
|
1351
|
+
startDate: z32.string().regex(
|
|
1146
1352
|
ISO_8601,
|
|
1147
1353
|
"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
|
|
1148
1354
|
).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
|
|
1149
|
-
endDate:
|
|
1355
|
+
endDate: z32.string().regex(
|
|
1150
1356
|
ISO_8601,
|
|
1151
1357
|
"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
|
|
1152
1358
|
).optional().describe(
|
|
1153
1359
|
`End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
|
|
1154
1360
|
),
|
|
1155
|
-
asin:
|
|
1156
|
-
parentAsin:
|
|
1157
|
-
search:
|
|
1158
|
-
includePlacements:
|
|
1361
|
+
asin: z32.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
|
|
1362
|
+
parentAsin: z32.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
|
|
1363
|
+
search: z32.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
|
|
1364
|
+
includePlacements: z32.boolean().optional().describe(
|
|
1159
1365
|
"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."
|
|
1160
1366
|
),
|
|
1161
|
-
sortBy:
|
|
1367
|
+
sortBy: z32.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
|
|
1162
1368
|
`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.`
|
|
1163
1369
|
),
|
|
1164
|
-
sortOrder:
|
|
1165
|
-
state:
|
|
1166
|
-
currentPage:
|
|
1167
|
-
pageSize:
|
|
1370
|
+
sortOrder: z32.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
|
|
1371
|
+
state: z32.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
|
|
1372
|
+
currentPage: z32.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1373
|
+
pageSize: z32.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
1168
1374
|
};
|
|
1169
1375
|
var listPpcCampaignsTool = {
|
|
1170
1376
|
name: "list_ppc_campaigns",
|
|
1171
1377
|
title: "List PPC Campaigns for a Seller",
|
|
1172
1378
|
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\`.`,
|
|
1173
|
-
inputSchema:
|
|
1379
|
+
inputSchema: inputSchema32,
|
|
1174
1380
|
annotations: { readOnlyHint: true },
|
|
1175
1381
|
handler: async (args, ctx) => {
|
|
1176
1382
|
if (args.asin && args.parentAsin) {
|
|
@@ -1201,13 +1407,13 @@ var listPpcCampaignsTool = {
|
|
|
1201
1407
|
};
|
|
1202
1408
|
|
|
1203
1409
|
// src/tools/get-asin-inventory-distribution.ts
|
|
1204
|
-
import { z as
|
|
1205
|
-
var
|
|
1206
|
-
sellerId:
|
|
1410
|
+
import { z as z33 } from "zod";
|
|
1411
|
+
var inputSchema33 = {
|
|
1412
|
+
sellerId: z33.string().min(1).describe(
|
|
1207
1413
|
"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."
|
|
1208
1414
|
),
|
|
1209
|
-
marketplace:
|
|
1210
|
-
asin:
|
|
1415
|
+
marketplace: z33.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
1416
|
+
asin: z33.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
|
|
1211
1417
|
"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
|
|
1212
1418
|
)
|
|
1213
1419
|
};
|
|
@@ -1215,7 +1421,7 @@ var getAsinInventoryDistributionTool = {
|
|
|
1215
1421
|
name: "get_asin_inventory_distribution",
|
|
1216
1422
|
title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
|
|
1217
1423
|
description: "Use this when the user asks about inventory levels, stock by fulfillment center, or where their units are sitting across Amazon's fulfillment network for a specific ASIN. 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 totalSellableUnits and a per-FC `distribution` array (fc, state, availableStock, availableStockPercentage). `lastUpdatedAt` may be null when no successful inventory ingestion has occurred in the last 30 days.",
|
|
1218
|
-
inputSchema:
|
|
1424
|
+
inputSchema: inputSchema33,
|
|
1219
1425
|
annotations: { readOnlyHint: true },
|
|
1220
1426
|
handler: async (args, ctx) => {
|
|
1221
1427
|
return await httpGet(
|
|
@@ -1228,18 +1434,18 @@ var getAsinInventoryDistributionTool = {
|
|
|
1228
1434
|
};
|
|
1229
1435
|
|
|
1230
1436
|
// src/tools/alert-query.ts
|
|
1231
|
-
import { z as
|
|
1437
|
+
import { z as z34 } from "zod";
|
|
1232
1438
|
var alertsQueryInputSchema = {
|
|
1233
|
-
sellerId:
|
|
1234
|
-
marketplace:
|
|
1235
|
-
status:
|
|
1439
|
+
sellerId: z34.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
|
|
1440
|
+
marketplace: z34.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.'),
|
|
1441
|
+
status: z34.enum(ALERT_STATUSES).optional().describe(
|
|
1236
1442
|
"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
|
|
1237
1443
|
),
|
|
1238
|
-
updatedSince:
|
|
1444
|
+
updatedSince: z34.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(
|
|
1239
1445
|
"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."
|
|
1240
1446
|
),
|
|
1241
|
-
currentPage:
|
|
1242
|
-
pageSize:
|
|
1447
|
+
currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1448
|
+
pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1243
1449
|
};
|
|
1244
1450
|
|
|
1245
1451
|
// src/tools/list-indexing-issue-alerts.ts
|
|
@@ -1289,12 +1495,12 @@ var listBlindSpendAlertsTool = {
|
|
|
1289
1495
|
};
|
|
1290
1496
|
|
|
1291
1497
|
// src/tools/get-quota.ts
|
|
1292
|
-
var
|
|
1498
|
+
var inputSchema34 = {};
|
|
1293
1499
|
var getQuotaTool = {
|
|
1294
1500
|
name: "get_quota",
|
|
1295
1501
|
title: "Get Quota Usage",
|
|
1296
1502
|
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.",
|
|
1297
|
-
inputSchema:
|
|
1503
|
+
inputSchema: inputSchema34,
|
|
1298
1504
|
annotations: { readOnlyHint: true },
|
|
1299
1505
|
handler: async (_args, ctx) => {
|
|
1300
1506
|
return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
|
|
@@ -1302,20 +1508,20 @@ var getQuotaTool = {
|
|
|
1302
1508
|
};
|
|
1303
1509
|
|
|
1304
1510
|
// src/tools/list-usage.ts
|
|
1305
|
-
import { z as
|
|
1306
|
-
var
|
|
1307
|
-
type:
|
|
1308
|
-
search:
|
|
1309
|
-
startDate:
|
|
1310
|
-
endDate:
|
|
1311
|
-
currentPage:
|
|
1312
|
-
pageSize:
|
|
1511
|
+
import { z as z35 } from "zod";
|
|
1512
|
+
var inputSchema35 = {
|
|
1513
|
+
type: z35.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
|
|
1514
|
+
search: z35.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
|
|
1515
|
+
startDate: z35.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."),
|
|
1516
|
+
endDate: z35.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."),
|
|
1517
|
+
currentPage: z35.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1518
|
+
pageSize: z35.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
|
|
1313
1519
|
};
|
|
1314
1520
|
var listUsageTool = {
|
|
1315
1521
|
name: "list_usage",
|
|
1316
1522
|
title: "List Billable Usage Logs",
|
|
1317
1523
|
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).",
|
|
1318
|
-
inputSchema:
|
|
1524
|
+
inputSchema: inputSchema35,
|
|
1319
1525
|
annotations: { readOnlyHint: true },
|
|
1320
1526
|
handler: async (args, ctx) => {
|
|
1321
1527
|
return await httpGet(
|
|
@@ -1337,6 +1543,7 @@ var listUsageTool = {
|
|
|
1337
1543
|
var allTools = [
|
|
1338
1544
|
listNichesTool,
|
|
1339
1545
|
getNicheKeywordsTool,
|
|
1546
|
+
listKeywordExclusionsTool,
|
|
1340
1547
|
getNicheRootsTool,
|
|
1341
1548
|
getNicheCompetitorsTool,
|
|
1342
1549
|
getRankingJuiceTool,
|
|
@@ -1349,6 +1556,10 @@ var allTools = [
|
|
|
1349
1556
|
createNicheDiveFromCompetitorsListTool,
|
|
1350
1557
|
rediveNicheTool,
|
|
1351
1558
|
getDiveStatusTool,
|
|
1559
|
+
addNicheKeywordsTool,
|
|
1560
|
+
removeNicheKeywordsTool,
|
|
1561
|
+
addKeywordExclusionsTool,
|
|
1562
|
+
removeKeywordExclusionsTool,
|
|
1352
1563
|
addRankRadarSearchTermsTool,
|
|
1353
1564
|
pauseRankRadarSearchTermsTool,
|
|
1354
1565
|
resumeRankRadarSearchTermsTool,
|
|
@@ -1371,6 +1582,21 @@ var allTools = [
|
|
|
1371
1582
|
|
|
1372
1583
|
// src/whats-new.ts
|
|
1373
1584
|
var WHATS_NEW = [
|
|
1585
|
+
{
|
|
1586
|
+
version: "0.17.0",
|
|
1587
|
+
released: "2026-10-12",
|
|
1588
|
+
items: [
|
|
1589
|
+
"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.",
|
|
1590
|
+
"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."
|
|
1591
|
+
]
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
version: "0.16.1",
|
|
1595
|
+
released: "2026-09-29",
|
|
1596
|
+
items: [
|
|
1597
|
+
"`get_rank_radar_data` returns every tracked keyword of a Rank Radar in one answer again, also in AI apps that still show its older inputs. Pass `currentPage` / `pageSize` to read one page at a time."
|
|
1598
|
+
]
|
|
1599
|
+
},
|
|
1374
1600
|
{
|
|
1375
1601
|
version: "0.16.0",
|
|
1376
1602
|
released: "2026-09-28",
|
|
@@ -1408,8 +1634,8 @@ var WHATS_NEW = [
|
|
|
1408
1634
|
}
|
|
1409
1635
|
];
|
|
1410
1636
|
var INSTRUCTIONS_RELEASES = 2;
|
|
1411
|
-
function formatEntries(
|
|
1412
|
-
return
|
|
1637
|
+
function formatEntries(entries2) {
|
|
1638
|
+
return entries2.map((e) => `${e.released} (v${e.version}):
|
|
1413
1639
|
${e.items.map((i) => `- ${i}`).join("\n")}`).join("\n\n");
|
|
1414
1640
|
}
|
|
1415
1641
|
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.
|