@datadive-tools/mcp 0.16.1 → 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 +11 -0
- package/dist/index.js +376 -185
- package/dist/index.js.map +1 -1
- package/dist/lib.js +376 -185
- 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.17.0";
|
|
143
143
|
|
|
144
144
|
// src/http/client.ts
|
|
145
145
|
var PKG_VERSION = version;
|
|
@@ -316,7 +316,7 @@ var inputSchema2 = {
|
|
|
316
316
|
var getNicheKeywordsTool = {
|
|
317
317
|
name: "get_niche_keywords",
|
|
318
318
|
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.',
|
|
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 }), 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
320
|
inputSchema: inputSchema2,
|
|
321
321
|
annotations: { readOnlyHint: true },
|
|
322
322
|
handler: async (args, ctx) => {
|
|
@@ -327,16 +327,35 @@ var getNicheKeywordsTool = {
|
|
|
327
327
|
}
|
|
328
328
|
};
|
|
329
329
|
|
|
330
|
-
// src/tools/
|
|
330
|
+
// src/tools/list-keyword-exclusions.ts
|
|
331
331
|
import { z as z3 } from "zod";
|
|
332
332
|
var inputSchema3 = {
|
|
333
333
|
nicheId: z3.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
334
334
|
};
|
|
335
|
+
var listKeywordExclusionsTool = {
|
|
336
|
+
name: "list_keyword_exclusions",
|
|
337
|
+
title: "List a Niche's Keyword Exclusions",
|
|
338
|
+
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`.",
|
|
339
|
+
inputSchema: inputSchema3,
|
|
340
|
+
annotations: { readOnlyHint: true },
|
|
341
|
+
handler: async (args, ctx) => {
|
|
342
|
+
return await httpGet(
|
|
343
|
+
{ config: ctx.config, toolName: "list_keyword_exclusions" },
|
|
344
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// src/tools/get-niche-roots.ts
|
|
350
|
+
import { z as z4 } from "zod";
|
|
351
|
+
var inputSchema4 = {
|
|
352
|
+
nicheId: z4.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
353
|
+
};
|
|
335
354
|
var getNicheRootsTool = {
|
|
336
355
|
name: "get_niche_roots",
|
|
337
356
|
title: "Get Keyword Roots for a Niche",
|
|
338
357
|
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:
|
|
358
|
+
inputSchema: inputSchema4,
|
|
340
359
|
annotations: { readOnlyHint: true },
|
|
341
360
|
handler: async (args, ctx) => {
|
|
342
361
|
return await httpGet(
|
|
@@ -347,15 +366,15 @@ var getNicheRootsTool = {
|
|
|
347
366
|
};
|
|
348
367
|
|
|
349
368
|
// src/tools/get-niche-competitors.ts
|
|
350
|
-
import { z as
|
|
351
|
-
var
|
|
352
|
-
nicheId:
|
|
369
|
+
import { z as z5 } from "zod";
|
|
370
|
+
var inputSchema5 = {
|
|
371
|
+
nicheId: z5.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
353
372
|
};
|
|
354
373
|
var getNicheCompetitorsTool = {
|
|
355
374
|
name: "get_niche_competitors",
|
|
356
375
|
title: "Get Competitors and Niche Statistics",
|
|
357
376
|
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:
|
|
377
|
+
inputSchema: inputSchema5,
|
|
359
378
|
annotations: { readOnlyHint: true },
|
|
360
379
|
handler: async (args, ctx) => {
|
|
361
380
|
return await httpGet(
|
|
@@ -366,15 +385,15 @@ var getNicheCompetitorsTool = {
|
|
|
366
385
|
};
|
|
367
386
|
|
|
368
387
|
// src/tools/get-ranking-juice.ts
|
|
369
|
-
import { z as
|
|
370
|
-
var
|
|
371
|
-
nicheId:
|
|
388
|
+
import { z as z6 } from "zod";
|
|
389
|
+
var inputSchema6 = {
|
|
390
|
+
nicheId: z6.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
372
391
|
};
|
|
373
392
|
var getRankingJuiceTool = {
|
|
374
393
|
name: "get_ranking_juice",
|
|
375
394
|
title: "Get Ranking Juices for a Niche",
|
|
376
395
|
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:
|
|
396
|
+
inputSchema: inputSchema6,
|
|
378
397
|
annotations: { readOnlyHint: true },
|
|
379
398
|
handler: async (args, ctx) => {
|
|
380
399
|
return await httpGet(
|
|
@@ -385,7 +404,7 @@ var getRankingJuiceTool = {
|
|
|
385
404
|
};
|
|
386
405
|
|
|
387
406
|
// src/tools/list-rank-radars.ts
|
|
388
|
-
import { z as
|
|
407
|
+
import { z as z7 } from "zod";
|
|
389
408
|
|
|
390
409
|
// src/types/api.ts
|
|
391
410
|
var ISO_8601 = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -401,6 +420,10 @@ var SUPPORTED_MARKETPLACES = [
|
|
|
401
420
|
"it",
|
|
402
421
|
"co.jp"
|
|
403
422
|
];
|
|
423
|
+
var NICHE_KEYWORDS_MAX_PER_CALL = 100;
|
|
424
|
+
var NICHE_KEYWORD_MAX_LENGTH = 255;
|
|
425
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
|
|
426
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
|
|
404
427
|
var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
|
|
405
428
|
var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
|
|
406
429
|
var PPC_CAMPAIGN_BASE_SORT_BY = [
|
|
@@ -446,14 +469,14 @@ var LISTING_COPY_STRATEGIES = ["cosmo", "ranking-juice", "nlp", "cosmo-rufus"];
|
|
|
446
469
|
|
|
447
470
|
// src/tools/list-rank-radars.ts
|
|
448
471
|
var LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;
|
|
449
|
-
var
|
|
450
|
-
currentPage:
|
|
451
|
-
pageSize:
|
|
452
|
-
nicheId:
|
|
453
|
-
status:
|
|
472
|
+
var inputSchema7 = {
|
|
473
|
+
currentPage: z7.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
474
|
+
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.`),
|
|
475
|
+
nicheId: z7.string().min(1).optional().describe("Filter Rank Radars by Niche identifier. Use after `list_niches`."),
|
|
476
|
+
status: z7.enum(RANK_RADAR_STATUSES).optional().describe(
|
|
454
477
|
"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
478
|
),
|
|
456
|
-
searchText:
|
|
479
|
+
searchText: z7.string().optional().describe("Filter Rank Radars by ASIN or product title.")
|
|
457
480
|
};
|
|
458
481
|
function minimize(item) {
|
|
459
482
|
const { asin, ...rest } = item;
|
|
@@ -463,7 +486,7 @@ var listRankRadarsTool = {
|
|
|
463
486
|
name: "list_rank_radars",
|
|
464
487
|
title: "List Rank Radars",
|
|
465
488
|
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:
|
|
489
|
+
inputSchema: inputSchema7,
|
|
467
490
|
annotations: { readOnlyHint: true },
|
|
468
491
|
handler: async (args, ctx) => {
|
|
469
492
|
const page = await httpGet(
|
|
@@ -482,18 +505,18 @@ var listRankRadarsTool = {
|
|
|
482
505
|
};
|
|
483
506
|
|
|
484
507
|
// src/tools/rank-radar-keyword-query.ts
|
|
485
|
-
import { z as
|
|
508
|
+
import { z as z8 } from "zod";
|
|
486
509
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
487
510
|
var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
|
|
488
511
|
var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
|
|
489
512
|
var rankRadarKeywordQueryInputSchema = {
|
|
490
|
-
rankRadarId:
|
|
491
|
-
startDate:
|
|
492
|
-
endDate:
|
|
513
|
+
rankRadarId: z8.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
|
|
514
|
+
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)."),
|
|
515
|
+
endDate: z8.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
|
|
493
516
|
`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
517
|
),
|
|
495
|
-
currentPage:
|
|
496
|
-
pageSize:
|
|
518
|
+
currentPage: z8.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
|
|
519
|
+
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
520
|
};
|
|
498
521
|
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
522
|
function rankRadarPath(rankRadarId, suffix = "") {
|
|
@@ -509,7 +532,7 @@ function rankRadarKeywordQuery(args) {
|
|
|
509
532
|
}
|
|
510
533
|
|
|
511
534
|
// src/tools/get-rank-radar-data.ts
|
|
512
|
-
var
|
|
535
|
+
var inputSchema8 = rankRadarKeywordQueryInputSchema;
|
|
513
536
|
async function readAllKeywords(args, ctx) {
|
|
514
537
|
const request = { config: ctx.config, toolName: "get_rank_radar_data" };
|
|
515
538
|
const path = rankRadarPath(args.rankRadarId);
|
|
@@ -539,7 +562,7 @@ var getRankRadarDataTool = {
|
|
|
539
562
|
name: "get_rank_radar_data",
|
|
540
563
|
title: "Get Keyword Rankings for a Rank Radar",
|
|
541
564
|
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\`.`,
|
|
542
|
-
inputSchema:
|
|
565
|
+
inputSchema: inputSchema8,
|
|
543
566
|
annotations: { readOnlyHint: true },
|
|
544
567
|
handler: async (args, ctx) => {
|
|
545
568
|
if (args.currentPage === void 0 && args.pageSize === void 0) {
|
|
@@ -554,12 +577,12 @@ var getRankRadarDataTool = {
|
|
|
554
577
|
};
|
|
555
578
|
|
|
556
579
|
// src/tools/get-rank-radar-sqp-data.ts
|
|
557
|
-
var
|
|
580
|
+
var inputSchema9 = rankRadarKeywordQueryInputSchema;
|
|
558
581
|
var getRankRadarSqpDataTool = {
|
|
559
582
|
name: "get_rank_radar_sqp_data",
|
|
560
583
|
title: "Get Search Query Performance for a Rank Radar",
|
|
561
584
|
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:
|
|
585
|
+
inputSchema: inputSchema9,
|
|
563
586
|
annotations: { readOnlyHint: true },
|
|
564
587
|
handler: async (args, ctx) => {
|
|
565
588
|
return await httpGet(
|
|
@@ -571,10 +594,10 @@ var getRankRadarSqpDataTool = {
|
|
|
571
594
|
};
|
|
572
595
|
|
|
573
596
|
// src/tools/get-rank-radar-ppc-data.ts
|
|
574
|
-
import { z as
|
|
575
|
-
var
|
|
597
|
+
import { z as z9 } from "zod";
|
|
598
|
+
var inputSchema10 = {
|
|
576
599
|
...rankRadarKeywordQueryInputSchema,
|
|
577
|
-
includeCampaigns:
|
|
600
|
+
includeCampaigns: z9.boolean().optional().describe(
|
|
578
601
|
"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
602
|
)
|
|
580
603
|
};
|
|
@@ -582,7 +605,7 @@ var getRankRadarPpcDataTool = {
|
|
|
582
605
|
name: "get_rank_radar_ppc_data",
|
|
583
606
|
title: "Get PPC Data for a Rank Radar",
|
|
584
607
|
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:
|
|
608
|
+
inputSchema: inputSchema10,
|
|
586
609
|
annotations: { readOnlyHint: true },
|
|
587
610
|
handler: async (args, ctx) => {
|
|
588
611
|
return await httpGet(
|
|
@@ -594,7 +617,7 @@ var getRankRadarPpcDataTool = {
|
|
|
594
617
|
};
|
|
595
618
|
|
|
596
619
|
// src/tools/create-rank-radar.ts
|
|
597
|
-
import { z as
|
|
620
|
+
import { z as z10 } from "zod";
|
|
598
621
|
|
|
599
622
|
// src/tools/confirm.ts
|
|
600
623
|
function needsConfirmation(confirm, ctx) {
|
|
@@ -615,11 +638,11 @@ function requireConfirmation(confirm, ctx, costNote) {
|
|
|
615
638
|
|
|
616
639
|
// src/tools/create-rank-radar.ts
|
|
617
640
|
var PATH = "/v1/niches/rank-radars";
|
|
618
|
-
var
|
|
619
|
-
asin:
|
|
620
|
-
numberOfKeywords:
|
|
621
|
-
nicheId:
|
|
622
|
-
confirm:
|
|
641
|
+
var inputSchema11 = {
|
|
642
|
+
asin: z10.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
643
|
+
numberOfKeywords: z10.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
644
|
+
nicheId: z10.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
|
|
645
|
+
confirm: z10.boolean().optional().describe(
|
|
623
646
|
"Must be true to proceed \u2014 creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first."
|
|
624
647
|
)
|
|
625
648
|
};
|
|
@@ -627,7 +650,7 @@ var createRankRadarTool = {
|
|
|
627
650
|
name: "create_rank_radar",
|
|
628
651
|
title: "Create a Rank Radar",
|
|
629
652
|
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:
|
|
653
|
+
inputSchema: inputSchema11,
|
|
631
654
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
632
655
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
633
656
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -652,20 +675,20 @@ var createRankRadarTool = {
|
|
|
652
675
|
};
|
|
653
676
|
|
|
654
677
|
// src/tools/create-niche-dive.ts
|
|
655
|
-
import { z as
|
|
656
|
-
var
|
|
657
|
-
marketplace:
|
|
678
|
+
import { z as z11 } from "zod";
|
|
679
|
+
var inputSchema12 = {
|
|
680
|
+
marketplace: z11.enum(SUPPORTED_MARKETPLACES).describe(
|
|
658
681
|
"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
682
|
),
|
|
660
|
-
asin:
|
|
661
|
-
numberOfCompetitors:
|
|
662
|
-
confirm:
|
|
683
|
+
asin: z11.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
|
|
684
|
+
numberOfCompetitors: z11.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
|
|
685
|
+
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
686
|
};
|
|
664
687
|
var createNicheDiveTool = {
|
|
665
688
|
name: "create_niche_dive",
|
|
666
689
|
title: "Create a Niche Dive",
|
|
667
690
|
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:
|
|
691
|
+
inputSchema: inputSchema12,
|
|
669
692
|
// destructiveHint is technically "additive" here (a dive creates a new niche, it
|
|
670
693
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
671
694
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -690,20 +713,20 @@ var createNicheDiveTool = {
|
|
|
690
713
|
};
|
|
691
714
|
|
|
692
715
|
// src/tools/create-niche-dive-from-competitors-list.ts
|
|
693
|
-
import { z as
|
|
716
|
+
import { z as z12 } from "zod";
|
|
694
717
|
var DIVE_FROM_COMPETITORS_MIN_ASINS = 2;
|
|
695
718
|
var DIVE_FROM_COMPETITORS_MAX_ASINS = 200;
|
|
696
|
-
var normalizedAsins =
|
|
719
|
+
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
720
|
message: "asins must not contain the same ASIN twice"
|
|
698
721
|
});
|
|
699
|
-
var
|
|
700
|
-
marketplace:
|
|
722
|
+
var inputSchema13 = {
|
|
723
|
+
marketplace: z12.enum(SUPPORTED_MARKETPLACES).describe(
|
|
701
724
|
"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
725
|
),
|
|
703
726
|
asins: normalizedAsins.describe(
|
|
704
727
|
`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
728
|
),
|
|
706
|
-
confirm:
|
|
729
|
+
confirm: z12.boolean().optional().describe(
|
|
707
730
|
"Must be true to proceed \u2014 this dive spends dive tokens for every ASIN. Confirm the cost with the user first."
|
|
708
731
|
)
|
|
709
732
|
};
|
|
@@ -711,7 +734,7 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
711
734
|
name: "create_niche_dive_from_competitors_list",
|
|
712
735
|
title: "Create a Niche Dive from a Competitor List",
|
|
713
736
|
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:
|
|
737
|
+
inputSchema: inputSchema13,
|
|
715
738
|
// Additive rather than destructive, but flagged destructive so clients prompt before the
|
|
716
739
|
// irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.
|
|
717
740
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -731,23 +754,23 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
731
754
|
};
|
|
732
755
|
|
|
733
756
|
// src/tools/redive-niche.ts
|
|
734
|
-
import { z as
|
|
735
|
-
var
|
|
736
|
-
nicheId:
|
|
737
|
-
mode:
|
|
757
|
+
import { z as z13 } from "zod";
|
|
758
|
+
var inputSchema14 = {
|
|
759
|
+
nicheId: z13.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
|
|
760
|
+
mode: z13.enum(["same_competitors", "discover"]).describe(
|
|
738
761
|
"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
762
|
),
|
|
740
|
-
numberOfCompetitors:
|
|
763
|
+
numberOfCompetitors: z13.number().int().min(2).optional().describe(
|
|
741
764
|
"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
765
|
),
|
|
743
|
-
heroAsin:
|
|
766
|
+
heroAsin: z13.string().min(1).optional().describe(
|
|
744
767
|
"`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
768
|
),
|
|
746
|
-
lockedAsins:
|
|
769
|
+
lockedAsins: z13.array(z13.string().min(1)).optional().describe(
|
|
747
770
|
"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
|
|
748
771
|
),
|
|
749
|
-
excludedAsins:
|
|
750
|
-
confirm:
|
|
772
|
+
excludedAsins: z13.array(z13.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
|
|
773
|
+
confirm: z13.boolean().optional().describe(
|
|
751
774
|
"Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
|
|
752
775
|
)
|
|
753
776
|
};
|
|
@@ -755,7 +778,7 @@ var rediveNicheTool = {
|
|
|
755
778
|
name: "redive_niche",
|
|
756
779
|
title: "Re-dive an Existing Niche",
|
|
757
780
|
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:
|
|
781
|
+
inputSchema: inputSchema14,
|
|
759
782
|
// Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
|
|
760
783
|
// irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
|
|
761
784
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -788,17 +811,17 @@ var rediveNicheTool = {
|
|
|
788
811
|
};
|
|
789
812
|
|
|
790
813
|
// 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`."
|
|
814
|
+
import { z as z14 } from "zod";
|
|
815
|
+
var inputSchema15 = {
|
|
816
|
+
diveId: z14.string().min(1).describe(
|
|
817
|
+
"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
818
|
)
|
|
796
819
|
};
|
|
797
820
|
var getDiveStatusTool = {
|
|
798
821
|
name: "get_dive_status",
|
|
799
822
|
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:
|
|
823
|
+
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.",
|
|
824
|
+
inputSchema: inputSchema15,
|
|
802
825
|
annotations: { readOnlyHint: true },
|
|
803
826
|
handler: async (args, ctx) => {
|
|
804
827
|
return await httpGet(
|
|
@@ -808,11 +831,166 @@ var getDiveStatusTool = {
|
|
|
808
831
|
}
|
|
809
832
|
};
|
|
810
833
|
|
|
834
|
+
// src/tools/add-niche-keywords.ts
|
|
835
|
+
import { z as z16 } from "zod";
|
|
836
|
+
|
|
837
|
+
// src/tools/niche-keyword-list.ts
|
|
838
|
+
import { z as z15 } from "zod";
|
|
839
|
+
var nicheKeyword = () => z15.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);
|
|
840
|
+
function uniqueKeywords(schema, field) {
|
|
841
|
+
return schema.refine((keywords) => new Set(keywords).size === keywords.length, {
|
|
842
|
+
message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
var nicheKeywordList = uniqueKeywords(
|
|
846
|
+
z15.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),
|
|
847
|
+
"keywords"
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
// src/tools/add-niche-keywords.ts
|
|
851
|
+
var inputSchema16 = {
|
|
852
|
+
nicheId: z16.string().min(1).describe("The niche whose Master Keyword List to add to. Get one from `list_niches`."),
|
|
853
|
+
keywords: nicheKeywordList.describe(
|
|
854
|
+
`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.`
|
|
855
|
+
),
|
|
856
|
+
confirm: z16.boolean().optional().describe(
|
|
857
|
+
"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."
|
|
858
|
+
),
|
|
859
|
+
maxTokensUsed: z16.number().int().min(0).optional().describe(
|
|
860
|
+
"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."
|
|
861
|
+
)
|
|
862
|
+
};
|
|
863
|
+
function isMaxTokensExceededBody(body) {
|
|
864
|
+
if (!body || typeof body !== "object") return false;
|
|
865
|
+
const b = body;
|
|
866
|
+
return b.error === "MAX_TOKENS_EXCEEDED" && typeof b.tokensUsed === "number";
|
|
867
|
+
}
|
|
868
|
+
var addNicheKeywordsTool = {
|
|
869
|
+
name: "add_niche_keywords",
|
|
870
|
+
title: "Add Keywords to a Niche's Master Keyword List",
|
|
871
|
+
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.",
|
|
872
|
+
inputSchema: inputSchema16,
|
|
873
|
+
// Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients
|
|
874
|
+
// need to prompt on. The confirm gate covers the paid path regardless.
|
|
875
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
876
|
+
handler: async (args, ctx) => {
|
|
877
|
+
const request = { config: ctx.config, toolName: "add_niche_keywords" };
|
|
878
|
+
const path = `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`;
|
|
879
|
+
if (needsConfirmation(args.confirm, ctx)) {
|
|
880
|
+
try {
|
|
881
|
+
return await httpPost(request, path, {
|
|
882
|
+
keywords: args.keywords,
|
|
883
|
+
maxTokensUsed: 0
|
|
884
|
+
});
|
|
885
|
+
} catch (err) {
|
|
886
|
+
if (err instanceof ApiError && isMaxTokensExceededBody(err.body)) {
|
|
887
|
+
const tokenCost = err.body.tokensUsed;
|
|
888
|
+
return {
|
|
889
|
+
...confirmationRequired(
|
|
890
|
+
`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.`
|
|
891
|
+
),
|
|
892
|
+
tokenCost
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
throw err;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return await httpPost(request, path, {
|
|
899
|
+
keywords: args.keywords,
|
|
900
|
+
maxTokensUsed: args.maxTokensUsed
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// src/tools/remove-niche-keywords.ts
|
|
906
|
+
import { z as z17 } from "zod";
|
|
907
|
+
var inputSchema17 = {
|
|
908
|
+
nicheId: z17.string().min(1).describe("The niche whose Master Keyword List to remove from. Get one from `list_niches`."),
|
|
909
|
+
keywords: nicheKeywordList.describe(
|
|
910
|
+
`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.`
|
|
911
|
+
)
|
|
912
|
+
};
|
|
913
|
+
var removeNicheKeywordsTool = {
|
|
914
|
+
name: "remove_niche_keywords",
|
|
915
|
+
title: "Remove Keywords from a Niche's Master Keyword List",
|
|
916
|
+
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.",
|
|
917
|
+
inputSchema: inputSchema17,
|
|
918
|
+
// Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.
|
|
919
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
920
|
+
handler: async (args, ctx) => {
|
|
921
|
+
return await httpPost(
|
|
922
|
+
{ config: ctx.config, toolName: "remove_niche_keywords" },
|
|
923
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keywords/remove`,
|
|
924
|
+
{ keywords: args.keywords }
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// src/tools/keyword-exclusions-input.ts
|
|
930
|
+
import { z as z18 } from "zod";
|
|
931
|
+
var entries = (field) => uniqueKeywords(z18.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();
|
|
932
|
+
var keywordExclusionsInputSchema = (verb) => ({
|
|
933
|
+
nicheId: z18.string().min(1).describe("The niche whose keyword exclusions to change. Get one from `list_niches`."),
|
|
934
|
+
exact: entries("exact").describe(
|
|
935
|
+
`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.`
|
|
936
|
+
),
|
|
937
|
+
phrase: entries("phrase").describe(
|
|
938
|
+
`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.`
|
|
939
|
+
)
|
|
940
|
+
});
|
|
941
|
+
function assertExclusionCount(exact, phrase) {
|
|
942
|
+
const total = (exact?.length ?? 0) + (phrase?.length ?? 0);
|
|
943
|
+
if (total === 0 || total > KEYWORD_EXCLUSIONS_MAX_PER_CALL) {
|
|
944
|
+
throw new ApiError(
|
|
945
|
+
"bad_request",
|
|
946
|
+
400,
|
|
947
|
+
`Send 1 to ${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\` (got ${total}). Split a longer list over several calls.`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/tools/add-keyword-exclusions.ts
|
|
953
|
+
var inputSchema18 = keywordExclusionsInputSchema("exclude");
|
|
954
|
+
var addKeywordExclusionsTool = {
|
|
955
|
+
name: "add_keyword_exclusions",
|
|
956
|
+
title: "Exclude Keywords from a Niche",
|
|
957
|
+
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.`,
|
|
958
|
+
inputSchema: inputSchema18,
|
|
959
|
+
// Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.
|
|
960
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
961
|
+
handler: async (args, ctx) => {
|
|
962
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
963
|
+
return await httpPost(
|
|
964
|
+
{ config: ctx.config, toolName: "add_keyword_exclusions" },
|
|
965
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,
|
|
966
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// src/tools/remove-keyword-exclusions.ts
|
|
972
|
+
var inputSchema19 = keywordExclusionsInputSchema("stop excluding");
|
|
973
|
+
var removeKeywordExclusionsTool = {
|
|
974
|
+
name: "remove_keyword_exclusions",
|
|
975
|
+
title: "Remove Keyword Exclusions from a Niche",
|
|
976
|
+
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.`,
|
|
977
|
+
inputSchema: inputSchema19,
|
|
978
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
979
|
+
handler: async (args, ctx) => {
|
|
980
|
+
assertExclusionCount(args.exact, args.phrase);
|
|
981
|
+
return await httpPost(
|
|
982
|
+
{ config: ctx.config, toolName: "remove_keyword_exclusions" },
|
|
983
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions/remove`,
|
|
984
|
+
{ exact: args.exact, phrase: args.phrase }
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
811
989
|
// src/tools/delete-niche.ts
|
|
812
|
-
import { z as
|
|
813
|
-
var
|
|
814
|
-
nicheId:
|
|
815
|
-
confirm:
|
|
990
|
+
import { z as z19 } from "zod";
|
|
991
|
+
var inputSchema20 = {
|
|
992
|
+
nicheId: z19.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
|
|
993
|
+
confirm: z19.boolean().optional().describe(
|
|
816
994
|
"Must be true to proceed \u2014 deleting a niche destroys its keywords, competitors and dive history permanently. Confirm with the user first."
|
|
817
995
|
)
|
|
818
996
|
};
|
|
@@ -820,7 +998,7 @@ var deleteNicheTool = {
|
|
|
820
998
|
name: "delete_niche",
|
|
821
999
|
title: "Delete a Niche",
|
|
822
1000
|
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:
|
|
1001
|
+
inputSchema: inputSchema20,
|
|
824
1002
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
825
1003
|
handler: async (args, ctx) => {
|
|
826
1004
|
const pending = requireConfirmation(
|
|
@@ -838,15 +1016,15 @@ var deleteNicheTool = {
|
|
|
838
1016
|
};
|
|
839
1017
|
|
|
840
1018
|
// src/tools/pause-rank-radar.ts
|
|
841
|
-
import { z as
|
|
842
|
-
var
|
|
843
|
-
rankRadarId:
|
|
1019
|
+
import { z as z20 } from "zod";
|
|
1020
|
+
var inputSchema21 = {
|
|
1021
|
+
rankRadarId: z20.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
|
|
844
1022
|
};
|
|
845
1023
|
var pauseRankRadarTool = {
|
|
846
1024
|
name: "pause_rank_radar",
|
|
847
1025
|
title: "Pause a Rank Radar",
|
|
848
1026
|
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:
|
|
1027
|
+
inputSchema: inputSchema21,
|
|
850
1028
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
851
1029
|
handler: async (args, ctx) => {
|
|
852
1030
|
await httpPost(
|
|
@@ -859,15 +1037,15 @@ var pauseRankRadarTool = {
|
|
|
859
1037
|
};
|
|
860
1038
|
|
|
861
1039
|
// src/tools/resume-rank-radar.ts
|
|
862
|
-
import { z as
|
|
863
|
-
var
|
|
864
|
-
rankRadarId:
|
|
1040
|
+
import { z as z21 } from "zod";
|
|
1041
|
+
var inputSchema22 = {
|
|
1042
|
+
rankRadarId: z21.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
|
|
865
1043
|
};
|
|
866
1044
|
var resumeRankRadarTool = {
|
|
867
1045
|
name: "resume_rank_radar",
|
|
868
1046
|
title: "Resume a Paused Rank Radar",
|
|
869
1047
|
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:
|
|
1048
|
+
inputSchema: inputSchema22,
|
|
871
1049
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
872
1050
|
handler: async (args, ctx) => {
|
|
873
1051
|
await httpPost(
|
|
@@ -880,10 +1058,10 @@ var resumeRankRadarTool = {
|
|
|
880
1058
|
};
|
|
881
1059
|
|
|
882
1060
|
// src/tools/delete-rank-radar.ts
|
|
883
|
-
import { z as
|
|
884
|
-
var
|
|
885
|
-
rankRadarId:
|
|
886
|
-
confirm:
|
|
1061
|
+
import { z as z22 } from "zod";
|
|
1062
|
+
var inputSchema23 = {
|
|
1063
|
+
rankRadarId: z22.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
|
|
1064
|
+
confirm: z22.boolean().optional().describe(
|
|
887
1065
|
"Must be true to proceed \u2014 deleting a Rank Radar destroys its ranking history permanently. Confirm with the user first."
|
|
888
1066
|
)
|
|
889
1067
|
};
|
|
@@ -891,7 +1069,7 @@ var deleteRankRadarTool = {
|
|
|
891
1069
|
name: "delete_rank_radar",
|
|
892
1070
|
title: "Delete a Rank Radar",
|
|
893
1071
|
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:
|
|
1072
|
+
inputSchema: inputSchema23,
|
|
895
1073
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
896
1074
|
handler: async (args, ctx) => {
|
|
897
1075
|
const pending = requireConfirmation(
|
|
@@ -909,10 +1087,10 @@ var deleteRankRadarTool = {
|
|
|
909
1087
|
};
|
|
910
1088
|
|
|
911
1089
|
// src/tools/add-rank-radar-search-terms.ts
|
|
912
|
-
import { z as
|
|
913
|
-
var
|
|
914
|
-
rankRadarId:
|
|
915
|
-
searchTerms:
|
|
1090
|
+
import { z as z23 } from "zod";
|
|
1091
|
+
var inputSchema24 = {
|
|
1092
|
+
rankRadarId: z23.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
|
|
1093
|
+
searchTerms: z23.array(z23.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
|
|
916
1094
|
`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
1095
|
)
|
|
918
1096
|
};
|
|
@@ -920,7 +1098,7 @@ var addRankRadarSearchTermsTool = {
|
|
|
920
1098
|
name: "add_rank_radar_search_terms",
|
|
921
1099
|
title: "Add Search Terms to a Rank Radar",
|
|
922
1100
|
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:
|
|
1101
|
+
inputSchema: inputSchema24,
|
|
924
1102
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
925
1103
|
handler: async (args, ctx) => {
|
|
926
1104
|
return await httpPost(
|
|
@@ -932,10 +1110,10 @@ var addRankRadarSearchTermsTool = {
|
|
|
932
1110
|
};
|
|
933
1111
|
|
|
934
1112
|
// src/tools/pause-rank-radar-search-terms.ts
|
|
935
|
-
import { z as
|
|
936
|
-
var
|
|
937
|
-
rankRadarId:
|
|
938
|
-
rankRadarKeywordIds:
|
|
1113
|
+
import { z as z24 } from "zod";
|
|
1114
|
+
var inputSchema25 = {
|
|
1115
|
+
rankRadarId: z24.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1116
|
+
rankRadarKeywordIds: z24.array(z24.string().uuid()).min(1).describe(
|
|
939
1117
|
"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
1118
|
)
|
|
941
1119
|
};
|
|
@@ -943,7 +1121,7 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
943
1121
|
name: "pause_rank_radar_search_terms",
|
|
944
1122
|
title: "Pause Search Terms of a Rank Radar",
|
|
945
1123
|
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:
|
|
1124
|
+
inputSchema: inputSchema25,
|
|
947
1125
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
948
1126
|
handler: async (args, ctx) => {
|
|
949
1127
|
await httpPost(
|
|
@@ -960,10 +1138,10 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
960
1138
|
};
|
|
961
1139
|
|
|
962
1140
|
// src/tools/resume-rank-radar-search-terms.ts
|
|
963
|
-
import { z as
|
|
964
|
-
var
|
|
965
|
-
rankRadarId:
|
|
966
|
-
rankRadarKeywordIds:
|
|
1141
|
+
import { z as z25 } from "zod";
|
|
1142
|
+
var inputSchema26 = {
|
|
1143
|
+
rankRadarId: z25.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1144
|
+
rankRadarKeywordIds: z25.array(z25.string().uuid()).min(1).describe(
|
|
967
1145
|
"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
1146
|
)
|
|
969
1147
|
};
|
|
@@ -971,7 +1149,7 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
971
1149
|
name: "resume_rank_radar_search_terms",
|
|
972
1150
|
title: "Resume Paused Search Terms of a Rank Radar",
|
|
973
1151
|
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:
|
|
1152
|
+
inputSchema: inputSchema26,
|
|
975
1153
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
976
1154
|
handler: async (args, ctx) => {
|
|
977
1155
|
await httpPost(
|
|
@@ -988,16 +1166,16 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
988
1166
|
};
|
|
989
1167
|
|
|
990
1168
|
// src/tools/generate-listing-copy.ts
|
|
991
|
-
import { z as
|
|
992
|
-
var
|
|
993
|
-
nicheId:
|
|
994
|
-
strategy:
|
|
1169
|
+
import { z as z26 } from "zod";
|
|
1170
|
+
var inputSchema27 = {
|
|
1171
|
+
nicheId: z26.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
|
|
1172
|
+
strategy: z26.enum(LISTING_COPY_STRATEGIES).describe(
|
|
995
1173
|
"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
1174
|
),
|
|
997
|
-
currentListing:
|
|
1175
|
+
currentListing: z26.record(z26.union([z26.string(), z26.array(z26.string())])).describe(
|
|
998
1176
|
'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
1177
|
),
|
|
1000
|
-
confirm:
|
|
1178
|
+
confirm: z26.boolean().optional().describe(
|
|
1001
1179
|
"Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
|
|
1002
1180
|
)
|
|
1003
1181
|
};
|
|
@@ -1005,7 +1183,7 @@ var generateListingCopyTool = {
|
|
|
1005
1183
|
name: "generate_listing_copy",
|
|
1006
1184
|
title: "Generate Listing Copy for a Niche",
|
|
1007
1185
|
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:
|
|
1186
|
+
inputSchema: inputSchema27,
|
|
1009
1187
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1010
1188
|
handler: async (args, ctx) => {
|
|
1011
1189
|
const pending = requireConfirmation(
|
|
@@ -1023,16 +1201,16 @@ var generateListingCopyTool = {
|
|
|
1023
1201
|
};
|
|
1024
1202
|
|
|
1025
1203
|
// src/tools/get-listing-copy-generation-status.ts
|
|
1026
|
-
import { z as
|
|
1027
|
-
var
|
|
1028
|
-
nicheId:
|
|
1029
|
-
generationId:
|
|
1204
|
+
import { z as z27 } from "zod";
|
|
1205
|
+
var inputSchema28 = {
|
|
1206
|
+
nicheId: z27.string().min(1).describe("The niche the generation was started for."),
|
|
1207
|
+
generationId: z27.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
|
|
1030
1208
|
};
|
|
1031
1209
|
var getListingCopyGenerationStatusTool = {
|
|
1032
1210
|
name: "get_listing_copy_generation_status",
|
|
1033
1211
|
title: "Get Listing Copy Generation Status",
|
|
1034
1212
|
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:
|
|
1213
|
+
inputSchema: inputSchema28,
|
|
1036
1214
|
annotations: { readOnlyHint: true },
|
|
1037
1215
|
handler: async (args, ctx) => {
|
|
1038
1216
|
return await httpGet(
|
|
@@ -1043,16 +1221,16 @@ var getListingCopyGenerationStatusTool = {
|
|
|
1043
1221
|
};
|
|
1044
1222
|
|
|
1045
1223
|
// src/tools/list-seller-profiles.ts
|
|
1046
|
-
import { z as
|
|
1047
|
-
var
|
|
1048
|
-
currentPage:
|
|
1049
|
-
pageSize:
|
|
1224
|
+
import { z as z28 } from "zod";
|
|
1225
|
+
var inputSchema29 = {
|
|
1226
|
+
currentPage: z28.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1227
|
+
pageSize: z28.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1050
1228
|
};
|
|
1051
1229
|
var listSellerProfilesTool = {
|
|
1052
1230
|
name: "list_seller_profiles",
|
|
1053
1231
|
title: "List Connected Amazon Seller Profiles",
|
|
1054
1232
|
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:
|
|
1233
|
+
inputSchema: inputSchema29,
|
|
1056
1234
|
annotations: { readOnlyHint: true },
|
|
1057
1235
|
handler: async (args, ctx) => {
|
|
1058
1236
|
return await httpGet(
|
|
@@ -1067,15 +1245,15 @@ var listSellerProfilesTool = {
|
|
|
1067
1245
|
};
|
|
1068
1246
|
|
|
1069
1247
|
// src/tools/get-seller-catalog.ts
|
|
1070
|
-
import { z as
|
|
1248
|
+
import { z as z30 } from "zod";
|
|
1071
1249
|
|
|
1072
1250
|
// src/tools/seller-scoped.ts
|
|
1073
|
-
import { z as
|
|
1251
|
+
import { z as z29 } from "zod";
|
|
1074
1252
|
var sellerScopedInputSchema = {
|
|
1075
|
-
sellerId:
|
|
1253
|
+
sellerId: z29.string().min(1).describe(
|
|
1076
1254
|
"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
1077
1255
|
),
|
|
1078
|
-
marketplace:
|
|
1256
|
+
marketplace: z29.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
|
|
1079
1257
|
};
|
|
1080
1258
|
function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
1081
1259
|
return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
|
|
@@ -1084,19 +1262,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
|
1084
1262
|
}
|
|
1085
1263
|
|
|
1086
1264
|
// src/tools/get-seller-catalog.ts
|
|
1087
|
-
var
|
|
1265
|
+
var inputSchema30 = {
|
|
1088
1266
|
...sellerScopedInputSchema,
|
|
1089
|
-
search:
|
|
1090
|
-
brand:
|
|
1091
|
-
status:
|
|
1092
|
-
currentPage:
|
|
1093
|
-
pageSize:
|
|
1267
|
+
search: z30.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1268
|
+
brand: z30.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1269
|
+
status: z30.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
|
|
1270
|
+
currentPage: z30.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1271
|
+
pageSize: z30.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1094
1272
|
};
|
|
1095
1273
|
var getSellerCatalogTool = {
|
|
1096
1274
|
name: "get_seller_catalog",
|
|
1097
1275
|
title: "List Catalog ASINs for a Seller",
|
|
1098
1276
|
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:
|
|
1277
|
+
inputSchema: inputSchema30,
|
|
1100
1278
|
annotations: { readOnlyHint: true },
|
|
1101
1279
|
handler: async (args, ctx) => {
|
|
1102
1280
|
return await httpGet(
|
|
@@ -1114,28 +1292,28 @@ var getSellerCatalogTool = {
|
|
|
1114
1292
|
};
|
|
1115
1293
|
|
|
1116
1294
|
// src/tools/get-seller-listing-changes.ts
|
|
1117
|
-
import { z as
|
|
1118
|
-
var
|
|
1295
|
+
import { z as z31 } from "zod";
|
|
1296
|
+
var inputSchema31 = {
|
|
1119
1297
|
...sellerScopedInputSchema,
|
|
1120
|
-
types:
|
|
1121
|
-
asin:
|
|
1122
|
-
brand:
|
|
1123
|
-
search:
|
|
1124
|
-
startDate:
|
|
1125
|
-
endDate:
|
|
1126
|
-
sortBy:
|
|
1127
|
-
sortOrder:
|
|
1128
|
-
includeCorrelations:
|
|
1298
|
+
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".'),
|
|
1299
|
+
asin: z31.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
|
|
1300
|
+
brand: z31.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1301
|
+
search: z31.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1302
|
+
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."),
|
|
1303
|
+
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."),
|
|
1304
|
+
sortBy: z31.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
|
|
1305
|
+
sortOrder: z31.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
|
|
1306
|
+
includeCorrelations: z31.boolean().optional().describe(
|
|
1129
1307
|
"When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
|
|
1130
1308
|
),
|
|
1131
|
-
currentPage:
|
|
1132
|
-
pageSize:
|
|
1309
|
+
currentPage: z31.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1310
|
+
pageSize: z31.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1133
1311
|
};
|
|
1134
1312
|
var getSellerListingChangesTool = {
|
|
1135
1313
|
name: "get_seller_listing_changes",
|
|
1136
1314
|
title: "List Listing Changes for a Seller",
|
|
1137
1315
|
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:
|
|
1316
|
+
inputSchema: inputSchema31,
|
|
1139
1317
|
annotations: { readOnlyHint: true },
|
|
1140
1318
|
handler: async (args, ctx) => {
|
|
1141
1319
|
return await httpGet(
|
|
@@ -1160,40 +1338,40 @@ var getSellerListingChangesTool = {
|
|
|
1160
1338
|
};
|
|
1161
1339
|
|
|
1162
1340
|
// src/tools/list-ppc-campaigns.ts
|
|
1163
|
-
import { z as
|
|
1341
|
+
import { z as z32 } from "zod";
|
|
1164
1342
|
var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
|
|
1165
1343
|
var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
|
|
1166
|
-
var
|
|
1344
|
+
var inputSchema32 = {
|
|
1167
1345
|
...sellerScopedInputSchema,
|
|
1168
|
-
startDate:
|
|
1346
|
+
startDate: z32.string().regex(
|
|
1169
1347
|
ISO_8601,
|
|
1170
1348
|
"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
|
|
1171
1349
|
).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
|
|
1172
|
-
endDate:
|
|
1350
|
+
endDate: z32.string().regex(
|
|
1173
1351
|
ISO_8601,
|
|
1174
1352
|
"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
|
|
1175
1353
|
).optional().describe(
|
|
1176
1354
|
`End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
|
|
1177
1355
|
),
|
|
1178
|
-
asin:
|
|
1179
|
-
parentAsin:
|
|
1180
|
-
search:
|
|
1181
|
-
includePlacements:
|
|
1356
|
+
asin: z32.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
|
|
1357
|
+
parentAsin: z32.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
|
|
1358
|
+
search: z32.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
|
|
1359
|
+
includePlacements: z32.boolean().optional().describe(
|
|
1182
1360
|
"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
1361
|
),
|
|
1184
|
-
sortBy:
|
|
1362
|
+
sortBy: z32.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
|
|
1185
1363
|
`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
1364
|
),
|
|
1187
|
-
sortOrder:
|
|
1188
|
-
state:
|
|
1189
|
-
currentPage:
|
|
1190
|
-
pageSize:
|
|
1365
|
+
sortOrder: z32.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
|
|
1366
|
+
state: z32.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
|
|
1367
|
+
currentPage: z32.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1368
|
+
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.`)
|
|
1191
1369
|
};
|
|
1192
1370
|
var listPpcCampaignsTool = {
|
|
1193
1371
|
name: "list_ppc_campaigns",
|
|
1194
1372
|
title: "List PPC Campaigns for a Seller",
|
|
1195
1373
|
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:
|
|
1374
|
+
inputSchema: inputSchema32,
|
|
1197
1375
|
annotations: { readOnlyHint: true },
|
|
1198
1376
|
handler: async (args, ctx) => {
|
|
1199
1377
|
if (args.asin && args.parentAsin) {
|
|
@@ -1224,13 +1402,13 @@ var listPpcCampaignsTool = {
|
|
|
1224
1402
|
};
|
|
1225
1403
|
|
|
1226
1404
|
// src/tools/get-asin-inventory-distribution.ts
|
|
1227
|
-
import { z as
|
|
1228
|
-
var
|
|
1229
|
-
sellerId:
|
|
1405
|
+
import { z as z33 } from "zod";
|
|
1406
|
+
var inputSchema33 = {
|
|
1407
|
+
sellerId: z33.string().min(1).describe(
|
|
1230
1408
|
"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
1409
|
),
|
|
1232
|
-
marketplace:
|
|
1233
|
-
asin:
|
|
1410
|
+
marketplace: z33.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
1411
|
+
asin: z33.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
|
|
1234
1412
|
"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
|
|
1235
1413
|
)
|
|
1236
1414
|
};
|
|
@@ -1238,7 +1416,7 @@ var getAsinInventoryDistributionTool = {
|
|
|
1238
1416
|
name: "get_asin_inventory_distribution",
|
|
1239
1417
|
title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
|
|
1240
1418
|
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.",
|
|
1241
|
-
inputSchema:
|
|
1419
|
+
inputSchema: inputSchema33,
|
|
1242
1420
|
annotations: { readOnlyHint: true },
|
|
1243
1421
|
handler: async (args, ctx) => {
|
|
1244
1422
|
return await httpGet(
|
|
@@ -1251,18 +1429,18 @@ var getAsinInventoryDistributionTool = {
|
|
|
1251
1429
|
};
|
|
1252
1430
|
|
|
1253
1431
|
// src/tools/alert-query.ts
|
|
1254
|
-
import { z as
|
|
1432
|
+
import { z as z34 } from "zod";
|
|
1255
1433
|
var alertsQueryInputSchema = {
|
|
1256
|
-
sellerId:
|
|
1257
|
-
marketplace:
|
|
1258
|
-
status:
|
|
1434
|
+
sellerId: z34.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
|
|
1435
|
+
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.'),
|
|
1436
|
+
status: z34.enum(ALERT_STATUSES).optional().describe(
|
|
1259
1437
|
"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
|
|
1260
1438
|
),
|
|
1261
|
-
updatedSince:
|
|
1439
|
+
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(
|
|
1262
1440
|
"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
1441
|
),
|
|
1264
|
-
currentPage:
|
|
1265
|
-
pageSize:
|
|
1442
|
+
currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1443
|
+
pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1266
1444
|
};
|
|
1267
1445
|
|
|
1268
1446
|
// src/tools/list-indexing-issue-alerts.ts
|
|
@@ -1312,12 +1490,12 @@ var listBlindSpendAlertsTool = {
|
|
|
1312
1490
|
};
|
|
1313
1491
|
|
|
1314
1492
|
// src/tools/get-quota.ts
|
|
1315
|
-
var
|
|
1493
|
+
var inputSchema34 = {};
|
|
1316
1494
|
var getQuotaTool = {
|
|
1317
1495
|
name: "get_quota",
|
|
1318
1496
|
title: "Get Quota Usage",
|
|
1319
1497
|
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:
|
|
1498
|
+
inputSchema: inputSchema34,
|
|
1321
1499
|
annotations: { readOnlyHint: true },
|
|
1322
1500
|
handler: async (_args, ctx) => {
|
|
1323
1501
|
return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
|
|
@@ -1325,20 +1503,20 @@ var getQuotaTool = {
|
|
|
1325
1503
|
};
|
|
1326
1504
|
|
|
1327
1505
|
// src/tools/list-usage.ts
|
|
1328
|
-
import { z as
|
|
1329
|
-
var
|
|
1330
|
-
type:
|
|
1331
|
-
search:
|
|
1332
|
-
startDate:
|
|
1333
|
-
endDate:
|
|
1334
|
-
currentPage:
|
|
1335
|
-
pageSize:
|
|
1506
|
+
import { z as z35 } from "zod";
|
|
1507
|
+
var inputSchema35 = {
|
|
1508
|
+
type: z35.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
|
|
1509
|
+
search: z35.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
|
|
1510
|
+
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."),
|
|
1511
|
+
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."),
|
|
1512
|
+
currentPage: z35.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1513
|
+
pageSize: z35.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
|
|
1336
1514
|
};
|
|
1337
1515
|
var listUsageTool = {
|
|
1338
1516
|
name: "list_usage",
|
|
1339
1517
|
title: "List Billable Usage Logs",
|
|
1340
1518
|
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:
|
|
1519
|
+
inputSchema: inputSchema35,
|
|
1342
1520
|
annotations: { readOnlyHint: true },
|
|
1343
1521
|
handler: async (args, ctx) => {
|
|
1344
1522
|
return await httpGet(
|
|
@@ -1360,6 +1538,7 @@ var listUsageTool = {
|
|
|
1360
1538
|
var allTools = [
|
|
1361
1539
|
listNichesTool,
|
|
1362
1540
|
getNicheKeywordsTool,
|
|
1541
|
+
listKeywordExclusionsTool,
|
|
1363
1542
|
getNicheRootsTool,
|
|
1364
1543
|
getNicheCompetitorsTool,
|
|
1365
1544
|
getRankingJuiceTool,
|
|
@@ -1372,6 +1551,10 @@ var allTools = [
|
|
|
1372
1551
|
createNicheDiveFromCompetitorsListTool,
|
|
1373
1552
|
rediveNicheTool,
|
|
1374
1553
|
getDiveStatusTool,
|
|
1554
|
+
addNicheKeywordsTool,
|
|
1555
|
+
removeNicheKeywordsTool,
|
|
1556
|
+
addKeywordExclusionsTool,
|
|
1557
|
+
removeKeywordExclusionsTool,
|
|
1375
1558
|
addRankRadarSearchTermsTool,
|
|
1376
1559
|
pauseRankRadarSearchTermsTool,
|
|
1377
1560
|
resumeRankRadarSearchTermsTool,
|
|
@@ -1394,6 +1577,14 @@ var allTools = [
|
|
|
1394
1577
|
|
|
1395
1578
|
// src/whats-new.ts
|
|
1396
1579
|
var WHATS_NEW = [
|
|
1580
|
+
{
|
|
1581
|
+
version: "0.17.0",
|
|
1582
|
+
released: "2026-10-12",
|
|
1583
|
+
items: [
|
|
1584
|
+
"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.",
|
|
1585
|
+
"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."
|
|
1586
|
+
]
|
|
1587
|
+
},
|
|
1397
1588
|
{
|
|
1398
1589
|
version: "0.16.1",
|
|
1399
1590
|
released: "2026-09-29",
|
|
@@ -1438,8 +1629,8 @@ var WHATS_NEW = [
|
|
|
1438
1629
|
}
|
|
1439
1630
|
];
|
|
1440
1631
|
var INSTRUCTIONS_RELEASES = 2;
|
|
1441
|
-
function formatEntries(
|
|
1442
|
-
return
|
|
1632
|
+
function formatEntries(entries2) {
|
|
1633
|
+
return entries2.map((e) => `${e.released} (v${e.version}):
|
|
1443
1634
|
${e.items.map((i) => `- ${i}`).join("\n")}`).join("\n\n");
|
|
1444
1635
|
}
|
|
1445
1636
|
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.
|