@datadive-tools/mcp 0.8.0 → 0.10.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 CHANGED
@@ -16,6 +16,8 @@ except as the `x-api-key` header on requests to `api.datadive.tools`.
16
16
  - "Show me my rank radars."
17
17
  - "Plot the organic ranking trend for rank radar Y from 2024-03-01 to 2024-04-01."
18
18
  - "Run a niche dive on ASIN B08N5WRWNW in the US marketplace with 5 competitors."
19
+ - "Refresh niche X with today's data, same competitors."
20
+ - "Re-dive niche X with 12 competitors but keep B08N5WRWNW in the set."
19
21
  - "Is my dive done yet?"
20
22
  - "Start a rank radar tracking 10 keywords for ASIN B08N5WRWNW in niche X."
21
23
  - "Which Amazon seller accounts are connected?"
@@ -103,7 +105,8 @@ niches, plus pagination metadata. If you don't, see Troubleshooting below.
103
105
  | `list_rank_radars` | Paginated list of rank radars. Filter by `nicheId` or `status`. |
104
106
  | `get_rank_radar_data` | Historical keyword rankings for a rank radar within a `startDate`/`endDate` range. |
105
107
  | `create_niche_dive` | **Spends dive tokens.** Starts new niche research from a seed ASIN. Async — returns a `diveId` to poll with `get_dive_status`. Requires `confirm: true`. |
106
- | `get_dive_status` | Poll a dive started by `create_niche_dive`: `in_progress`, `success` (carries the new `nicheId`), or `error`. |
108
+ | `redive_niche` | **Spends dive tokens.** Refreshes an existing niche with current data — either the same competitors or a newly discovered set. Async — returns a `diveId` to poll with `get_dive_status`. Requires `confirm: true`. |
109
+ | `get_dive_status` | Poll a dive started by `create_niche_dive` or `redive_niche`: `in_progress`, `success` (carries the `nicheId`), or `error`. |
107
110
  | `create_rank_radar` | **Spends Search Term tokens.** Starts tracking keyword rankings for an ASIN in a niche. Returns a `rankRadarId`. Requires `confirm: true`. |
108
111
  | `list_seller_profiles` | Paginated list of connected Amazon seller accounts. Discovery step — returns the `sellerId` + `marketplace` the seller-scoped tools below (and the alert tools) need. |
109
112
  | `get_seller_catalog` | Paginated catalog of a seller's own ASINs. Filter by `search`, `brand`, and `status` (Active by default). |
@@ -115,27 +118,44 @@ niches, plus pagination metadata. If you don't, see Troubleshooting below.
115
118
  | `list_usage` | Paginated billable usage logs (token-consumption events). Filter by `type`, `search` (user), and `startDate`/`endDate`. |
116
119
 
117
120
  All data is scoped to the organization that owns the API key. Most tools are
118
- read-only; the two `create_*` tools below spend tokens (`get_dive_status` only
119
- polls a dive and is read-only) — see
121
+ read-only; the three tools below spend tokens (`get_dive_status` only polls a
122
+ dive and is read-only) — see
120
123
  [Creating dives & rank radars](#creating-dives--rank-radars).
121
124
 
122
125
  ### Creating dives & rank radars
123
126
 
124
- `create_niche_dive` and `create_rank_radar` **consume billable tokens and cannot
125
- be undone**, so they require an explicit `confirm: true` argument. The assistant
126
- should confirm the cost with you before passing it. The amount scales with
127
- `numberOfCompetitors` (dives) / `numberOfKeywords` (rank radars). Check remaining
128
- balance any time with `get_quota`.
127
+ `create_niche_dive`, `redive_niche` and `create_rank_radar` **consume billable
128
+ tokens and cannot be undone**, so they require an explicit `confirm: true`
129
+ argument. The assistant should confirm the cost with you before passing it. The
130
+ amount scales with `numberOfCompetitors` (dives and re-dives) /
131
+ `numberOfKeywords` (rank radars). Check remaining balance any time with
132
+ `get_quota`.
129
133
 
130
134
  To skip the per-call confirmation (e.g. in an automated setup), set
131
135
  `DATADIVE_AUTO_CONFIRM_WRITES=true` in your client config — then these tools run
132
136
  without `confirm`.
133
137
 
138
+ None of the three is **safe to retry** — each call spends tokens again and
139
+ creates a separate dive / re-dive / Rank Radar, even with identical arguments. If
140
+ a call errors or times out, check `get_dive_status` / `list_niches` /
141
+ `list_rank_radars` for what already exists before calling again.
142
+
134
143
  Dives are **asynchronous**. `create_niche_dive` returns a `diveId` and an
135
144
  estimated completion time immediately; poll `get_dive_status` with that `diveId`
136
145
  until it reports `success`, which carries the new `nicheId` you then feed to
137
146
  `list_niches`, `get_niche_keywords`, and the other niche tools.
138
147
 
148
+ `redive_niche` refreshes a niche you already have rather than creating another
149
+ one, and it keeps the same `nicheId` — so rank radars and reports built on that
150
+ niche follow the refreshed data. It takes a `mode`:
151
+
152
+ - `same_competitors` — re-dive the niche's current competitor set. Nothing else
153
+ to supply; use it purely to pull in current sales, price and keyword data.
154
+ - `discover` — search for a fresh competitor set of `numberOfCompetitors` ASINs.
155
+ Steer it with `heroAsin` (the seed to build around; defaults to the niche's
156
+ highest-selling competitor, preferring one of your own ASINs), `lockedAsins`
157
+ (kept no matter what discovery finds) and `excludedAsins` (never selected).
158
+
139
159
  > ⚠️ For `create_niche_dive`, `marketplace` uses full Amazon domain suffixes
140
160
  > (`com`, `co.uk`, `com.mx`, `co.jp`, …) — e.g. the UK marketplace is `co.uk`,
141
161
  > not `uk`.
package/dist/index.js CHANGED
@@ -105,7 +105,7 @@ function extractMessage(body) {
105
105
  }
106
106
 
107
107
  // package.json
108
- var version = "0.8.0";
108
+ var version = "0.10.0";
109
109
 
110
110
  // src/http/client.ts
111
111
  var PKG_VERSION = version;
@@ -222,21 +222,37 @@ function isObject(v) {
222
222
  }
223
223
 
224
224
  // src/tools/list-niches.ts
225
+ var LIST_NICHES_MAX_PAGE_SIZE = 50;
225
226
  var inputSchema = {
226
- currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
227
- pageSize: z.number().int().min(1).max(100).optional().describe("Items per page (max 100). Defaults to 20.")
227
+ currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true."),
228
+ pageSize: z.number().int().min(1).max(LIST_NICHES_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`),
229
+ searchText: z.string().trim().min(1).max(200).optional().describe(
230
+ "Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging through everything when the user names a niche."
231
+ ),
232
+ searchAsin: z.string().trim().regex(/^[A-Za-z0-9]{10}$/, "must be a 10-character ASIN").optional().describe("Only niches whose competitor set contains this ASIN (10 characters, e.g. B08N5WRWNW)."),
233
+ orderBy: z.enum(["lastDived", "name"]).optional().describe(
234
+ "Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` (by nicheLabel, A\u2192Z by default). Defaults to `lastDived`."
235
+ ),
236
+ sortOrder: z.enum(["ASC", "DESC"]).optional().describe("Sort direction. Defaults to DESC for `lastDived` and ASC for `name`.")
228
237
  };
229
238
  var listNichesTool = {
230
239
  name: "list_niches",
231
240
  title: "List DataDive Niches",
232
- description: "Use this first when the user asks about their niches, or to find a `nicheId` for use with `get_niche_keywords`, `get_niche_competitors`, or `get_ranking_juice`. Retrieves a paginated list of Niches. Each Niche represents a market segment or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace (com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
241
+ description: `Use this first when the user asks about their niches, or to find a \`nicheId\` for use with \`get_niche_keywords\`, \`get_niche_competitors\`, or \`get_ranking_juice\`. Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace (com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). Narrow with \`searchText\` (label/keyword) or \`searchAsin\` (a competitor ASIN) instead of scanning pages; a page holds at most ${LIST_NICHES_MAX_PAGE_SIZE} niches, so the full list of a large account needs several calls.`,
233
242
  inputSchema,
234
243
  annotations: { readOnlyHint: true },
235
244
  handler: async (args, ctx) => {
236
245
  return await httpGet(
237
246
  { config: ctx.config, toolName: "list_niches" },
238
247
  "/v1/niches",
239
- { currentPage: args.currentPage, pageSize: args.pageSize }
248
+ {
249
+ currentPage: args.currentPage,
250
+ pageSize: args.pageSize,
251
+ searchText: args.searchText,
252
+ searchAsin: args.searchAsin,
253
+ orderBy: args.orderBy,
254
+ sortOrder: args.sortOrder
255
+ }
240
256
  );
241
257
  }
242
258
  };
@@ -426,7 +442,7 @@ var inputSchema8 = {
426
442
  var createRankRadarTool = {
427
443
  name: "create_rank_radar",
428
444
  title: "Create a Rank Radar",
429
- 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. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
445
+ 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. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
430
446
  inputSchema: inputSchema8,
431
447
  // destructiveHint is technically "additive" here (this creates a new Rank Radar, it
432
448
  // doesn't destroy data), but we flag it destructive so clients prompt before the
@@ -464,7 +480,7 @@ var inputSchema9 = {
464
480
  var createNicheDiveTool = {
465
481
  name: "create_niche_dive",
466
482
  title: "Create a Niche Dive",
467
- 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. 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).",
483
+ 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).",
468
484
  inputSchema: inputSchema9,
469
485
  // destructiveHint is technically "additive" here (a dive creates a new niche, it
470
486
  // doesn't destroy data), but we flag it destructive so clients prompt before the
@@ -489,16 +505,73 @@ var createNicheDiveTool = {
489
505
  }
490
506
  };
491
507
 
492
- // src/tools/get-dive-status.ts
508
+ // src/tools/redive-niche.ts
493
509
  import { z as z10 } from "zod";
494
510
  var inputSchema10 = {
495
- diveId: z10.string().min(1).describe("The dive identifier returned by `create_niche_dive`.")
511
+ nicheId: z10.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
512
+ mode: z10.enum(["same_competitors", "discover"]).describe(
513
+ "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."
514
+ ),
515
+ numberOfCompetitors: z10.number().int().min(2).optional().describe(
516
+ "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."
517
+ ),
518
+ heroAsin: z10.string().min(1).optional().describe(
519
+ "`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."
520
+ ),
521
+ lockedAsins: z10.array(z10.string().min(1)).optional().describe(
522
+ "`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
523
+ ),
524
+ excludedAsins: z10.array(z10.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
525
+ confirm: z10.boolean().optional().describe(
526
+ "Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
527
+ )
528
+ };
529
+ var rediveNicheTool = {
530
+ name: "redive_niche",
531
+ title: "Re-dive an Existing Niche",
532
+ 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.",
533
+ inputSchema: inputSchema10,
534
+ // Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
535
+ // irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
536
+ annotations: { readOnlyHint: false, destructiveHint: true },
537
+ handler: async (args, ctx) => {
538
+ if (args.mode === "discover" && args.numberOfCompetitors === void 0) {
539
+ throw new ApiError(
540
+ "bad_request",
541
+ 400,
542
+ 'numberOfCompetitors is required in `discover` mode \u2014 it is how many ASINs the refreshed dive will contain, and therefore what it costs. Ask the user how many competitors they want (minimum 2), or use mode "same_competitors" to re-dive the niche\'s current set.'
543
+ );
544
+ }
545
+ const pending = requireConfirmation(
546
+ args.confirm,
547
+ ctx,
548
+ args.mode === "discover" ? `Re-diving this niche consumes dive tokens \u2014 one batch per ASIN analyzed (${args.numberOfCompetitors} requested).` : "Re-diving this niche consumes dive tokens \u2014 one batch per competitor currently in the niche. Use `get_niche_competitors` first if the user needs the exact count."
549
+ );
550
+ if (pending) return pending;
551
+ return await httpPost(
552
+ { config: ctx.config, toolName: "redive_niche" },
553
+ `/v1/niches/${encodeURIComponent(args.nicheId)}/redive`,
554
+ {
555
+ mode: args.mode,
556
+ numberOfCompetitors: args.numberOfCompetitors,
557
+ heroAsin: args.heroAsin,
558
+ lockedAsins: args.lockedAsins,
559
+ excludedAsins: args.excludedAsins
560
+ }
561
+ );
562
+ }
563
+ };
564
+
565
+ // src/tools/get-dive-status.ts
566
+ import { z as z11 } from "zod";
567
+ var inputSchema11 = {
568
+ diveId: z11.string().min(1).describe("The dive identifier returned by `create_niche_dive` or `redive_niche`.")
496
569
  };
497
570
  var getDiveStatusTool = {
498
571
  name: "get_dive_status",
499
572
  title: "Get Niche Dive Status",
500
- description: "Use this to poll a niche dive started with `create_niche_dive` until it finishes. Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), `success` (with the new `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. Re-poll periodically \u2014 dives can take minutes; the `estimatedCompletionDate` hints at when to check.",
501
- inputSchema: inputSchema10,
573
+ description: "Use this to poll a niche dive started with `create_niche_dive` 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.",
574
+ inputSchema: inputSchema11,
502
575
  annotations: { readOnlyHint: true },
503
576
  handler: async (args, ctx) => {
504
577
  return await httpGet(
@@ -509,16 +582,16 @@ var getDiveStatusTool = {
509
582
  };
510
583
 
511
584
  // src/tools/list-seller-profiles.ts
512
- import { z as z11 } from "zod";
513
- var inputSchema11 = {
514
- currentPage: z11.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
515
- pageSize: z11.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
585
+ import { z as z12 } from "zod";
586
+ var inputSchema12 = {
587
+ currentPage: z12.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
588
+ pageSize: z12.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
516
589
  };
517
590
  var listSellerProfilesTool = {
518
591
  name: "list_seller_profiles",
519
592
  title: "List Connected Amazon Seller Profiles",
520
593
  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).',
521
- inputSchema: inputSchema11,
594
+ inputSchema: inputSchema12,
522
595
  annotations: { readOnlyHint: true },
523
596
  handler: async (args, ctx) => {
524
597
  return await httpGet(
@@ -533,15 +606,15 @@ var listSellerProfilesTool = {
533
606
  };
534
607
 
535
608
  // src/tools/get-seller-catalog.ts
536
- import { z as z13 } from "zod";
609
+ import { z as z14 } from "zod";
537
610
 
538
611
  // src/tools/seller-scoped.ts
539
- import { z as z12 } from "zod";
612
+ import { z as z13 } from "zod";
540
613
  var sellerScopedInputSchema = {
541
- sellerId: z12.string().min(1).describe(
614
+ sellerId: z13.string().min(1).describe(
542
615
  "Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
543
616
  ),
544
- marketplace: z12.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
617
+ marketplace: z13.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
545
618
  };
546
619
  function sellerMarketplacePath(sellerId, marketplace, suffix) {
547
620
  return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
@@ -550,19 +623,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
550
623
  }
551
624
 
552
625
  // src/tools/get-seller-catalog.ts
553
- var inputSchema12 = {
626
+ var inputSchema13 = {
554
627
  ...sellerScopedInputSchema,
555
- search: z13.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
556
- brand: z13.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
557
- status: z13.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
558
- currentPage: z13.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
559
- pageSize: z13.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
628
+ search: z14.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
629
+ brand: z14.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
630
+ status: z14.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
631
+ currentPage: z14.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
632
+ pageSize: z14.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
560
633
  };
561
634
  var getSellerCatalogTool = {
562
635
  name: "get_seller_catalog",
563
636
  title: "List Catalog ASINs for a Seller",
564
637
  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).",
565
- inputSchema: inputSchema12,
638
+ inputSchema: inputSchema13,
566
639
  annotations: { readOnlyHint: true },
567
640
  handler: async (args, ctx) => {
568
641
  return await httpGet(
@@ -580,28 +653,28 @@ var getSellerCatalogTool = {
580
653
  };
581
654
 
582
655
  // src/tools/get-seller-listing-changes.ts
583
- import { z as z14 } from "zod";
584
- var inputSchema13 = {
656
+ import { z as z15 } from "zod";
657
+ var inputSchema14 = {
585
658
  ...sellerScopedInputSchema,
586
- types: z14.array(z14.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
587
- asin: z14.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
588
- brand: z14.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
589
- search: z14.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
590
- startDate: z14.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."),
591
- endDate: z14.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."),
592
- sortBy: z14.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
593
- sortOrder: z14.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
594
- includeCorrelations: z14.boolean().optional().describe(
659
+ types: z15.array(z15.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
660
+ asin: z15.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
661
+ brand: z15.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
662
+ search: z15.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
663
+ startDate: z15.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."),
664
+ endDate: z15.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."),
665
+ sortBy: z15.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
666
+ sortOrder: z15.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
667
+ includeCorrelations: z15.boolean().optional().describe(
595
668
  "When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
596
669
  ),
597
- currentPage: z14.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
598
- pageSize: z14.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
670
+ currentPage: z15.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
671
+ pageSize: z15.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
599
672
  };
600
673
  var getSellerListingChangesTool = {
601
674
  name: "get_seller_listing_changes",
602
675
  title: "List Listing Changes for a Seller",
603
676
  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).",
604
- inputSchema: inputSchema13,
677
+ inputSchema: inputSchema14,
605
678
  annotations: { readOnlyHint: true },
606
679
  handler: async (args, ctx) => {
607
680
  return await httpGet(
@@ -626,13 +699,13 @@ var getSellerListingChangesTool = {
626
699
  };
627
700
 
628
701
  // src/tools/get-asin-inventory-distribution.ts
629
- import { z as z15 } from "zod";
630
- var inputSchema14 = {
631
- sellerId: z15.string().min(1).describe(
702
+ import { z as z16 } from "zod";
703
+ var inputSchema15 = {
704
+ sellerId: z16.string().min(1).describe(
632
705
  "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."
633
706
  ),
634
- marketplace: z15.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
635
- asin: z15.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
707
+ marketplace: z16.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
708
+ asin: z16.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
636
709
  "Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
637
710
  )
638
711
  };
@@ -640,7 +713,7 @@ var getAsinInventoryDistributionTool = {
640
713
  name: "get_asin_inventory_distribution",
641
714
  title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
642
715
  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.",
643
- inputSchema: inputSchema14,
716
+ inputSchema: inputSchema15,
644
717
  annotations: { readOnlyHint: true },
645
718
  handler: async (args, ctx) => {
646
719
  return await httpGet(
@@ -653,18 +726,18 @@ var getAsinInventoryDistributionTool = {
653
726
  };
654
727
 
655
728
  // src/tools/alert-query.ts
656
- import { z as z16 } from "zod";
729
+ import { z as z17 } from "zod";
657
730
  var alertsQueryInputSchema = {
658
- sellerId: z16.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
659
- marketplace: z16.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.'),
660
- status: z16.enum(ALERT_STATUSES).optional().describe(
731
+ sellerId: z17.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
732
+ marketplace: z17.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.'),
733
+ status: z17.enum(ALERT_STATUSES).optional().describe(
661
734
  "Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
662
735
  ),
663
- updatedSince: z16.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(
736
+ updatedSince: z17.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(
664
737
  "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."
665
738
  ),
666
- currentPage: z16.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
667
- pageSize: z16.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
739
+ currentPage: z17.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
740
+ pageSize: z17.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
668
741
  };
669
742
 
670
743
  // src/tools/list-indexing-issue-alerts.ts
@@ -714,12 +787,12 @@ var listBlindSpendAlertsTool = {
714
787
  };
715
788
 
716
789
  // src/tools/get-quota.ts
717
- var inputSchema15 = {};
790
+ var inputSchema16 = {};
718
791
  var getQuotaTool = {
719
792
  name: "get_quota",
720
793
  title: "Get Quota Usage",
721
794
  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.",
722
- inputSchema: inputSchema15,
795
+ inputSchema: inputSchema16,
723
796
  annotations: { readOnlyHint: true },
724
797
  handler: async (_args, ctx) => {
725
798
  return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
@@ -727,20 +800,20 @@ var getQuotaTool = {
727
800
  };
728
801
 
729
802
  // src/tools/list-usage.ts
730
- import { z as z17 } from "zod";
731
- var inputSchema16 = {
732
- type: z17.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
733
- search: z17.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
734
- startDate: z17.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."),
735
- endDate: z17.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."),
736
- currentPage: z17.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
737
- pageSize: z17.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
803
+ import { z as z18 } from "zod";
804
+ var inputSchema17 = {
805
+ type: z18.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
806
+ search: z18.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
807
+ startDate: z18.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."),
808
+ endDate: z18.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."),
809
+ currentPage: z18.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
810
+ pageSize: z18.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
738
811
  };
739
812
  var listUsageTool = {
740
813
  name: "list_usage",
741
814
  title: "List Billable Usage Logs",
742
815
  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).",
743
- inputSchema: inputSchema16,
816
+ inputSchema: inputSchema17,
744
817
  annotations: { readOnlyHint: true },
745
818
  handler: async (args, ctx) => {
746
819
  return await httpGet(
@@ -769,6 +842,7 @@ var allTools = [
769
842
  getRankRadarDataTool,
770
843
  createRankRadarTool,
771
844
  createNicheDiveTool,
845
+ rediveNicheTool,
772
846
  getDiveStatusTool,
773
847
  listSellerProfilesTool,
774
848
  getSellerCatalogTool,