@datadive-tools/mcp 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/index.js +734 -389
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +7 -0
- package/dist/lib.js +734 -389
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/server.ts","../src/tools/list-niches.ts","../src/http/errors.ts","../package.json","../src/http/client.ts","../src/tools/get-niche-keywords.ts","../src/tools/list-keyword-exclusions.ts","../src/tools/get-niche-roots.ts","../src/tools/get-niche-competitors.ts","../src/tools/get-ranking-juice.ts","../src/tools/list-rank-radars.ts","../src/types/api.ts","../src/tools/rank-radar-keyword-query.ts","../src/tools/get-rank-radar-data.ts","../src/tools/get-rank-radar-sqp-data.ts","../src/tools/get-rank-radar-ppc-data.ts","../src/tools/create-rank-radar.ts","../src/tools/confirm.ts","../src/tools/create-niche-dive.ts","../src/tools/create-niche-dive-from-competitors-list.ts","../src/tools/redive-niche.ts","../src/tools/get-dive-status.ts","../src/tools/add-niche-keywords.ts","../src/tools/niche-keyword-list.ts","../src/tools/remove-niche-keywords.ts","../src/tools/keyword-exclusions-input.ts","../src/tools/add-keyword-exclusions.ts","../src/tools/remove-keyword-exclusions.ts","../src/tools/delete-niche.ts","../src/tools/pause-rank-radar.ts","../src/tools/resume-rank-radar.ts","../src/tools/delete-rank-radar.ts","../src/tools/add-rank-radar-search-terms.ts","../src/tools/pause-rank-radar-search-terms.ts","../src/tools/resume-rank-radar-search-terms.ts","../src/tools/list-rank-radar-tags.ts","../src/tools/create-rank-radar-tag.ts","../src/tools/update-rank-radar-tag.ts","../src/tools/delete-rank-radar-tag.ts","../src/tools/add-rank-radar-tag-search-terms.ts","../src/tools/remove-rank-radar-tag-search-terms.ts","../src/tools/generate-listing-copy.ts","../src/tools/get-listing-copy-generation-status.ts","../src/tools/list-seller-profiles.ts","../src/tools/get-seller-catalog.ts","../src/tools/seller-scoped.ts","../src/tools/get-seller-listing-changes.ts","../src/tools/list-ppc-campaigns.ts","../src/tools/get-asin-inventory-distribution.ts","../src/tools/alert-query.ts","../src/tools/list-indexing-issue-alerts.ts","../src/tools/list-blind-spend-alerts.ts","../src/tools/get-quota.ts","../src/tools/list-usage.ts","../src/tools/index.ts","../src/whats-new.ts"],"sourcesContent":["/**\n * Entry point for the datadive-mcp stdio binary.\n *\n * Wired via package.json#bin -> dist/index.js so `npx -y @datadive-tools/mcp` runs it.\n * The shebang is added at build time by tsup.config.ts (banner.js).\n */\n\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { loadConfig } from \"./config.js\";\nimport { buildServer } from \"./server.js\";\n\nasync function main() {\n let config;\n try {\n config = loadConfig();\n } catch (err) {\n // Config errors (e.g. missing API key) are user-facing; print to stderr and exit\n // non-zero so the MCP client surfaces a real error rather than silently disconnecting.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[datadive-mcp] ${msg}\\n`);\n process.exit(1);\n }\n\n const server = buildServer(config);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`[datadive-mcp] fatal: ${msg}\\n`);\n process.exit(1);\n});\n","/**\n * Loads and validates the server's runtime configuration from environment.\n *\n * Required:\n * DATADIVE_API_KEY - your DataDive API key (generate at 2.datadive.tools/api-key)\n *\n * Optional:\n * DATADIVE_API_BASE_URL - override the API base URL (defaults to https://api.datadive.tools)\n * DATADIVE_AUTO_CONFIRM_WRITES - skip the confirm gate on token-spending write tools\n * (create_niche_dive, create_rank_radar). Truthy =\n * \"1\"/\"true\"/\"yes\" (case-insensitive). Defaults to false.\n *\n * The env-based loader always produces api-key credentials (the local stdio path).\n * A remote host embedding this package builds a Config directly with bearer\n * credentials (the OAuth path) — see Credentials.\n */\n\n/** OAuth scope covering the read tools. */\nexport const SCOPE_READ = \"datadive.read\";\n/** OAuth scope covering the token-spending write tools. */\nexport const SCOPE_WRITE = \"datadive.write\";\n\n/**\n * How the HTTP client authenticates against the DataDive /v1 API.\n * - \"api-key\": the local stdio path — sends `x-api-key` (DATADIVE_API_KEY).\n * - \"bearer\": the remote/OAuth path — sends `Authorization: Bearer <token>`.\n * The token is read at request time, so a host may mutate the\n * credentials object to rotate a refreshed access token in place.\n */\nexport type Credentials = { kind: \"api-key\"; apiKey: string } | { kind: \"bearer\"; token: string };\n\nexport interface Config {\n credentials: Credentials;\n baseUrl: string;\n /**\n * When true, the token-spending write tools proceed without requiring `confirm: true`.\n * A user-controlled \"don't ask me again\" opt-out, set via DATADIVE_AUTO_CONFIRM_WRITES.\n */\n autoConfirmWrites: boolean;\n /**\n * OAuth scopes granted to this session. When set, tool calls are gated:\n * read tools require SCOPE_READ and write tools SCOPE_WRITE (see server.ts).\n * Undefined (the stdio/api-key path) means no scope gating.\n */\n scopes?: readonly string[];\n /**\n * The end user's IP address, sent as `x-forwarded-for` so the DataDive API logs and rate-limits\n * the person behind the call rather than the host making it. Set only by the hosted (remote)\n * MCP server, which takes it from its own load balancer; the stdio path leaves it unset.\n */\n forwardedFor?: string;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.datadive.tools\";\nconst KEY_HELP_URL = \"https://2.datadive.tools/api-key\";\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {\n const apiKey = (env.DATADIVE_API_KEY ?? \"\").trim();\n if (!apiKey) {\n throw new Error(\n `DATADIVE_API_KEY environment variable is required. Generate one at ${KEY_HELP_URL} ` +\n `and add it to your MCP client config under env.DATADIVE_API_KEY.`,\n );\n }\n\n const rawBase = (env.DATADIVE_API_BASE_URL ?? DEFAULT_BASE_URL).trim();\n // Normalize: strip trailing slashes so we can append \"/v1/...\" paths cleanly.\n const baseUrl = rawBase.replace(/\\/+$/, \"\");\n\n const autoConfirmWrites = [\"1\", \"true\", \"yes\"].includes(\n (env.DATADIVE_AUTO_CONFIRM_WRITES ?? \"\").trim().toLowerCase(),\n );\n\n return { credentials: { kind: \"api-key\", apiKey }, baseUrl, autoConfirmWrites };\n}\n","/**\n * Builds the MCP server: registers each tool from the registry with the SDK.\n *\n * Error handling: if a tool handler throws (most commonly an ApiError from the\n * HTTP client), we surface the message back through MCP as a tool-level error\n * (`isError: true`). The LLM sees the message and can react — e.g. tell the\n * user to regenerate their API key on a 401.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { AnyTool } from \"./tools/types.js\";\nimport { SCOPE_READ, SCOPE_WRITE, type Config } from \"./config.js\";\nimport { allTools } from \"./tools/index.js\";\nimport { ApiError } from \"./http/errors.js\";\nimport { PKG_VERSION, takeUpgradeNotice } from \"./http/client.js\";\nimport { SERVER_INSTRUCTIONS, WHATS_NEW_PROMPT, whatsNewPromptText } from \"./whats-new.js\";\n\n/**\n * Returns the OAuth scope a tool call needs: SCOPE_READ for read-only tools,\n * SCOPE_WRITE for the token-spending writes. Classification comes from the\n * `readOnlyHint` annotation, which every tool in the registry carries.\n */\nexport function requiredScope(tool: Pick<AnyTool, \"annotations\">): string {\n return tool.annotations?.readOnlyHint === true ? SCOPE_READ : SCOPE_WRITE;\n}\n\nexport function buildServer(config: Config): McpServer {\n const server = new McpServer(\n {\n name: \"datadive\",\n version: PKG_VERSION,\n },\n { instructions: SERVER_INSTRUCTIONS },\n );\n\n server.registerPrompt(\n WHATS_NEW_PROMPT.name,\n { title: WHATS_NEW_PROMPT.title, description: WHATS_NEW_PROMPT.description },\n () => ({\n messages: [{ role: \"user\" as const, content: { type: \"text\" as const, text: whatsNewPromptText() } }],\n }),\n );\n\n for (const tool of allTools) {\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema,\n annotations: tool.annotations,\n },\n async (args: Record<string, unknown>) => {\n // OAuth scope gate (remote path only — config.scopes is unset over stdio).\n // The backend enforces scopes on /v1 as well; failing here just gives the\n // model a clearer error without a round-trip.\n const scope = requiredScope(tool);\n if (config.scopes && !config.scopes.includes(scope)) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text:\n `Insufficient scope: this tool requires the \"${scope}\" scope, which was not granted ` +\n `to this connection. Reconnect DataDive and enable the missing permission to use it.`,\n },\n ],\n };\n }\n try {\n const data = await tool.handler(args, { config });\n const content = [\n {\n type: \"text\" as const,\n text: JSON.stringify(data, null, 2),\n },\n ];\n // If the backend signaled a newer release, append a one-time upgrade\n // nudge so the model relays it to the user. takeUpgradeNotice() returns\n // null after the first time, so this fires at most once per session.\n const notice = takeUpgradeNotice();\n if (notice) {\n content.push({\n type: \"text\" as const,\n text: `⚠️ datadive-mcp update available — ${notice}`,\n });\n }\n return { content };\n } catch (err) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text:\n err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err),\n },\n ],\n };\n }\n },\n );\n }\n\n return server;\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; larger values are rejected there with 400. */\nexport const LIST_NICHES_MAX_PAGE_SIZE = 50;\n\n/**\n * `searchText`, `searchAsin`, `orderBy` and `sortOrder` are forwarded to `GET /v1/niches`, which\n * accepts them since the 2026-09-07 API release. They were withdrawn on 2026-08-28 because the\n * endpoint bound only `currentPage`/`pageSize` and dropped unknown query params silently, so they\n * filtered nothing; the same release also fixed paging, which TypeORM had been discarding on the\n * joined builder (RS-11494). Do not re-add a filter here before the route reads it.\n */\nconst inputSchema = {\n currentPage: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(\"Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_NICHES_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`),\n searchText: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe(\n \"Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging \" +\n \"through everything when the user names a niche.\",\n ),\n searchAsin: z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9]{10}$/, \"must be a 10-character ASIN\")\n .optional()\n .describe(\"Only niches whose competitor set contains this ASIN (10 characters, e.g. B08N5WRWNW).\"),\n orderBy: z\n .enum([\"lastDived\", \"name\"])\n .optional()\n .describe(\n \"Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` \" +\n \"(by nicheLabel, A→Z by default). Defaults to `lastDived`.\",\n ),\n sortOrder: z\n .enum([\"ASC\", \"DESC\"])\n .optional()\n .describe(\"Sort direction. Defaults to DESC for `lastDived` and ASC for `name`.\"),\n};\n\nexport const listNichesTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_niches\",\n title: \"List DataDive Niches\",\n description:\n \"Use this first when the user asks about their niches, or to find a `nicheId` for use with \" +\n \"`get_niche_keywords`, `get_niche_competitors`, or `get_ranking_juice`. \" +\n \"Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment \" +\n \"or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace \" +\n \"(com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, \" +\n \"pageSize, total, lastPage, hasNext, hasPrev). Narrow with `searchText` (label/keyword) or \" +\n \"`searchAsin` (a competitor ASIN) instead of scanning pages; a page holds at most \" +\n `${LIST_NICHES_MAX_PAGE_SIZE} niches, so the full list of a large account needs several calls.`,\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheList>(\n { config: ctx.config, toolName: \"list_niches\" },\n \"/v1/niches\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n searchText: args.searchText,\n searchAsin: args.searchAsin,\n orderBy: args.orderBy,\n sortOrder: args.sortOrder,\n },\n );\n },\n};\n","/**\n * Error mapping for the DataDive /v1 API.\n *\n * The MCP server is a thin HTTP shim — when api.datadive.tools returns an error,\n * we want the LLM (and ultimately the user) to see a useful, actionable message\n * rather than \"Request failed\". `ApiError.fromHttp` turns an HTTP status + body\n * into one of those messages.\n *\n * Source of truth for status semantics:\n * datadive-backend/src/external-api/auth/api-key.guard.ts (401/402 logic)\n * datadive-backend/src/external-api/external-api-v1.controller.ts (per-route docs)\n * datadive-backend/src/microservices/token/quota-exceeded.error.ts (QUOTA_EXCEEDED body)\n */\n\nconst KEY_HELP_URL = \"https://2.datadive.tools/api-key\";\n\nexport type ApiErrorKind =\n | \"auth\"\n | \"payment\"\n | \"quota\"\n | \"forbidden\"\n | \"not_found\"\n | \"bad_request\"\n | \"rate_limit\"\n | \"server\"\n | \"network\"\n | \"http\";\n\n/**\n * Body the backend returns whenever a billable action would exceed the subscription's quota,\n * regardless of the HTTP status (400 for most features, 403 for the AI Copywriter). `error` is the\n * stable discriminant; the usage fields are present only when the throw site knows them.\n */\nexport interface QuotaExceededBody {\n error: \"QUOTA_EXCEEDED\";\n message: string;\n subscriptionUrl: string;\n feature?: string;\n used?: number | null;\n capacity?: number | null;\n nextRefreshDate?: string | null;\n}\n\n/** Display names for the backend's `BillableFeature` values; unknown values fall through verbatim. */\nconst FEATURE_LABELS: Record<string, string> = {\n DIVED_ASINS: \"Dive tokens\",\n RANK_RADAR_KEYWORDS: \"Rank Radar tracked keywords\",\n PRODUCT_BRIEF_ASINS: \"Product Brief ASINs\",\n AI_COPYWRITER_PROMPTS: \"AI Copywriter prompts\",\n INDEXING_DIAGNOSIS: \"Indexing Diagnosis checks\",\n};\n\nexport class ApiError extends Error {\n public readonly kind: ApiErrorKind;\n public readonly status: number;\n public readonly body: unknown;\n\n constructor(kind: ApiErrorKind, status: number, message: string, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.kind = kind;\n this.status = status;\n this.body = body;\n }\n\n static fromHttp(status: number, body: unknown): ApiError {\n // Checked before the status branches: the backend uses 400 or 403 depending on the\n // feature, and neither generic mapping would tell the model what ran out or where to go.\n if (isQuotaExceededBody(body)) {\n return new ApiError(\"quota\", status, quotaExceededMessage(body), body);\n }\n\n const serverMsg = extractMessage(body);\n\n if (status === 400) {\n return new ApiError(\n \"bad_request\",\n 400,\n serverMsg ? `Bad request: ${serverMsg}` : \"Bad request: invalid parameters.\",\n body,\n );\n }\n if (status === 401) {\n return new ApiError(\n \"auth\",\n 401,\n `Authentication failed: your DATADIVE_API_KEY is invalid or expired. Generate a new key at ${KEY_HELP_URL}.`,\n body,\n );\n }\n if (status === 402) {\n return new ApiError(\n \"payment\",\n 402,\n \"Subscription is inactive or paused. Resume billing at https://2.datadive.tools to use the API.\",\n body,\n );\n }\n if (status === 403) {\n return new ApiError(\n \"forbidden\",\n 403,\n serverMsg\n ? `Forbidden: ${serverMsg}`\n : \"Forbidden: this API key cannot access the requested resource.\",\n body,\n );\n }\n if (status === 404) {\n return new ApiError(\"not_found\", 404, serverMsg ? `Not found: ${serverMsg}` : \"Resource not found.\", body);\n }\n if (status === 429) {\n return new ApiError(\"rate_limit\", 429, \"Rate limit exceeded. Wait a few seconds and retry.\", body);\n }\n if (status >= 500) {\n return new ApiError(\n \"server\",\n status,\n `DataDive API error (${status}). ${serverMsg ?? \"Please try again.\"}`.trim(),\n body,\n );\n }\n return new ApiError(\"http\", status, serverMsg ?? `HTTP ${status}`, body);\n }\n}\n\nexport function isQuotaExceededBody(body: unknown): body is QuotaExceededBody {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return b.error === \"QUOTA_EXCEEDED\" && typeof b.subscriptionUrl === \"string\";\n}\n\n/**\n * Deterministic, server-data-only wording for a quota error. The model relays this instead of\n * improvising: it names the exhausted feature, states usage when known, points at the\n * informational subscription page, and tells the model not to retry. It deliberately does not\n * quote prices or push a specific upgrade — the connector directories only allow explaining that\n * a feature needs a different plan and linking to a plan page.\n */\nexport function quotaExceededMessage(body: QuotaExceededBody): string {\n const lead = typeof body.message === \"string\" && body.message.length > 0 ? body.message : \"Quota exceeded\";\n const feature = body.feature ? (FEATURE_LABELS[body.feature] ?? body.feature) : null;\n\n const parts: string[] = [];\n if (feature) {\n parts.push(`This subscription has used up its ${feature} quota${usageSuffix(body)}.`);\n } else {\n parts.push(`This subscription has used up its quota for this action${usageSuffix(body)}.`);\n }\n if (body.nextRefreshDate) {\n parts.push(`It refreshes on ${body.nextRefreshDate.slice(0, 10)}.`);\n }\n parts.push(\n `Tell the user which quota ran out and that they can review or raise it on their subscription page: ${body.subscriptionUrl}`,\n );\n parts.push(\"Do not retry this call until the quota has been raised.\");\n\n return `${lead}. ${parts.join(\" \")}`;\n}\n\nfunction usageSuffix(body: QuotaExceededBody): string {\n if (typeof body.used === \"number\" && typeof body.capacity === \"number\") {\n return ` (${body.used} of ${body.capacity} used)`;\n }\n return \"\";\n}\n\nfunction extractMessage(body: unknown): string | null {\n if (!body || typeof body !== \"object\") return null;\n const b = body as Record<string, unknown>;\n // NestJS conventional error shapes: { message: string | string[] } or { error, message }.\n if (typeof b.message === \"string\") return b.message;\n if (Array.isArray(b.message) && b.message.every((m) => typeof m === \"string\")) {\n return (b.message as string[]).join(\"; \");\n }\n if (typeof b.error === \"string\") return b.error;\n return null;\n}\n","{\n \"name\": \"@datadive-tools/mcp\",\n \"version\": \"0.18.0\",\n \"description\": \"MCP server for DataDive — query your niches, keywords, competitors, and Rank Radar data from Claude Desktop, Claude Code, or Cursor.\",\n \"type\": \"module\",\n \"bin\": {\n \"datadive-mcp\": \"./dist/index.js\"\n },\n \"main\": \"./dist/lib.js\",\n \"types\": \"./dist/lib.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"engines\": {\n \"node\": \">=22\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:coverage\": \"vitest run --coverage --coverage.include=\\\"src/**\\\" --coverage.reporter=json-summary --coverage.reporter=text\",\n \"test:watch\": \"vitest\",\n \"test:smoke\": \"DATADIVE_SMOKE=1 vitest run test/smoke.test.ts\",\n \"lint\": \"eslint .\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm run test && npm run build\",\n \"prepare\": \"npm run build\"\n },\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"datadive\",\n \"amazon\",\n \"claude\"\n ],\n \"author\": \"DataDive\",\n \"license\": \"MIT\",\n \"homepage\": \"https://github.com/Data-Dive-Tools/datadive-mcp\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Data-Dive-Tools/datadive-mcp.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Data-Dive-Tools/datadive-mcp/issues\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.0.0\",\n \"zod\": \"^3.23.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.0\",\n \"@types/node\": \"^20.0.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n \"@typescript-eslint/parser\": \"^8.0.0\",\n \"@vitest/coverage-v8\": \"4.1.8\",\n \"eslint\": \"^9.0.0\",\n \"prettier\": \"^3.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"~5.9.0\",\n \"vitest\": \"4.1.8\"\n }\n}\n","/**\n * HTTP client for the DataDive /v1 API.\n *\n * Responsibilities:\n * 1. Attach auth from config.credentials to every request — `x-api-key` (local\n * stdio path) or `Authorization: Bearer` (remote/OAuth path).\n * 2. Tag the User-Agent with the package version + tool name so backend access\n * logs can attribute usage per tool (no separate telemetry endpoint needed).\n * 3. Map HTTP errors via `ApiError.fromHttp`.\n * 4. Unwrap the `ResponseDto<T>` envelope (`{ message?, success?, data }`)\n * while preserving bare `PaginationResponse<T>` bodies (which need their\n * pagination metadata to reach the LLM).\n *\n * Envelope detection (verified against datadive-backend):\n * - GET /v1/niches -> NicheList extends PaginationResponse<NicheItem> (BARE)\n * - GET /v1/niches/rank-radars -> ResponseDto<ExternalRankRadarListResponseDto>\n * where the inner DTO IS itself paginated.\n * - All other read endpoints -> ResponseDto<T> (UNWRAP)\n *\n * Heuristic: if the body has `data` AND any pagination key (currentPage, lastPage,\n * hasNext, hasPrev, pageSize, total), it's a PaginationResponse — return as-is.\n * Otherwise if the body has `data`, it's a ResponseDto envelope — return body.data.\n *\n * Cite: datadive-backend/src/common/pagination/pagination.dto.ts (pagination keys)\n * datadive-backend/src/common/dto/response.dto.ts (envelope shape)\n */\n\nimport type { Config } from \"../config.js\";\nimport { ApiError } from \"./errors.js\";\nimport { version } from \"../../package.json\";\n\n/**\n * The running package version, inlined from package.json at build time by\n * tsup/esbuild (and by vite under vitest). Single source of truth — no manual\n * bump here, so it can never drift from what's published to npm.\n *\n * Written into the User-Agent header, and compared against the backend's\n * advertised latest version (see noteLatestVersion) to nudge stale installs.\n */\nexport const PKG_VERSION: string = version;\n\n/** Response header the DataDive backend uses to advertise the latest published MCP version. */\nconst LATEST_VERSION_HEADER = \"x-datadive-mcp-latest\";\n\n/**\n * One-time upgrade notice. Armed when the backend advertises a version newer than\n * the one we're running; consumed (and then suppressed for the rest of the session)\n * by takeUpgradeNotice(). We surface it once per process so we don't nag on every call.\n */\nlet pendingUpgradeNotice: string | null = null;\nlet hasWarned = false;\n\n/**\n * Compares dotted version strings (major.minor.patch). Pre-release / build suffixes\n * are stripped — a stable release is the signal we act on. Returns true iff `latest`\n * is strictly newer than `current`. Exported for unit testing.\n */\nexport function isNewerVersion(latest: string, current: string): boolean {\n const parse = (v: string) => v.split(/[-+]/)[0]!.split(\".\").map((n) => parseInt(n, 10) || 0);\n const a = parse(latest);\n const b = parse(current);\n for (let i = 0; i < 3; i++) {\n const da = a[i] ?? 0;\n const db = b[i] ?? 0;\n if (da !== db) return da > db;\n }\n return false;\n}\n\n/** Records the advertised latest version; arms a one-time notice if we're behind. Never throws. */\nfunction noteLatestVersion(latest: string | null): void {\n if (!latest || hasWarned) return;\n if (isNewerVersion(latest, PKG_VERSION)) {\n pendingUpgradeNotice =\n `A newer version of datadive-mcp is available (${latest}; you are running ${PKG_VERSION}). ` +\n `Update by reinstalling the latest from npm — e.g. \\`npm install -g @datadive-tools/mcp@latest\\` — ` +\n `or bump the @datadive-tools/mcp version in your MCP client config, then restart the client.`;\n }\n}\n\n/**\n * Returns a pending upgrade notice once, then suppresses further notices for the rest\n * of the session. Called when assembling a tool result so the LLM can relay it to the\n * user. Returns null when there's nothing to surface.\n */\nexport function takeUpgradeNotice(): string | null {\n if (!pendingUpgradeNotice) return null;\n const notice = pendingUpgradeNotice;\n pendingUpgradeNotice = null;\n hasWarned = true;\n return notice;\n}\n\nconst PAGINATION_KEYS = [\"currentPage\", \"lastPage\", \"hasNext\", \"hasPrev\", \"pageSize\", \"total\"] as const;\n\nexport interface RequestContext {\n config: Config;\n /** Tool name used to tag the User-Agent header for per-tool log attribution. */\n toolName: string;\n}\n\nexport async function httpGet<T>(ctx: RequestContext, path: string, query?: Record<string, unknown>): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path, query);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"GET\",\n headers: {\n ...authHeaders(ctx.config),\n ...forwardedForHeader(ctx.config),\n accept: \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * POST a JSON body to a /v1 write endpoint. Mirrors httpGet (same auth/UA headers,\n * version notice, error mapping, and envelope unwrapping) but adds a JSON request\n * body. The create endpoints return bare objects (no ResponseDto envelope), which\n * unwrap() passes through untouched.\n */\nexport async function httpPost<T>(ctx: RequestContext, path: string, body: unknown): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...authHeaders(ctx.config),\n ...forwardedForHeader(ctx.config),\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n body: JSON.stringify(body),\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * PATCH a JSON body to a /v1 write endpoint. Mirrors httpPost (same auth/UA headers,\n * version notice, error mapping, envelope unwrapping) but uses PATCH for partial\n * updates (`update_rank_radar_tag`, RS-11822). Backend returns the updated resource\n * wrapped in the usual ResponseDto envelope.\n */\nexport async function httpPatch<T>(ctx: RequestContext, path: string, body: unknown): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"PATCH\",\n headers: {\n ...authHeaders(ctx.config),\n ...forwardedForHeader(ctx.config),\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n body: JSON.stringify(body),\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * DELETE a /v1 resource. Same auth/UA headers, version notice and error mapping as\n * httpGet. The delete endpoints answer 204 No Content, so handleResponse resolves to\n * `null` — callers return their own confirmation payload rather than that null.\n */\nexport async function httpDelete<T>(ctx: RequestContext, path: string): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"DELETE\",\n headers: {\n ...authHeaders(ctx.config),\n ...forwardedForHeader(ctx.config),\n accept: \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\nfunction networkError(url: string, e: unknown): ApiError {\n const msg = e instanceof Error ? e.message : String(e);\n return new ApiError(\"network\", 0, `Network error reaching ${url}: ${msg}`);\n}\n\n/**\n * Shared response handling for httpGet/httpPost: arm the one-time upgrade notice,\n * parse the body, map HTTP errors via ApiError, and unwrap the ResponseDto envelope.\n */\nasync function handleResponse<T>(res: Response): Promise<T> {\n // The backend advertises the latest published MCP version on every response\n // (including errors); arm a one-time upgrade notice if we're behind.\n noteLatestVersion(res.headers.get(LATEST_VERSION_HEADER));\n\n const text = await res.text();\n let body: unknown;\n try {\n body = text.length > 0 ? JSON.parse(text) : null;\n } catch {\n body = text;\n }\n\n if (!res.ok) {\n throw ApiError.fromHttp(res.status, body);\n }\n\n return unwrap<T>(body);\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, unknown>): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n const u = new URL(`${baseUrl}${normalizedPath}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined || v === null) continue;\n u.searchParams.set(k, String(v));\n }\n }\n return u.toString();\n}\n\nfunction userAgent(toolName: string): string {\n return `datadive-mcp/${PKG_VERSION} tool/${toolName}`;\n}\n\n/**\n * Auth header(s) for the configured credentials: `x-api-key` on the local stdio\n * path, `Authorization: Bearer` on the remote/OAuth path. The credentials object\n * is read per request so a host can rotate a refreshed bearer token in place.\n */\nfunction authHeaders(config: Config): Record<string, string> {\n const c = config.credentials;\n return c.kind === \"bearer\" ? { authorization: `Bearer ${c.token}` } : { \"x-api-key\": c.apiKey };\n}\n\nfunction forwardedForHeader(config: Config): Record<string, string> {\n return config.forwardedFor ? { \"x-forwarded-for\": config.forwardedFor } : {};\n}\n\n/**\n * Unwraps a ResponseDto<T> envelope while preserving bare PaginationResponse bodies.\n * Exported for unit testing.\n */\nexport function unwrap<T>(body: unknown): T {\n if (!isObject(body)) return body as T;\n if (!(\"data\" in body)) return body as T;\n // If pagination metadata is present at the top level, return as-is.\n for (const key of PAGINATION_KEYS) {\n if (key in body) return body as T;\n }\n // Plain ResponseDto envelope -> unwrap.\n return (body as { data: T }).data;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { GetMasterKeywordListResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_keywords\",\n title: \"Get Master Keyword List for a Niche\",\n description:\n \"Use this when the user asks about keywords, search terms, or search volume for a niche. \" +\n \"Retrieves the master keyword list for the specified Niche — the relevant search terms \" +\n \"monitored for ranking and performance metrics. For each keyword, returns search volume, \" +\n \"relevancy (numeric score or \\\"Outlier\\\"), and competitor ASIN organic ranks \" +\n \"(asinRanks: { ASIN -> rank | null }), plus `origin`: `mkl` (found by the dive), `manual` (added by \" +\n \"hand with `add_niche_keywords`), `outlier` / `residue` (moved in from those lists). Only `manual`, \" +\n \"`outlier` and `residue` keywords can be taken out with `remove_niche_keywords`. Keywords hidden by the \" +\n \"niche's keyword exclusions are not listed; see `list_keyword_exclusions`. Also returns the \" +\n \"latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<GetMasterKeywordListResult>(\n { config: ctx.config, toolName: \"get_niche_keywords\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { KeywordExclusions } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const listKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_keyword_exclusions\",\n title: \"List a Niche's Keyword Exclusions\",\n description:\n \"Use this to see which keywords are hidden from a niche's Master Keyword List, e.g. when a keyword the \" +\n \"user expects is missing from `get_niche_keywords`. Returns the same lists the DataDive web app shows, \" +\n \"oldest first: `exact` — each entry hides the one keyword that matches it exactly, with that keyword's \" +\n \"`searchVolume` (0 when the niche has no such keyword); `phrase` — each entry hides every keyword that \" +\n \"contains the phrase, with `excludedKeywordsCount` and the total `searchVolume` of the keywords it hides \" +\n \"(not counting ones already excluded exactly). Exclusions hide keywords wherever they come from: the \" +\n \"dive, the Outliers and Residue lists, and hand-added ones. Change them with `add_keyword_exclusions` \" +\n \"and `remove_keyword_exclusions`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<KeywordExclusions>(\n { config: ctx.config, toolName: \"list_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheRoots } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheRootsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_roots\",\n title: \"Get Keyword Roots for a Niche\",\n description:\n \"Use this when the user wants to find the highest-impact words across a niche's keywords, asks about \" +\n \"'keyword roots', or wants to prioritize terms for a listing. \" +\n \"Retrieves the keyword lexical roots for the Niche — individual words or word-combinations extracted \" +\n \"from the Master Keyword List (e.g. 'bluetooth headphones' yields roots 'bluetooth', 'headphones', \" +\n \"'bluetooth headphones'). Returns `roots` and `normalizedRoots` tables, each item with root, frequency \" +\n \"(how many keywords contain it), broadSearchVolume (summed search volume of those keywords), and \" +\n \"broadSearchVolumeRatio (0-1, relative to the top root), plus the per-keyword breakdowns and \" +\n \"latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheRoots>(\n { config: ctx.config, toolName: \"get_niche_roots\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/roots`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { GetCompetitorListResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheCompetitorsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_competitors\",\n title: \"Get Competitors and Niche Statistics\",\n description:\n \"Use this for competitor ASINs, product titles, BSR, sales/revenue benchmarks, or niche \" +\n \"opportunity scoring. Retrieves the list of Competitors within the specified Niche along with \" +\n \"Niche statistics. For the Niche: keyword statistics, opportunity evaluation, benchmark median \" +\n \"values, and overall competitor strength assessment. For each Competitor: ASIN, product title, \" +\n \"BSR, category and full category tree, sales, revenue, ratings, reviews, price, number of \" +\n \"variations, image URL, total Ranking Juice, and ranking data. BSR supports ranking \" +\n \"Competitors by sales position within mentioned category. For the Ranking Juice breakdown by \" +\n \"title, bullets and description, use `get_ranking_juice`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<GetCompetitorListResult>(\n { config: ctx.config, toolName: \"get_niche_competitors\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/competitors`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheRankingJuices } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getRankingJuiceTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_ranking_juice\",\n title: \"Get Ranking Juices for a Niche\",\n description:\n \"Use this when the user asks 'how do I rank higher', 'what's my ranking juice', or wants \" +\n \"listing-optimization guidance. \" +\n \"Retrieves the Ranking Juices for each Competitor within the specified Niche. Ranking Juice is \" +\n \"DataDive's proprietary metric identifying the key factors driving search-ranking success \" +\n \"for each Competitor's listing. Returns currentListing and optimizedListing breakdowns \" +\n \"(rankingJuice + per-property contributions: title, bullets, description) plus per-competitor \" +\n \"listing scores and the latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheRankingJuices>(\n { config: ctx.config, toolName: \"get_ranking_juice\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ranking-juices`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport {\n RANK_RADAR_STATUSES,\n type ApiRankRadarItem,\n type ApiRankRadarList,\n type RankRadarItem,\n type RankRadarList,\n} from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; a larger value is not honoured there. */\nexport const LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_RANK_RADARS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_RANK_RADARS_MAX_PAGE_SIZE}). Defaults to 20.`),\n nicheId: z\n .string()\n .min(1)\n .optional()\n .describe(\"Filter Rank Radars by Niche identifier. Use after `list_niches`.\"),\n status: z\n .enum(RANK_RADAR_STATUSES)\n .optional()\n .describe(\n \"Which Rank Radars to return. ACTIVE (the default) are tracking; PAUSED have been stopped \" +\n \"but keep their history and can be resumed; ARCHIVED have been deleted; ALL means active \" +\n \"and paused together. Archived Rank Radars are returned only by an explicit ARCHIVED.\",\n ),\n searchText: z.string().optional().describe(\"Filter Rank Radars by ASIN or product title.\"),\n};\n\n/**\n * Drops the `krt_asin` row's internal identifiers and keeps the ASIN itself. The API sends\n * `asin: { id, krtId, asin }`, where `id` is an internal row id and `krtId` repeats the item's own\n * `id`; neither is accepted by any tool, so both are noise in the model's context.\n */\nfunction minimize(item: ApiRankRadarItem): RankRadarItem {\n const { asin, ...rest } = item;\n\n return { ...rest, asin: asin?.asin };\n}\n\nexport const listRankRadarsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_rank_radars\",\n title: \"List Rank Radars\",\n description:\n \"Use this to find a `rankRadarId` before calling `get_rank_radar_data`. Filter by `nicheId` \" +\n \"if the user has already identified a niche. \" +\n \"Retrieves a paginated list of Rank Radars — keyword-rank trackers monitoring organic and \" +\n \"sponsored positions for specific ASINs over time. Each item includes id, status (ACTIVE, \" +\n \"PAUSED or ARCHIVED), asin, marketplace, keywordCount, title, imageUrl, and summary metrics: \" +\n \"top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, \" +\n `total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADARS_MAX_PAGE_SIZE} ` +\n \"Rank Radars, so a large account needs several calls.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<RankRadarList> => {\n const page = await httpGet<ApiRankRadarList>(\n { config: ctx.config, toolName: \"list_rank_radars\" },\n \"/v1/niches/rank-radars\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n nicheId: args.nicheId,\n status: args.status,\n searchText: args.searchText,\n },\n );\n\n return { ...page, data: (page.data ?? []).map(minimize) };\n },\n};\n","/**\n * Hand-mirrored DTOs from the DataDive backend (v1 external API).\n *\n * Source of truth (in this priority order):\n * 1. Live Swagger: https://developer.datadive.tools/docs#/v1\n * (machine-readable: https://developer.datadive.tools/docs-json)\n * 2. Backend DTOs: datadive-backend/src/external-api/dto/*.ts\n * datadive-backend/src/niche-research/dto/rank-radar.response.dto.ts\n * datadive-backend/src/common/dto/response.dto.ts\n * datadive-backend/src/common/pagination/pagination.dto.ts\n * 3. Controller: datadive-backend/src/external-api/external-api-v1.controller.ts\n *\n * Last synced: 2026-06-19.\n *\n * The MCP server forwards JSON straight to the LLM, so deeply-nested DTOs are\n * intentionally typed loosely (with `unknown` or pass-through Records) where\n * the TS surface doesn't add safety. Pin tighter as use cases require.\n */\n\n// ─── Envelope + pagination ───────────────────────────────────────────────────\n\n/** Generic NestJS response envelope: { message?, success?, data? }. */\nexport interface ResponseEnvelope<T> {\n message?: string;\n /** @deprecated — use HTTP status as the success indicator. */\n success?: boolean;\n data?: T | null;\n}\n\n/** Standard pagination envelope returned by list endpoints. */\nexport interface PaginationResponse<T> {\n data: T[];\n currentPage: number;\n pageSize: number;\n total: number;\n lastPage: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/**\n * ISO-8601 date or timestamp, mirroring the backend's `@IsISO8601()` (date-only\n * allowed). Shared by the date/timestamp query params across /v1 tools\n * (alerts `updatedSince`, usage `startDate`/`endDate`).\n */\nexport const ISO_8601 = /^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/**\n * Marketplace codes accepted by `marketplace` params across /v1 endpoints\n * (alerts, inventory). Mirrors the backend's `SupportedMarketplaceEnum`.\n */\nexport const SUPPORTED_MARKETPLACES = [\n \"com\",\n \"ca\",\n \"co.uk\",\n \"com.mx\",\n \"in\",\n \"fr\",\n \"de\",\n \"es\",\n \"it\",\n \"co.jp\",\n] as const;\n\n// ─── /v1/niches (NicheList = PaginationResponse<NicheItem>) ─────────────────\n\nexport interface NicheItem {\n nicheId: string;\n heroKeyword: string;\n nicheLabel: string;\n /** Amazon marketplace TLD: \"com\", \"uk\", \"de\", etc. */\n marketplace: string;\n /** ISO date string when the Niche was last researched. Nullable. */\n latestResearchDate: string | null;\n}\n\nexport type NicheList = PaginationResponse<NicheItem>;\n\n// ─── /v1/niches/:nicheId/keywords (GetMasterKeywordListResult, wrapped) ─────\n\n/** Map of competitor ASIN -> rank (or null when unranked). */\nexport type AsinRanks = Record<string, number | null>;\n\n/**\n * How a keyword got into the Master Keyword List: `mkl` found by the dive, `manual` added by hand,\n * `outlier` / `residue` moved in from those lists. The last three are what `remove_niche_keywords`\n * can take back out.\n */\nexport type MklKeywordOrigin = \"mkl\" | \"manual\" | \"outlier\" | \"residue\";\n\nexport interface MasterKeyword {\n keyword: string;\n searchVolume: number;\n /** \"Outlier\" or a numeric relevancy score. */\n relevancy: string | number;\n asinRanks: AsinRanks;\n origin: MklKeywordOrigin;\n}\n\nexport interface GetMasterKeywordListResult {\n keywords: MasterKeyword[];\n /** ISO date string. */\n latestResearchDate: string;\n}\n\n// ─── POST /v1/niches/:nicheId/keywords (AddNicheKeywordsResponseDto, wrapped) ─\n\n/** Mirrors the API's `ADD_MANUAL_KEYWORDS_MAX_LIMIT` and `MANUAL_KEYWORD_MAX_LENGTH`. */\nexport const NICHE_KEYWORDS_MAX_PER_CALL = 100;\nexport const NICHE_KEYWORD_MAX_LENGTH = 255;\n\nexport interface AddNicheKeywordsResult {\n /** Every submitted keyword, in exactly one group. */\n originBreakdown: {\n alreadyInMkl: string[];\n takenFromOutliers: string[];\n takenFromResidue: string[];\n addedManually: string[];\n };\n /** Dive tokens spent (or, on a dry run, the most the re-dive can cost). 0 for a fresh niche. */\n tokensUsed: number;\n /** Present only when the add re-dived a stale niche; poll `get_dive_status` with `diveId`. */\n refresh?: { diveId: string; estimatedCompletionDate: string };\n}\n\n/** 400 body when a stale-niche add would cost more than `maxTokensUsed`. Nothing is saved or charged. */\nexport interface MaxTokensExceededBody {\n error: \"MAX_TOKENS_EXCEEDED\";\n message: string;\n maxTokensUsed: number;\n tokensUsed: number;\n}\n\n// ─── POST /v1/niches/:nicheId/keywords/remove (RemoveNicheKeywordsResponseDto, wrapped) ─\n\nexport interface RemoveNicheKeywordsResult {\n removedManual: string[];\n sentBackToOutliers: string[];\n sentBackToResidue: string[];\n /** Found by the dive itself, or not in the niche at all. Left unchanged. */\n notRemovable: string[];\n}\n\n// ─── /v1/niches/:nicheId/keyword-exclusions (KeywordExclusionsDto, wrapped) ──\n\n/** Mirrors the API's `KEYWORD_EXCLUSIONS_MAX_PER_REQUEST` and `KEYWORD_EXCLUSIONS_MAX_PER_NICHE`. */\nexport const KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;\nexport const KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1000;\n\nexport interface KeywordExclusions {\n /** Oldest first. `searchVolume` is 0 when the niche has no such keyword. */\n exact: Array<{ keyword: string; searchVolume: number }>;\n /** Oldest first. Volume and count of the keywords the phrase hides, besides exact-excluded ones. */\n phrase: Array<{ phrase: string; searchVolume: number; excludedKeywordsCount: number }>;\n}\n\nexport interface KeywordExclusionEntries {\n exact: string[];\n phrase: string[];\n}\n\n/** POST /v1/niches/:nicheId/keyword-exclusions: the lists after the add, plus what changed. */\nexport interface AddKeywordExclusionsResult extends KeywordExclusions {\n added: KeywordExclusionEntries;\n /** Already excluded; left as is. */\n unchanged: KeywordExclusionEntries;\n}\n\n/** POST /v1/niches/:nicheId/keyword-exclusions/remove: the lists after the remove, plus what changed. */\nexport interface RemoveKeywordExclusionsResult extends KeywordExclusions {\n removed: KeywordExclusionEntries;\n /** Were not excluded; left as is. */\n unchanged: KeywordExclusionEntries;\n}\n\n// ─── /v1/niches/:nicheId/competitors (GetCompetitorListResult, wrapped) ─────\n\n/**\n * A single Competitor row. The fields documented here are the ones worth naming for\n * a consumer; the index signature keeps the rest of the payload (keyword and ads\n * diagnostics, evaluation labels) available to the LLM without enumerating it.\n */\nexport interface Competitor {\n asin: string;\n /** Product title. */\n title: string;\n /** Best Sellers Rank. Null when Amazon publishes none for the product. */\n bsr: number | null;\n /** Amazon category name. Null when unknown. */\n category: string | null;\n /** Full category path, e.g. \"Pet Supplies > Dogs > Apparel & Accessories\". Null when unknown. */\n categoryTree: string | null;\n brand: string | null;\n price: number | null;\n rating: number | null;\n reviewCount: number | null;\n sales: number | null;\n revenue: number | null;\n numberOfVariations: number;\n /**\n * Total Ranking Juice for this listing. Use `get_ranking_juice` for the breakdown\n * by title, bullets and description.\n */\n listingRankingJuice: { value: number };\n [key: string]: unknown;\n}\n\n/**\n * Niche competitors response — several nested DTOs from the backend (MklStatisticsData,\n * OpportunityEvaluation, MklBenchmark, CompetitorsStrength). We type the outer shape and\n * the competitor rows, and leave the niche-level aggregates as opaque records; the LLM\n * sees the full JSON and can reason about it. Tighten if a specific consumer needs it.\n */\nexport interface GetCompetitorListResult {\n /** Marketplace enum (AmazonStore in backend). */\n marketplace: string;\n statistics: Record<string, unknown>;\n opportunityEvaluation: Record<string, unknown>;\n benchmark: Record<string, unknown>;\n competitorsStrength: Record<string, unknown>;\n competitors: Competitor[];\n latestResearchDate: string;\n}\n\n// ─── /v1/niches/:nicheId/ranking-juices (NicheRankingJuices, wrapped) ───────\n\nexport interface RankingJuiceProperty {\n rankingJuice: number;\n}\n\nexport interface ListingRankingJuice {\n rankingJuice: number;\n title: RankingJuiceProperty;\n bullets: RankingJuiceProperty;\n description: RankingJuiceProperty;\n}\n\nexport interface CompetitorRankingJuice {\n asin: string;\n listing: ListingRankingJuice;\n}\n\nexport interface NicheRankingJuices {\n currentListing: ListingRankingJuice;\n optimizedListing: ListingRankingJuice;\n competitors: CompetitorRankingJuice[];\n latestResearchDate: string;\n}\n\n// ─── /v1/niches/rank-radars (ExternalRankRadarListResponseDto, wrapped) ─────\n\n/**\n * States a Rank Radar can be reported in. Matches the backend's `ExternalRankRadarStatus`, so a\n * value read out of a response can be fed straight back into the `status` filter.\n *\n * `PAUSED` is what the API's own archive endpoint produces (tracking stopped, data kept);\n * `ARCHIVED` is a Rank Radar that was deleted.\n */\nexport const RANK_RADAR_STATES = [\"ACTIVE\", \"PAUSED\", \"ARCHIVED\"] as const;\nexport type RankRadarState = (typeof RANK_RADAR_STATES)[number];\n\n/**\n * Accepted values of the `status` query param on /v1/niches/rank-radars. `ALL` is filter-only —\n * it selects active and paused together and never appears as an item's own status.\n *\n * The API rejects anything else with 400 (datadive-backend `ExternalKeywordRankTrackerStatus`).\n * Before RS-11518 it accepted only `ALL`/`PAUSED` and silently answered anything else with the\n * active set, so this list must not be widened ahead of the API.\n */\nexport const RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, \"ALL\"] as const;\nexport type RankRadarStatus = (typeof RANK_RADAR_STATUSES)[number];\n\n/** A row exactly as `/v1/niches/rank-radars` sends it, before the tool minimizes it. */\nexport interface ApiRankRadarItem {\n id: string;\n status: RankRadarState;\n /** Only `asin` carries information; `id`/`krtId` are internal row identifiers. */\n asin: { id: string; krtId: string; asin: string };\n marketplace: string;\n keywordCount: number;\n title: string;\n imageUrl: string;\n top10KW: number | null;\n top10SV: number | null;\n top50KW: number | null;\n top50SV: number | null;\n}\n\n/** A row as `list_rank_radars` returns it: `asin` flattened to the ASIN, no internal identifiers. */\nexport interface RankRadarItem extends Omit<ApiRankRadarItem, \"asin\"> {\n asin: string;\n}\n\nexport type ApiRankRadarList = PaginationResponse<ApiRankRadarItem>;\nexport type RankRadarList = PaginationResponse<RankRadarItem>;\n\n// ─── POST /v1/niches/rank-radars (CreateRankRadarSuccessResponseDto, bare) ──\n\n/**\n * A non-blocking notice the API attaches to an otherwise successful write (RS-11615).\n * `message` is customer-facing prose composed by the backend so every surface — the\n * DataDive UI, the public API and this server — words it identically; show it verbatim\n * rather than paraphrasing it. Treat an unrecognised `code` as informational.\n */\nexport interface ApiWarning {\n /** Machine-readable code, e.g. `FAMILY_ALREADY_TRACKED`. */\n code: string;\n message: string;\n /** Ids of the existing resources the warning refers to, when it names any. */\n existingRankRadarIds?: string[];\n}\n\n/** Result of creating a Rank Radar. */\nexport interface CreateRankRadarResult {\n /** `null` when the request was a dry run, because nothing was created. */\n rankRadarId: string | null;\n /** Non-blocking notices about this creation. Empty or absent when there is nothing to flag. */\n warnings?: ApiWarning[];\n}\n\n// ─── POST /v1/niches/dives (CreateNicheDiveSuccessResponseDto, bare) ────────\n\n/** Result of kicking off a Niche Dive. Poll get_dive_status with `diveId`. */\nexport interface CreateNicheDiveResult {\n diveId: string;\n /** ISO-8601 estimated completion timestamp. */\n estimatedCompletionDate: string;\n}\n\n// ─── POST /v1/niches/:nicheId/redive (RediveNicheResponseDto, wrapped) ──────\n\n/**\n * Result of re-diving an existing niche. Same shape as CreateNicheDiveResult and\n * polled the same way, but the niche keeps its existing `nicheId` — so unlike a new\n * dive, `get_dive_status` success carries no id the caller did not already have.\n */\nexport interface RediveNicheResult {\n diveId: string;\n /** ISO-8601 estimated completion timestamp. */\n estimatedCompletionDate: string;\n}\n\n// ─── GET /v1/niches/dives/:diveId (NicheDiveStatus*ResponseDto, bare oneOf) ──\n\n/**\n * Niche Dive status, discriminated on `status`:\n * - \"in_progress\" → estimatedCompletionDate\n * - \"success\" → nicheId + token usage (feed nicheId to list_niches / get_niche_*)\n * - \"error\" → error message\n */\nexport type DiveStatus =\n | { diveId: string; status: \"in_progress\"; estimatedCompletionDate: string }\n | { diveId: string; status: \"success\"; nicheId: string; tokensUsed: number; tokensLeft: number }\n | { diveId: string; status: \"error\"; error: string };\n\n// ─── /v1/niches/rank-radars/:rankRadarId (KrtKeywordListResponseDto, wrapped) ─\n\nexport interface KrtAsinRank {\n /** yyyy-mm-dd. */\n date: string;\n organicRank?: number | null;\n sponsoredRank?: number | null;\n impressionRank?: number | null;\n}\n\n/**\n * A tag (\"highlight\") attached to a Rank Radar keyword in its ranking timeline. Same object as a\n * row in `list_rank_radar_tags` — the `id` field is the one the tag-management tools (update,\n * delete, add/remove search terms) take as `tagId`. The v1 response field names are frozen, so\n * this type does not rename them: `krtId` is the Rank Radar id, `krtKeywordId` the keyword id.\n * `type` is the backend `KRTHighlightType` enum; the system \"paused\" tag uses the reserved colour\n * `TAG_RESERVED_COLOR` and is also surfaced here.\n */\nexport interface KrtHighlight {\n id: string;\n krtId: string;\n krtKeywordId: string;\n title: string;\n description: string | null;\n color: string | null;\n type: string;\n /** ISO-8601 timestamp or date; null when the tag has no window. */\n startAt: string | null;\n /** ISO-8601 timestamp or date; null when the tag has no window. */\n endAt: string | null;\n}\n\nexport interface KrtKeyword {\n id: string;\n keyword: string;\n searchVolume: number | null;\n /** Phrase relevancy score for this keyword within its Niche. */\n relevancy?: number | null;\n ranks: KrtAsinRank[];\n /** User tags on this keyword — the same objects `list_rank_radar_tags` returns (RS-11822). */\n highlights: KrtHighlight[];\n /** PPC data. Still sent by the API but due for removal (RS-11744); kept opaque. */\n adData?: Record<string, unknown> | null;\n /** SQP data. Still sent by the API but due for removal (RS-11744); kept opaque. */\n sqpData?: Record<string, unknown> | null;\n}\n\n/** One page of a Rank Radar's active keywords (paged since RS-11683); paused keywords are not included. */\nexport type RankRadarKeywordList = PaginationResponse<KrtKeyword>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/tags (RS-11822) ────────────────────────────────────\n\n/** Mirrors backend `TAG_TITLE_MAX_LENGTH`. */\nexport const TAG_TITLE_MAX_LENGTH = 100;\n\n/** Mirrors backend `TAG_KEYWORD_IDS_MAX` — attach/detach cap and initial-assignment cap on create. */\nexport const TAG_KEYWORD_IDS_MAX = 1000;\n\n/**\n * Reserved for the system \"paused\" tag the quota pauser writes. Rejected with 400 on tag\n * create and update; the system tag that uses it is excluded from `list_rank_radar_tags` and\n * returns 404 on update / delete / search-term attach / search-term remove.\n */\nexport const TAG_RESERVED_COLOR = \"#EB5757\";\n\n/** `#RRGGBB` only, matching the UI colour picker's output. Not `#C9D` short form, not `rgba(...)`. */\nexport const TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;\n\n/** A single tag row — `list` returns these, `create` and `update` return one of them. */\nexport interface RankRadarTag {\n id: string;\n title: string;\n color: string | null;\n description: string | null;\n startAt: string | null;\n endAt: string | null;\n keywordCount: number;\n createdAt: string | null;\n}\n\n/** `GET /v1/niches/rank-radars/:rankRadarId/tags` — bare paginated body (follows the backend list pattern). */\nexport type RankRadarTagList = PaginationResponse<RankRadarTag>;\n\n/**\n * `POST .../tags/:tagId/search-terms` (attach) and `.../search-terms/remove` (detach). Kept\n * opaque for now — the backend side ships in RS-11821 and its response shape is not yet frozen.\n * When RS-11821 lands, tighten this to the real DTO.\n */\nexport type TagSearchTermsResult = Record<string, unknown>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/sqp (RankRadarSqpKeywordListResponseDto, bare) ─\n\n/** SQP metrics of one keyword, aggregated over the requested range. Null when Amazon has no SQP data for it. */\nexport interface RankRadarSqpKeyword {\n id: string;\n keyword: string;\n numberOfDaysWithData: number | null;\n searchQueryVolume: number | null;\n searchQueryScore: number | null;\n impressionsTotalCount: number | null;\n impressionsAsinCount: number | null;\n impressionsAsinShare: number | null;\n clicksTotalCount: number | null;\n clicksAsinCount: number | null;\n clicksAsinShare: number | null;\n clicksClickRate: number | null;\n cartAddsTotalCount: number | null;\n cartAddsAsinCount: number | null;\n cartAddsAsinShare: number | null;\n cartAddsCartAddRate: number | null;\n purchasesTotalCount: number | null;\n purchasesAsinCount: number | null;\n purchasesAsinShare: number | null;\n purchasesPurchaseRate: number | null;\n ctrTotal: number | null;\n ctrAsin: number | null;\n cvrTotal: number | null;\n cvrAsin: number | null;\n}\n\nexport type RankRadarSqpKeywordList = PaginationResponse<RankRadarSqpKeyword>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/ppc (RankRadarPpcKeywordListResponseDto, bare) ─\n\n/** One campaign/ad group targeting the keyword; present only with `includeCampaigns=true`. */\nexport interface RankRadarPpcCampaign {\n campaignId: number;\n campaignName: string;\n adGroupName: string;\n matchType: string | null;\n targeting: string | null;\n costPerClicks: number | null;\n totalImpressions: number | null;\n totalOrders: number | null;\n totalClicks: number | null;\n ppcSpend: number | null;\n ppcSales: number | null;\n clickThroughRate: number | null;\n conversionRate: number | null;\n acos: number | null;\n}\n\n/** PPC metrics of one keyword, aggregated over the requested range. */\nexport interface RankRadarPpcKeyword {\n id: string;\n keyword: string;\n /** Median sponsored rank over the range. */\n sponsoredRank: number | null;\n impressionRank: number | null;\n impressionRankShare: number | null;\n exactMatches: number | null;\n phraseMatches: number | null;\n broadMatches: number | null;\n autoMatches: number | null;\n organicSales: number | null;\n ppcSales: number | null;\n ppcSpend: number | null;\n costPerClicks: number | null;\n clickThroughRate: number | null;\n conversionRate: number | null;\n totalClicks: number;\n totalImpressions: number;\n totalOrders: number;\n acos: number;\n campaigns?: RankRadarPpcCampaign[];\n}\n\nexport type RankRadarPpcKeywordList = PaginationResponse<RankRadarPpcKeyword>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/ppc/campaigns ───\n// (PpcCampaignsExternalListDto, bare PaginationResponse)\n\n/** Mirrors the backend's `PpcCampaignsBaseSortBy`. */\nexport const PPC_CAMPAIGN_BASE_SORT_BY = [\n \"name\",\n \"state\",\n \"bidStrategy\",\n \"budget\",\n \"impressions\",\n \"clicks\",\n \"ctr\",\n \"cpc\",\n \"spend\",\n \"unitsSold\",\n \"sales\",\n \"acos\",\n \"cvr\",\n \"tosImpressionShare\",\n] as const;\n\n/** Mirrors the backend's `PpcCampaignsPlacementSortBy`; accepted only with placements included. */\nexport const PPC_CAMPAIGN_PLACEMENT_SORT_BY = [\n ...([\"tos\", \"ros\", \"pp\", \"off\"] as const).flatMap((p) =>\n ([\"Impressions\", \"Clicks\", \"Spend\", \"Sales\", \"UnitsSold\", \"Ctr\", \"Cpc\", \"Acos\"] as const).map(\n (m) => `${p}${m}` as const,\n ),\n ),\n \"tosBidAdjustment\",\n \"rosBidAdjustment\",\n \"ppBidAdjustment\",\n] as const;\n\n/** Mirrors the backend's `CampaignStateFilter`. */\nexport const PPC_CAMPAIGN_STATES = [\"ENABLED\", \"PAUSED\"] as const;\n\nexport interface PpcPlacementMetrics {\n /** Placement bid adjustment, percent. Null for off-Amazon, which has none. */\n bidAdjustment: number | null;\n impressions: number;\n clicks: number;\n ctr: number | null;\n cpc: number | null;\n spend: number;\n unitsSold: number;\n sales: number;\n acos: number | null;\n}\n\nexport interface PpcCampaign {\n campaignId: number;\n name: string;\n type: string | null;\n state: string;\n targetingType: string | null;\n bidStrategy: string | null;\n budget: { type: string | null; amount: number | null; currencyCode: string | null };\n impressions: number;\n clicks: number;\n ctr: number | null;\n cpc: number | null;\n cvr: number | null;\n spend: number;\n unitsSold: number;\n orders: number;\n sales: number;\n tosImpressionShare: number | null;\n acos: number | null;\n roas: number | null;\n tacos: number | null;\n asinCount: number;\n placements?: {\n topOfSearch: PpcPlacementMetrics;\n restOfSearch: PpcPlacementMetrics;\n productPage: PpcPlacementMetrics;\n offAmazon: PpcPlacementMetrics;\n homePageBidAdjustment: number | null;\n };\n}\n\nexport type PpcCampaignList = PaginationResponse<PpcCampaign>;\n\n// ─── /v1/sellers/:sellerId/marketplaces/:marketplace/asins/:asin/inventory ────\n// (InventoryByFcResponseDto, wrapped)\n\nexport interface InventoryByFcItem {\n /** Fulfillment center code. */\n fc: string;\n /** State or region code where the FC is located, when known. */\n state: string;\n /** Units of sellable inventory currently at this FC. May be 0 when stock is out. */\n availableStock: number;\n /** Share of the ASIN's total sellable inventory at this FC, as a 0..1 fraction. */\n availableStockPercentage: number;\n}\n\nexport interface InventoryByFcResponse {\n asin: string;\n sellerId: string;\n /** Marketplace enum: \"com\", \"ca\", \"co.uk\", \"com.mx\", \"in\", \"fr\", \"de\", \"es\", \"it\", \"co.jp\". */\n marketplace: string;\n /** ISO timestamp of the last live-stock refresh, which the metrics come from. Null if none in the last 30 days. */\n lastUpdatedAt: string | null;\n /** ISO timestamp of the last `distribution` refresh, usually up to 2 days behind. Null if none in the last 30 days. */\n distributionLastUpdatedAt: string | null;\n // Metrics: null when Amazon has no data, or when listed in `lockedFields`.\n fulfillableStock: number | null;\n totalStock: number | null;\n reservedStock: number | null;\n inboundStock: number | null;\n researchingStock: number | null;\n unfulfillableStock: number | null;\n daysOfStock: number | null;\n daysOfStockSource: \"AMAZON\" | \"ESTIMATE\" | null;\n daysOfStock30: number | null;\n daysOfStock90: number | null;\n inLowInventoryState: boolean | null;\n /** Fees are in `currency`. */\n estWeeklyLilf: number | null;\n estLtsfNextAssessment: number | null;\n ltsfLastMonthActual: number | null;\n agedUnits: number | null;\n oldestUnitAgeDays: number | null;\n /** ISO 4217 marketplace currency, e.g. \"USD\". */\n currency: string;\n /** Metrics the user's plan does not include; returned as null. */\n lockedFields: string[];\n lockedReason: \"PLAN_UPGRADE_REQUIRED\" | null;\n totalSellableUnits: number;\n distribution: InventoryByFcItem[];\n}\n\n// ─── /v1/alerts/indexing-issues (IndexingIssueAlertListDto, bare) ───────────\n\n/**\n * Allowed values for the `status` query param on the /v1/alerts/* endpoints.\n * `active` is the server-side default; dismissed alerts are never returned.\n */\nexport const ALERT_STATUSES = [\"active\", \"resolved\", \"all\"] as const;\nexport type AlertStatus = (typeof ALERT_STATUSES)[number];\n\nexport interface IndexingIssueAlertItem {\n id: number;\n /** The ASIN that is no longer indexed for its tracked keywords. */\n asin: string;\n title: string | null;\n imageUrl: string | null;\n /** True when the ASIN is a parent of a variation family. */\n isParent: boolean;\n sellerId: string;\n marketplace: string;\n /** ISO timestamp of when the alert was most recently surfaced. Updates each time it re-fires. */\n lastAlertedAt: string;\n /** ISO timestamp of when the alert was resolved, or null if still active. */\n resolvedAt: string | null;\n}\n\nexport type IndexingIssueAlertList = PaginationResponse<IndexingIssueAlertItem>;\n\n// ─── /v1/alerts/blind-spend (BlindSpendAlertListDto, bare) ──────────────────\n\nexport interface BlindSpendSearchTerm {\n /** The customer search term that wasted spend. */\n term: string;\n spend: number | null;\n sales: number | null;\n clicks: number | null;\n /** Conversion rate as a 0..1 fraction. */\n cvr: number | null;\n impressions: number | null;\n}\n\nexport interface BlindSpendAlertItem {\n id: number;\n asin: string | null;\n title: string | null;\n imageUrl: string | null;\n sellerId: string;\n marketplace: string;\n /** ISO timestamp of when the alert was most recently surfaced. Updates each time it re-fires. */\n lastAlertedAt: string;\n /** ISO timestamp of when the alert was resolved, or null if still active. */\n resolvedAt: string | null;\n /** Total ad spend across the unresolved wasted-spend search terms. */\n wastedSpend: number;\n totalKeywordCount: number;\n unresolvedKeywordCount: number;\n /** The unresolved wasted-spend search terms. */\n searchTerms: BlindSpendSearchTerm[];\n}\n\nexport type BlindSpendAlertList = PaginationResponse<BlindSpendAlertItem>;\n\n// ─── Billable features (shared by /v1/quota and /v1/usage) ───────────────────\n\n/**\n * Billable feature types. These are both the keys of the `quota.features` object\n * and the allowed values of the `type` filter on /v1/usage. Mirrors the backend's\n * billable-feature enum.\n */\nexport const BILLABLE_FEATURE_TYPES = [\n \"DIVED_ASINS\",\n \"PRODUCT_BRIEF_ASINS\",\n \"AI_COPYWRITER_PROMPTS\",\n \"RANK_RADAR_KEYWORDS\",\n] as const;\nexport type BillableFeatureType = (typeof BILLABLE_FEATURE_TYPES)[number];\n\n// ─── /v1/niches/:nicheId/roots (ExternalRootResponseDto[], wrapped) ─────────\n\nexport interface RootsTableItem {\n /** A word or word-combination extracted from the master keyword list. */\n root: string;\n /** Count of keywords in the master list that contain this root. */\n frequency: number;\n /** Sum of search volume across all original keywords containing this root. */\n broadSearchVolume: number;\n /** broadSearchVolume / maxBroadSearchVolume, as a 0..1 fraction. */\n broadSearchVolumeRatio: number;\n}\n\n/**\n * Keyword-roots analysis for a Niche. `keywords` and `consolidatedKeywords` are\n * the per-keyword breakdowns (kept opaque — the LLM sees the full JSON); `roots`\n * and `normalizedRoots` are the ranked root tables.\n *\n * Note: the OpenAPI spec types the endpoint's `data` as an array, but the live\n * API returns a single object (verified against api-qa, 2026-06-17). The client's\n * unwrap() strips the `{ data }` envelope, so the tool returns this object directly.\n */\nexport interface NicheRoots {\n keywords: Array<Record<string, unknown>>;\n consolidatedKeywords: Array<Record<string, unknown>>;\n roots: RootsTableItem[];\n normalizedRoots: RootsTableItem[];\n /** ISO date string of the last successful research for the niche. */\n latestResearchDate: string;\n}\n\n// ─── /v1/quota (ExternalQuotaResponseDto) ───────────────────────────────────\n\nexport interface QuotaFeature {\n /** Current usage count. Null when not applicable to the plan. */\n used: number | null;\n /** Quota capacity. Null when unlimited / not applicable. */\n capacity: number | null;\n}\n\nexport interface Quota {\n /** ISO-8601 timestamp of the next quota reset. Null when no reset is scheduled. */\n nextRefreshDate: string | null;\n /** Per-billable-feature usage and capacity. */\n features: Record<BillableFeatureType, QuotaFeature>;\n}\n\n// ─── /v1/usage (ExternalUsageLogListDto, bare PaginationResponse) ───────────\n\nexport interface UsageLogItem {\n /** Name of the user who performed the action. Null when unknown. */\n name: string | null;\n email: string;\n /** Number of tokens consumed. */\n qty: number;\n type: BillableFeatureType;\n /** Specific action performed (e.g. \"RANK_RADAR_CREATE\"). Null when not set. */\n action: string | null;\n nicheId: string | null;\n nicheName: string | null;\n rankRadarId: string | null;\n /** ISO timestamp of when the usage was recorded. */\n date: string;\n}\n\nexport type UsageLogList = PaginationResponse<UsageLogItem>;\n\n// ─── /v1/seller_profiles (SellerProfileListDto, bare PaginationResponse) ─────\n\nexport interface SellerProfileItem {\n /** Amazon seller account ID — the value other seller-scoped tools require. */\n sellerId: string;\n /** Display name of the connected seller account. */\n sellerName: string;\n /** Marketplace code, e.g. \"com\", \"co.uk\" (see SUPPORTED_MARKETPLACES). */\n marketplace: string;\n /** True when Amazon Advertising API credentials are connected for this account. */\n hasAdApi: boolean;\n /** ISO timestamp of when the account was connected to DataDive. */\n createdAt: string;\n}\n\nexport type SellerProfileList = PaginationResponse<SellerProfileItem>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/catalog ──────────\n// (CatalogAsinListDto, bare PaginationResponse)\n\n/** Allowed values for the `status` query param on the seller catalog endpoint. */\nexport const CATALOG_STATUSES = [\"Active\", \"all\"] as const;\nexport type CatalogStatus = (typeof CATALOG_STATUSES)[number];\n\nexport interface CatalogAsinItem {\n asin: string;\n title: string;\n /** Parent ASIN for a variation child, or null for standalone/parent ASINs. */\n parentAsin: string | null;\n brand: string | null;\n /** Listing status, e.g. \"Active\". */\n status: string;\n imageUrl: string | null;\n /** True when the ASIN has variation children. Null when unknown. */\n hasVariations: boolean | null;\n}\n\nexport type SellerCatalogList = PaginationResponse<CatalogAsinItem>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/listing-changes ──\n// (ListingChangeExternalListDto, bare PaginationResponse)\n\n/** Allowed values for the `types` query param on the listing-changes endpoint. */\nexport const LISTING_CHANGE_TYPES = [\"Price\", \"Content\", \"Image\"] as const;\nexport type ListingChangeType = (typeof LISTING_CHANGE_TYPES)[number];\n\n/** Allowed values for the `sortBy` query param on the listing-changes endpoint. */\nexport const LISTING_CHANGE_SORT_BY = [\"date\", \"type\"] as const;\nexport type ListingChangeSortBy = (typeof LISTING_CHANGE_SORT_BY)[number];\n\n/** Generic sort direction, shared where a `sortOrder` query param is accepted. */\nexport const SORT_ORDER = [\"ASC\", \"DESC\"] as const;\nexport type SortOrder = (typeof SORT_ORDER)[number];\n\n/**\n * Optional ranking/conversion correlation for a listing change, present only when\n * `includeCorrelations=true`. Sub-objects (salesCvr, top10/top50SearchTerms) carry\n * before/after figures and are kept loosely typed per this file's convention.\n */\nexport interface ListingChangeCorrelation {\n /** \"PENDING\" | \"AVAILABLE\" | \"UNAVAILABLE_NO_ACTIVE_RANK_RADAR\" | \"UNAVAILABLE\". */\n status: string;\n message: string | null;\n salesCvr: Record<string, unknown> | null;\n top10SearchTerms: Record<string, unknown> | null;\n top50SearchTerms: Record<string, unknown> | null;\n}\n\nexport interface ListingChangeItem {\n asin: string;\n title: string | null;\n imageUrl: string | null;\n /** ISO timestamp of when the change was detected. */\n date: string;\n /** \"Price\" | \"Content\" | \"Image\". */\n type: string;\n /** Sub-type for content changes (e.g. title/bullets/description); null otherwise. */\n contentType: string | null;\n description: string;\n /** Prior value of the changed field. Shape varies by change type; kept opaque. */\n previousValue: Record<string, unknown> | null;\n /** New value of the changed field. Shape varies by change type; kept opaque. */\n newValue: Record<string, unknown> | null;\n /** Present only when `includeCorrelations=true`; null otherwise. */\n correlation: ListingChangeCorrelation | null;\n}\n\nexport type ListingChangeList = PaginationResponse<ListingChangeItem>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/search-terms (ExternalAddSearchTermsResponseDto, wrapped) ──\n\n/**\n * Where each submitted search term ended up. `manual` and `suggestions` are terms the\n * niche had never seen; the rest were already known to the niche in that role.\n * Mirrors the backend's SearchTermOriginBreakdown.\n */\nexport interface SearchTermOriginBreakdown {\n tracked: string[];\n paused: string[];\n outliers: string[];\n residues: string[];\n ppc: string[];\n /** Terms the niche has never seen, typed in by the user. */\n manual: string[];\n /** Terms the niche has never seen, accepted from the Rank Radar keyword suggestions. */\n suggestions: string[];\n}\n\nexport interface AddSearchTermsResult {\n originBreakdown: SearchTermOriginBreakdown;\n /** search term -> rankRadarKeywordId, the id the archive/resume search-term tools take. */\n keywordToRankRadarKeywordIdMap: Record<string, string>;\n}\n\n/** Backend cap on how many search terms one add call accepts (ADD_SEARCH_TERM_MAX_LIMIT). */\nexport const ADD_SEARCH_TERMS_MAX = 1000;\n\n// ─── /v1/niches/:nicheId/ai-copywriter (AI Copywriter, wrapped) ─────────────\n\n/**\n * Generation strategies the AI Copywriter accepts. Mirrors the backend's\n * GenerateAiCopywriterPromptOption enum.\n */\nexport const LISTING_COPY_STRATEGIES = [\"cosmo\", \"ranking-juice\", \"nlp\", \"cosmo-rufus\"] as const;\nexport type ListingCopyStrategy = (typeof LISTING_COPY_STRATEGIES)[number];\n\n/** Returned immediately when a generation is enqueued. */\nexport interface CreateListingCopyResult {\n generationId: string;\n status: \"generating\";\n}\n\nexport interface ListingCopyResult {\n title: string;\n bullets: string[];\n description: string;\n itemHighlights: string;\n /** Ranking Juice metrics for the generated copy (value + per-field contribution). */\n rankingJuice: Record<string, unknown>;\n methodology_breakdown?: unknown;\n}\n\n/** Polled status of a generation. Discriminated on `status`, like DiveStatus. */\nexport type ListingCopyGenerationStatus =\n | { generationId: string; status: \"generating\" }\n | { generationId: string; status: \"complete\"; result: ListingCopyResult }\n | { generationId: string; status: \"failed\"; error: string };\n","/**\n * Shared inputs for the Rank Radar keyword tools (`get_rank_radar_data`, `get_rank_radar_sqp_data`,\n * `get_rank_radar_ppc_data`). All three API routes take the same date range and keyword paging\n * (RS-11683), so the fields are defined once here and spread into each tool's inputSchema,\n * mirroring seller-scoped.ts.\n */\n\nimport { z } from \"zod\";\n\n/** yyyy-mm-dd matcher used by both startDate and endDate. */\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Mirrors the API's `RANK_RADAR_KEYWORD_LIST_MAX_PAGE_SIZE`. A larger value is not rejected there:\n * it silently falls back to the default page of 20, so the cap has to be enforced here.\n */\nexport const RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;\n\n/** Mirrors the API's `MAX_DATE_RANGE_DAYS`; a longer range is refused with a 400. */\nexport const RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;\n\nexport const rankRadarKeywordQueryInputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID (from `list_rank_radars`).\"),\n startDate: z\n .string()\n .regex(ISO_DATE, \"startDate must be in yyyy-mm-dd format\")\n .describe(\"Start date of the range, yyyy-mm-dd (e.g. 2024-03-26).\"),\n endDate: z\n .string()\n .regex(ISO_DATE, \"endDate must be in yyyy-mm-dd format\")\n .describe(\n \"End date of the range, yyyy-mm-dd (e.g. 2024-04-26). Must be on or after startDate, \" +\n `and at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days after it.`,\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page of keywords, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Keywords per page (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\n/** Description sentence shared by the three tools: how to walk the pages. */\nexport const RANK_RADAR_KEYWORD_PAGING_NOTE =\n \"Results are paged by keyword: `data` holds one page, and the response carries currentPage, pageSize, \" +\n \"total, lastPage, hasNext and hasPrev. `total` is the Rank Radar's active keyword count; to read every \" +\n \"keyword, call again with `currentPage` + 1 while `hasNext` is true, using \" +\n `\\`pageSize\\` ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE} for large Rank Radars. Paused keywords are not included.`;\n\nexport function rankRadarPath(rankRadarId: string, suffix = \"\"): string {\n return `/v1/niches/rank-radars/${encodeURIComponent(rankRadarId)}${suffix ? `/${suffix}` : \"\"}`;\n}\n\n/** Query params common to the three routes; `undefined` values are dropped by the HTTP client. */\nexport function rankRadarKeywordQuery(args: {\n startDate: string;\n endDate: string;\n currentPage?: number;\n pageSize?: number;\n}) {\n return {\n startDate: args.startDate,\n endDate: args.endDate,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n };\n}\n","import { httpGet } from \"../http/client.js\";\nimport type { KrtKeyword, RankRadarKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { HandlerContext, ToolDefinition } from \"./types.js\";\n\nexport {\n RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n} from \"./rank-radar-keyword-query.js\";\n\nconst inputSchema = rankRadarKeywordQueryInputSchema;\n\ntype DateRange = { rankRadarId: string; startDate: string; endDate: string };\n\n/**\n * Reads every active keyword of a Rank Radar, one bounded API page at a time (RS-11791).\n *\n * This is what a call without `currentPage` / `pageSize` gets. Those inputs arrived after the API\n * started paging (RS-11683), and MCP clients keep the tool list they fetched when they connected —\n * ChatGPT, claude.ai and Claude Code were all seen still offering the three original inputs days\n * later, with no protocol message that makes them reload it. Such a client can never ask for page 2,\n * so without this it would silently see only the API's default first 20 keywords. Reading the pages\n * here keeps the tool's original contract — every keyword — for those clients, while each request\n * stays a normal, bounded page for the backend.\n *\n * Pages are read in order, not in parallel, so one tool call never puts more than one keyword page\n * on the backend at a time.\n */\nasync function readAllKeywords(args: DateRange, ctx: HandlerContext): Promise<RankRadarKeywordList> {\n const request = { config: ctx.config, toolName: \"get_rank_radar_data\" };\n const path = rankRadarPath(args.rankRadarId);\n const keywords: KrtKeyword[] = [];\n let page: RankRadarKeywordList;\n let currentPage = 1;\n\n do {\n page = await httpGet<RankRadarKeywordList>(\n request,\n path,\n rankRadarKeywordQuery({ ...args, currentPage, pageSize: RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE }),\n );\n keywords.push(...(page.data ?? []));\n currentPage++;\n // `lastPage` and an empty page both end the walk, so an API that stopped honouring\n // `currentPage` could not turn this into an endless loop.\n } while (page.hasNext && currentPage <= page.lastPage && (page.data?.length ?? 0) > 0);\n\n return {\n data: keywords,\n currentPage: 1,\n pageSize: keywords.length,\n total: page.total,\n lastPage: 1,\n hasNext: false,\n hasPrev: false,\n };\n}\n\nexport const getRankRadarDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_data\",\n title: \"Get Keyword Rankings for a Rank Radar\",\n description:\n \"Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), \" +\n `at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. ` +\n \"Retrieves historical keyword ranking data for the specified Rank Radar within the date range. \" +\n \"Without `currentPage` and `pageSize` it returns every active keyword in one result. To read one page \" +\n `instead — faster for large Rank Radars — pass \\`currentPage\\` and/or \\`pageSize\\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) ` +\n \"and continue with `currentPage` + 1 while `hasNext` is true. The response carries currentPage, pageSize, \" +\n \"total, lastPage, hasNext and hasPrev; `total` is the Rank Radar's active keyword count. Paused keywords \" +\n \"are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day \" +\n \"{ date, organicRank, sponsoredRank, impressionRank }), and `highlights` — the user tags on this \" +\n \"keyword. Each `highlights` entry is the same object `list_rank_radar_tags` returns (id, title, \" +\n \"color, description, startAt, endAt, type, krtId, krtKeywordId); its `id` is the `tagId` the \" +\n \"tag-management tools take. A rank of 101 means the product was not in the top 100 results that \" +\n \"day. For PPC metrics use `get_rank_radar_ppc_data`; for Search Query Performance use \" +\n \"`get_rank_radar_sqp_data`. Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n if (args.currentPage === undefined && args.pageSize === undefined) {\n return await readAllKeywords(args, ctx);\n }\n\n return await httpGet<RankRadarKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_data\" },\n rankRadarPath(args.rankRadarId),\n rankRadarKeywordQuery(args),\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { RankRadarSqpKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORD_PAGING_NOTE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = rankRadarKeywordQueryInputSchema;\n\nexport const getRankRadarSqpDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_sqp_data\",\n title: \"Get Search Query Performance for a Rank Radar\",\n description:\n \"Use this when the user asks how shoppers search, click, add to cart and buy for the keywords of a Rank \" +\n \"Radar — Amazon's Search Query Performance (SQP) data, from Brand Analytics. Requires startDate and \" +\n `endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it ` +\n \"fast. Each keyword's metrics are aggregated over the range: searchQueryVolume and searchQueryScore; \" +\n \"impressions, clicks, cart adds and purchases, each as the total across all sellers (`*TotalCount`), \" +\n \"this ASIN family's count (`*AsinCount`) and its share (`*AsinShare`); click, cart-add and purchase \" +\n \"rates; and ctr/cvr for the market (`*Total`) and for this ASIN family (`*Asin`). `numberOfDaysWithData` \" +\n \"says how many days of the range had SQP data; metrics are null when there is none, which is normal \" +\n \"for low-volume keywords, recent dates (Amazon publishes SQP with a delay) and sellers without Brand \" +\n `Analytics. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited ` +\n \"(about 60 requests/minute). Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<RankRadarSqpKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_sqp_data\" },\n rankRadarPath(args.rankRadarId, \"sqp\"),\n rankRadarKeywordQuery(args),\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { RankRadarPpcKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORD_PAGING_NOTE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...rankRadarKeywordQueryInputSchema,\n includeCampaigns: z\n .boolean()\n .optional()\n .describe(\n \"When true, each keyword also carries `campaigns`: the per-campaign/ad-group breakdown (campaign and \" +\n \"ad group name, match type, targeting, spend, sales, clicks, impressions, orders, CPC, CTR, CVR, ACOS). \" +\n \"Defaults to false; leave it off unless the user asks which campaigns drive a keyword.\",\n ),\n};\n\nexport const getRankRadarPpcDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_ppc_data\",\n title: \"Get PPC Data for a Rank Radar\",\n description:\n \"Use this when the user asks about advertising (Sponsored Products) performance for the keywords of a \" +\n \"Rank Radar. Requires startDate and endDate (yyyy-mm-dd), at most \" +\n `${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are ` +\n \"aggregated over the range: sponsoredRank (median; 101 means not in the top 100 sponsored results), \" +\n \"impressionRank and impressionRankShare; how many exact, phrase, broad and auto campaigns target it; \" +\n \"organicSales, ppcSales, ppcSpend, costPerClicks, clickThroughRate, conversionRate, totalClicks, \" +\n \"totalImpressions, totalOrders and acos. Metrics are null or 0 when the seller runs no ads on a keyword. Set \" +\n `\\`includeCampaigns\\` for the per-campaign breakdown. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts ` +\n \"toward API usage and is rate limited (about 60 requests/minute). For the seller's campaigns as a \" +\n \"whole, use `list_ppc_campaigns`. Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<RankRadarPpcKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_ppc_data\" },\n rankRadarPath(args.rankRadarId, \"ppc\"),\n { ...rankRadarKeywordQuery(args), includeCampaigns: args.includeCampaigns },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { CreateRankRadarResult } from \"../types/api.js\";\nimport { confirmationRequired, needsConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst PATH = \"/v1/niches/rank-radars\";\n\nconst inputSchema = {\n asin: z.string().min(1).describe(\"The ASIN to track keyword rankings for.\"),\n numberOfKeywords: z\n .number()\n .int()\n .min(1)\n .describe(\"How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens.\"),\n nicheId: z\n .string()\n .min(1)\n .describe(\"The niche this Rank Radar belongs to. Get one from `list_niches`.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first.\",\n ),\n};\n\nexport const createRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_rank_radar\",\n title: \"Create a Rank Radar\",\n description:\n \"Use this to start tracking organic and sponsored keyword rankings for an ASIN within a niche. \" +\n \"⚠️ Spends Search Term tokens (cost scales with `numberOfKeywords`) and cannot be undone — set \" +\n \"`confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again \" +\n \"and creates a separate Rank Radar — if a call errors or times out, check `list_rank_radars` instead of \" +\n \"re-calling. The first call previews the creation and may return `warnings` alongside the cost note — \" +\n \"for example that this product family is already tracked by another Rank Radar. Show every warning \" +\n \"`message` to the user verbatim, before asking them to confirm; do not summarise it or decide on their \" +\n \"behalf. A warning never blocks creation. Returns the new `rankRadarId`; read its data \" +\n \"later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.\",\n inputSchema,\n // destructiveHint is technically \"additive\" here (this creates a new Rank Radar, it\n // doesn't destroy data), but we flag it destructive so clients prompt before the\n // irreversible token spend. The confirm gate enforces this server-side regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const request = { config: ctx.config, toolName: \"create_rank_radar\" };\n const body = {\n asin: args.asin,\n numberOfKeywords: args.numberOfKeywords,\n nicheId: args.nicheId,\n };\n\n if (needsConfirmation(args.confirm, ctx)) {\n // RS-11615. `dryRun` runs the API's full validation, quota check and duplicate check\n // and creates nothing, so the confirmation payload can carry the API's own warnings —\n // \"this family is already tracked\", say — instead of only a static cost note. The\n // wording therefore comes from the same endpoint on both passes as the one the DataDive\n // UI shows, and cannot drift from it.\n //\n // `dryRun` is deliberately absent from inputSchema and set only here. If the model could\n // set it, it could dry-run, read a success-shaped response, and tell the user their Rank\n // Radar was created when nothing was created and nothing was spent.\n const preview = await httpPost<CreateRankRadarResult>(request, PATH, { ...body, dryRun: true });\n\n // A backend older than RS-11615 ignores the unknown `dryRun` field and creates the Rank\n // Radar for real. It gives itself away by returning an id where a dry run returns null.\n // Report what actually happened rather than claiming a confirmation is still pending for\n // something already paid for.\n if (preview.rankRadarId != null) return preview;\n\n return confirmationRequired(\n `Creating this Rank Radar consumes Search Term tokens — one per keyword tracked (${args.numberOfKeywords} requested).`,\n preview.warnings,\n );\n }\n\n // Confirmed, or the gate is opted out via DATADIVE_AUTO_CONFIRM_WRITES. On that opt-out\n // path there is no confirmation step at all, so the `warnings` in this response are the\n // only ones the user will ever see — which is why they are returned, not dropped.\n return await httpPost<CreateRankRadarResult>(request, PATH, body);\n },\n};\n","/**\n * Shared confirm gate for token-spending write tools (create_niche_dive,\n * create_rank_radar).\n *\n * Creating a Dive or Rank Radar consumes billable tokens irreversibly, so these\n * tools require an explicit `confirm: true` before they hit the API. When the flag\n * is missing, requireConfirmation returns a structured (non-error) payload telling\n * the model to confirm the token spend with the user and re-call with `confirm: true`;\n * the handler returns that payload early WITHOUT calling the API.\n *\n * The user can opt out of the gate entirely by setting DATADIVE_AUTO_CONFIRM_WRITES\n * (see config.ts) — a persistent \"don't ask me again\".\n */\n\nimport type { ApiWarning } from \"../types/api.js\";\nimport type { HandlerContext } from \"./types.js\";\n\nexport interface ConfirmationRequired {\n status: \"confirmation_required\";\n message: string;\n /** Human-readable note on what this action will cost in tokens. */\n costNote: string;\n /**\n * Non-blocking notices the API reported for this exact request, obtained from a\n * dry run (RS-11615). Absent when the tool has no dry-run preview or the API\n * flagged nothing. Each `message` is customer-facing text to be shown verbatim.\n */\n warnings?: ApiWarning[];\n}\n\n/**\n * Whether the gate applies — whether the tool must stop and ask before spending\n * anything. Split out from requireConfirmation so a tool can run a read-only preview\n * first and fold its result into the payload; see create_rank_radar's dry run.\n */\nexport function needsConfirmation(confirm: boolean | undefined, ctx: HandlerContext): boolean {\n return !(confirm === true || ctx.config.autoConfirmWrites);\n}\n\n/** The gate payload. `warnings` is omitted entirely when there are none, to keep the JSON quiet. */\nexport function confirmationRequired(costNote: string, warnings?: ApiWarning[]): ConfirmationRequired {\n return {\n status: \"confirmation_required\",\n message:\n \"This action spends billable tokens and cannot be undone. Confirm the cost with \" +\n \"the user, then call again with `confirm: true` to proceed.\",\n costNote,\n ...(warnings && warnings.length > 0 ? { warnings } : {}),\n };\n}\n\n/**\n * Returns a ConfirmationRequired payload if the action is not yet confirmed, or null\n * if the handler should proceed (confirm:true was passed, or the opt-out is set).\n */\nexport function requireConfirmation(\n confirm: boolean | undefined,\n ctx: HandlerContext,\n costNote: string,\n): ConfirmationRequired | null {\n if (!needsConfirmation(confirm, ctx)) return null;\n return confirmationRequired(costNote);\n}\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type CreateNicheDiveResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe(\n \"Amazon marketplace of the seed ASIN. Note these are full domain suffixes: com, ca, \" +\n \"co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`).\",\n ),\n asin: z\n .string()\n .min(1)\n .describe(\"The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it.\"),\n numberOfCompetitors: z\n .number()\n .int()\n .min(2)\n .describe(\"How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\"Must be true to proceed — creating a dive spends dive tokens. Confirm the cost with the user first.\"),\n};\n\nexport const createNicheDiveTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_niche_dive\",\n title: \"Create a Niche Dive\",\n description:\n \"Use this to start new niche research from a seed ASIN. ⚠️ Spends dive tokens (cost scales with \" +\n \"`numberOfCompetitors`) and cannot be undone — set `confirm: true` only after the user approves the cost. \" +\n \"Not safe to retry: each call spends tokens again and starts a separate dive — if a call errors or times \" +\n \"out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. \" +\n \"The dive runs asynchronously: this returns immediately with a `diveId` and an `estimatedCompletionDate`. \" +\n \"Poll `get_dive_status` with that `diveId` until it reports `success` (which carries the new `nicheId` for \" +\n \"use with `list_niches`, `get_niche_keywords`, etc.). Requires `marketplace`, `asin`, and `numberOfCompetitors` (min 2).\",\n inputSchema,\n // destructiveHint is technically \"additive\" here (a dive creates a new niche, it\n // doesn't destroy data), but we flag it destructive so clients prompt before the\n // irreversible token spend. The confirm gate enforces this server-side regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n `Creating this dive consumes dive tokens — one batch per competitor analyzed (${args.numberOfCompetitors} requested).`,\n );\n if (pending) return pending;\n\n return await httpPost<CreateNicheDiveResult>(\n { config: ctx.config, toolName: \"create_niche_dive\" },\n \"/v1/niches/dives\",\n {\n marketplace: args.marketplace,\n asin: args.asin,\n numberOfCompetitors: args.numberOfCompetitors,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type CreateNicheDiveResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `MIN_REQUIRED_ASINS_FOR_DIVE` and `MAX_ASINS_PER_DIVE_WITH_COMPETITORS`. */\nexport const DIVE_FROM_COMPETITORS_MIN_ASINS = 2;\nexport const DIVE_FROM_COMPETITORS_MAX_ASINS = 200;\n\n/**\n * The API upper-cases and trims each ASIN before its uniqueness check, so \"b0abc\" and \"B0ABC\" are\n * the same competitor there. Normalising here makes the duplicate check below agree with it, and\n * the cost note count the ASINs the API will actually charge for.\n */\nconst normalizedAsins = z\n .array(z.string().trim().min(1).toUpperCase())\n .min(DIVE_FROM_COMPETITORS_MIN_ASINS)\n .max(DIVE_FROM_COMPETITORS_MAX_ASINS)\n .refine((asins) => new Set(asins).size === asins.length, {\n message: \"asins must not contain the same ASIN twice\",\n });\n\nconst inputSchema = {\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe(\n \"Amazon marketplace where the ASINs are listed. Full domain suffixes: com, ca, co.uk, com.mx, in, \" +\n \"fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`).\",\n ),\n asins: normalizedAsins.describe(\n `The exact competitor ASINs to build the niche from, ${DIVE_FROM_COMPETITORS_MIN_ASINS} to ` +\n `${DIVE_FROM_COMPETITORS_MAX_ASINS}, each once. No other competitors are added.`,\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — this dive spends dive tokens for every ASIN. Confirm the cost with the user first.\",\n ),\n};\n\nexport const createNicheDiveFromCompetitorsListTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_niche_dive_from_competitors_list\",\n title: \"Create a Niche Dive from a Competitor List\",\n description:\n \"Use this when the user already knows which products belong in the niche — e.g. they list competitor \" +\n \"ASINs, or picked them from search results — and wants niche research on exactly those. Unlike \" +\n \"`create_niche_dive`, which discovers competitors around one seed ASIN, this builds the niche from the \" +\n `given ASINs only (${DIVE_FROM_COMPETITORS_MIN_ASINS}-${DIVE_FROM_COMPETITORS_MAX_ASINS}). ⚠️ Spends dive ` +\n \"tokens for every ASIN and cannot be undone — set `confirm: true` only after the user approves the cost. \" +\n \"Not safe to retry: each call spends tokens again and starts a separate dive — if a call errors or times \" +\n \"out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. Fails without spending \" +\n \"tokens when the ASIN count is over the plan's limit, when the account is at its niche limit, or when \" +\n \"Amazon product data cannot be found for enough of the ASINs. The dive runs asynchronously: this \" +\n \"returns a `diveId` and an `estimatedCompletionDate`; poll `get_dive_status` with the `diveId` until it \" +\n \"reports `success`, which carries the new `nicheId`.\",\n inputSchema,\n // Additive rather than destructive, but flagged destructive so clients prompt before the\n // irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n `Creating this dive consumes dive tokens — one batch per ASIN analyzed (${args.asins.length} ASINs given).`,\n );\n if (pending) return pending;\n\n return await httpPost<CreateNicheDiveResult>(\n { config: ctx.config, toolName: \"create_niche_dive_from_competitors_list\" },\n \"/v1/niches/dive_with_competitors\",\n { marketplace: args.marketplace, asins: args.asins },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport type { RediveNicheResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to re-dive. Get one from `list_niches`.\"),\n mode: z\n .enum([\"same_competitors\", \"discover\"])\n .describe(\n \"How to pick the competitors for the refreshed dive. `same_competitors` re-runs the niche's \" +\n \"current competitor set and takes no other argument — use it to refresh stale data. `discover` \" +\n \"searches for a fresh competitor set, which is what you want when the niche has changed.\",\n ),\n numberOfCompetitors: z\n .number()\n .int()\n .min(2)\n .optional()\n .describe(\n \"Total ASINs the refreshed dive should contain, including `lockedAsins`. Required in `discover` \" +\n \"mode and rejected in `same_competitors` mode. Dive tokens are spent per ASIN.\",\n ),\n heroAsin: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"`discover` mode only. Seed product the competitor discovery starts from. Defaults to the niche's \" +\n \"highest-selling competitor, preferring one the user's own connected seller account owns.\",\n ),\n lockedAsins: z\n .array(z.string().min(1))\n .optional()\n .describe(\n \"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery \" +\n \"finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`.\",\n ),\n excludedAsins: z\n .array(z.string().min(1))\n .optional()\n .describe(\"`discover` mode only. ASINs discovery must never select.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — a re-dive spends dive tokens. Confirm the cost with the user first.\",\n ),\n};\n\nexport const rediveNicheTool: ToolDefinition<typeof inputSchema> = {\n name: \"redive_niche\",\n title: \"Re-dive an Existing Niche\",\n description:\n \"Use this to refresh an existing niche's research with current Amazon data, instead of creating a new \" +\n \"niche with `create_niche_dive`. ⚠️ Spends dive tokens (one batch per ASIN dived) and cannot be undone — \" +\n \"set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens \" +\n \"again and starts a separate re-dive — if a call errors or times out, poll `get_dive_status` instead of \" +\n \"re-calling. Two modes: `same_competitors` re-dives the \" +\n \"niche's current competitor set (no other argument needed) and `discover` finds a fresh set, sized by \" +\n \"`numberOfCompetitors` and steerable with `heroAsin` / `lockedAsins` / `excludedAsins`. Runs \" +\n \"asynchronously: returns a `diveId` and an `estimatedCompletionDate` — poll `get_dive_status` with that \" +\n \"`diveId` until it reports `success`. The niche keeps its `nicheId`, so existing rank radars and reports \" +\n \"follow the refreshed data.\",\n inputSchema,\n // Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the\n // irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // `numberOfCompetitors` cannot be required in the schema (same_competitors mode forbids it), so\n // check it here — before the confirm gate, because a gate that cannot state the cost is worse\n // than no gate. Everything else is left to the API, which names the offending field.\n if (args.mode === \"discover\" && args.numberOfCompetitors === undefined) {\n throw new ApiError(\n \"bad_request\",\n 400,\n \"numberOfCompetitors is required in `discover` mode — it is how many ASINs the refreshed dive \" +\n \"will contain, and therefore what it costs. Ask the user how many competitors they want \" +\n '(minimum 2), or use mode \"same_competitors\" to re-dive the niche\\'s current set.',\n );\n }\n\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n args.mode === \"discover\"\n ? `Re-diving this niche consumes dive tokens — one batch per ASIN analyzed (${args.numberOfCompetitors} requested).`\n : \"Re-diving this niche consumes dive tokens — one batch per competitor currently in the niche. \" +\n \"Use `get_niche_competitors` first if the user needs the exact count.\",\n );\n if (pending) return pending;\n\n // Mode-specific fields are forwarded as given rather than filtered by mode: the API rejects a\n // field that does not belong to the requested mode with a message naming it, which is a clearer\n // correction for the model than silently dropping what it asked for. `undefined` fields are\n // dropped by JSON.stringify, so an unused optional never reaches the wire.\n return await httpPost<RediveNicheResult>(\n { config: ctx.config, toolName: \"redive_niche\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/redive`,\n {\n mode: args.mode,\n numberOfCompetitors: args.numberOfCompetitors,\n heroAsin: args.heroAsin,\n lockedAsins: args.lockedAsins,\n excludedAsins: args.excludedAsins,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { DiveStatus } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n diveId: z\n .string()\n .min(1)\n .describe(\n \"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or \" +\n \"`redive_niche`, or the `refresh.diveId` returned by `add_niche_keywords`.\",\n ),\n};\n\nexport const getDiveStatusTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_dive_status\",\n title: \"Get Niche Dive Status\",\n description:\n \"Use this to poll a niche dive started with `create_niche_dive`, `create_niche_dive_from_competitors_list` or \" +\n \"`redive_niche`, or the re-dive `add_niche_keywords` started on a stale niche, until it finishes. \" +\n \"Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), \" +\n \"`success` (with the `nicheId` plus `tokensUsed`/`tokensLeft`), or `error` (with an `error` message). \" +\n \"On `success`, use the `nicheId` with `list_niches`, `get_niche_keywords`, `get_niche_competitors`, etc. \" +\n \"(for a re-dive it is the same niche you passed in, now carrying refreshed data). \" +\n \"Re-poll periodically — dives can take minutes; the `estimatedCompletionDate` hints at when to check.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<DiveStatus>(\n { config: ctx.config, toolName: \"get_dive_status\" },\n `/v1/niches/dives/${encodeURIComponent(args.diveId)}`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport {\n NICHE_KEYWORDS_MAX_PER_CALL,\n type AddNicheKeywordsResult,\n type MaxTokensExceededBody,\n} from \"../types/api.js\";\nimport { confirmationRequired, needsConfirmation, type ConfirmationRequired } from \"./confirm.js\";\nimport { nicheKeywordList } from \"./niche-keyword-list.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche whose Master Keyword List to add to. Get one from `list_niches`.\"),\n keywords: nicheKeywordList.describe(\n `The keywords to add, 1–${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are ` +\n \"lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool \" +\n \"allows only 2 calls per minute per niche.\",\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Leave unset on the first call. Set to true only after the user approved the Dive token cost from a \" +\n \"`confirmation_required` response — it lets the add re-dive a stale niche.\",\n ),\n maxTokensUsed: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\n \"With `confirm: true`: the `tokenCost` the user approved. The call then fails with nothing saved or \" +\n \"charged rather than spend more. Ignored without `confirm`, where the tool never spends anything.\",\n ),\n};\n\n/** The gate payload for a stale niche, carrying the API's own figure for the re-dive. */\nexport interface AddNicheKeywordsConfirmation extends ConfirmationRequired {\n /** The most the re-dive can cost, in Dive tokens: the niche's competitor count. */\n tokenCost: number;\n}\n\nfunction isMaxTokensExceededBody(body: unknown): body is MaxTokensExceededBody {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return b.error === \"MAX_TOKENS_EXCEEDED\" && typeof b.tokensUsed === \"number\";\n}\n\nexport const addNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_niche_keywords\",\n title: \"Add Keywords to a Niche's Master Keyword List\",\n description:\n \"Use this to add keywords to a niche's Master Keyword List (MKL): the user's own search terms, or \" +\n \"keywords from the niche's Outliers or Residue lists. Free while the niche is fresh (dived in the last \" +\n \"24 hours, or a dive is running): the first call adds the keywords at once and they are readable with \" +\n \"`get_niche_keywords`. On a stale niche, any keyword new to the niche makes the add re-dive the whole \" +\n \"niche with its current competitors, which spends one Dive token per competitor. The first call then \" +\n \"saves and spends nothing and returns `confirmation_required` with `tokenCost`; after the user approves, \" +\n \"call again with `confirm: true` and `maxTokensUsed` set to that `tokenCost`. Keywords moved in from \" +\n \"Outliers or Residue never cost anything. Returns `originBreakdown` (`alreadyInMkl`, `takenFromOutliers`, \" +\n \"`takenFromResidue`, `addedManually`) and `tokensUsed`. When `refresh` is present a re-dive is running: \" +\n \"poll `get_dive_status` with `refresh.diveId`; the `addedManually` keywords show in the MKL once it \" +\n \"succeeds. A confirmed call is not safe to retry: if it errors or times out, poll `get_dive_status` or \" +\n \"re-read `get_niche_keywords` instead of re-calling. Limited to 2 calls per minute per niche, so batch \" +\n \"keywords into one call. Undo with `remove_niche_keywords`. A keyword hidden by the niche's keyword \" +\n \"exclusions stays hidden after an add: check `list_keyword_exclusions` and remove the exclusion with \" +\n \"`remove_keyword_exclusions` first.\",\n inputSchema,\n // Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients\n // need to prompt on. The confirm gate covers the paid path regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx): Promise<AddNicheKeywordsResult | AddNicheKeywordsConfirmation> => {\n const request = { config: ctx.config, toolName: \"add_niche_keywords\" };\n const path = `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`;\n\n if (needsConfirmation(args.confirm, ctx)) {\n // A real call capped at 0 tokens, not a dry run followed by a real call: a fresh niche is free,\n // so this one request does the add, while a stale one is refused with the re-dive's cost and\n // nothing saved. A dry run would spend half the route's 2-per-minute allowance on every free add.\n // `maxTokensUsed` is set only here and on the confirmed path, never taken from an unconfirmed\n // caller, so an unconfirmed call can never spend.\n try {\n return await httpPost<AddNicheKeywordsResult>(request, path, {\n keywords: args.keywords,\n maxTokensUsed: 0,\n });\n } catch (err) {\n if (err instanceof ApiError && isMaxTokensExceededBody(err.body)) {\n const tokenCost = err.body.tokensUsed;\n return {\n ...confirmationRequired(\n `This niche was last dived more than 24 hours ago, so adding keywords that are new to it ` +\n `re-dives the whole niche with its current competitors: up to ${tokenCost} Dive tokens, one ` +\n \"per competitor (less if some can no longer be found). Nothing was added or charged yet.\",\n ),\n tokenCost,\n };\n }\n throw err;\n }\n }\n\n // Confirmed, or the gate is opted out via DATADIVE_AUTO_CONFIRM_WRITES. `maxTokensUsed` left unset\n // means no cap; JSON.stringify drops it.\n return await httpPost<AddNicheKeywordsResult>(request, path, {\n keywords: args.keywords,\n maxTokensUsed: args.maxTokensUsed,\n });\n },\n};\n","/**\n * Keyword-list input shared by the niche keyword write tools (add/remove MKL keywords).\n *\n * The API lower-cases and trims every keyword before its own uniqueness check, so \"Dog Hat\" and\n * \"dog hat \" are the same keyword there and sending both is a 400. Normalising here makes the\n * duplicate check agree with the API and turns that mistake into a schema error the model sees\n * before any call, which matters because these routes allow only 2 writes per minute per niche.\n */\n\nimport { z } from \"zod\";\nimport { NICHE_KEYWORD_MAX_LENGTH, NICHE_KEYWORDS_MAX_PER_CALL } from \"../types/api.js\";\n\n/**\n * A fresh schema per use: one instance shared by two fields is emitted as a `$ref` to the first in the\n * tool's JSON Schema, which some MCP clients do not resolve.\n */\nexport const nicheKeyword = () => z.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);\n\n/** Rejects a list holding the same keyword twice, compared after normalisation. */\nexport function uniqueKeywords<T extends z.ZodType<string[]>>(schema: T, field: string) {\n return schema.refine((keywords) => new Set(keywords).size === keywords.length, {\n message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`,\n });\n}\n\nexport const nicheKeywordList = uniqueKeywords(\n z.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),\n \"keywords\",\n);\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { NICHE_KEYWORDS_MAX_PER_CALL, type RemoveNicheKeywordsResult } from \"../types/api.js\";\nimport { nicheKeywordList } from \"./niche-keyword-list.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche whose Master Keyword List to remove from. Get one from `list_niches`.\"),\n keywords: nicheKeywordList.describe(\n `The keywords to take out, 1–${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They ` +\n \"are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool \" +\n \"allows only 2 calls per minute per niche.\",\n ),\n};\n\nexport const removeNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_niche_keywords\",\n title: \"Remove Keywords from a Niche's Master Keyword List\",\n description:\n \"Use this to undo `add_niche_keywords`: take keywords back out of a niche's Master Keyword List (MKL). \" +\n \"Only keywords that were added to the MKL can be removed — `get_niche_keywords` shows each keyword's \" +\n \"`origin`. What happens depends on that origin, and the response reports it per keyword: \" +\n \"`removedManual` — added by hand (`manual`), deleted from the niche; `sentBackToOutliers` / \" +\n \"`sentBackToResidue` — moved in from those lists (`outlier` / `residue`), listed there again with their \" +\n \"data kept; `notRemovable` — found by the dive itself (`mkl`) or not in the niche, left unchanged. To hide \" +\n \"a keyword the dive found, use `add_keyword_exclusions` instead. Free, and safe to repeat: a keyword already \" +\n \"removed comes back under `notRemovable`. Adding a hand-added keyword back later re-dives the niche and \" +\n \"costs Dive tokens if the niche is stale by then. Limited to 2 calls per minute per niche.\",\n inputSchema,\n // Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<RemoveNicheKeywordsResult>(\n { config: ctx.config, toolName: \"remove_niche_keywords\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords/remove`,\n { keywords: args.keywords },\n );\n },\n};\n","/**\n * Input shared by add_keyword_exclusions and remove_keyword_exclusions.\n *\n * The API normalises entries like MKL keywords (lower-cased, trimmed, unique per list), so the\n * per-entry rules come from niche-keyword-list.ts. The 1–100 total spans both optional lists, which\n * a raw input shape cannot express, so `assertExclusionCount` checks it in the handler — before the\n * call, so a request the API would refuse never reaches it.\n */\n\nimport { z } from \"zod\";\nimport { ApiError } from \"../http/errors.js\";\nimport { KEYWORD_EXCLUSIONS_MAX_PER_CALL } from \"../types/api.js\";\nimport { nicheKeyword, uniqueKeywords } from \"./niche-keyword-list.js\";\n\nconst entries = (field: string) =>\n uniqueKeywords(z.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();\n\nexport const keywordExclusionsInputSchema = (verb: \"exclude\" | \"stop excluding\") => ({\n nicheId: z.string().min(1).describe(\"The niche whose keyword exclusions to change. Get one from `list_niches`.\"),\n exact: entries(\"exact\").describe(\n `Exact-match entries to ${verb}: each one hides the single keyword that matches it exactly. At most 255 ` +\n \"characters each; lower-cased and trimmed, and must be unique after that.\",\n ),\n phrase: entries(\"phrase\").describe(\n `Phrase-match entries to ${verb}: each one hides every keyword that contains the phrase (e.g. \"puppy\" ` +\n 'hides \"puppy hat\" and \"hat for puppy\"). At most 255 characters each; lower-cased and trimmed, and ' +\n \"must be unique after that.\",\n ),\n});\n\nexport function assertExclusionCount(exact: string[] | undefined, phrase: string[] | undefined): void {\n const total = (exact?.length ?? 0) + (phrase?.length ?? 0);\n if (total === 0 || total > KEYWORD_EXCLUSIONS_MAX_PER_CALL) {\n throw new ApiError(\n \"bad_request\",\n 400,\n `Send 1 to ${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` and \\`phrase\\` ` +\n `(got ${total}). Split a longer list over several calls.`,\n );\n }\n}\n","import { httpPost } from \"../http/client.js\";\nimport {\n KEYWORD_EXCLUSIONS_MAX_PER_CALL,\n KEYWORD_EXCLUSIONS_MAX_PER_NICHE,\n type AddKeywordExclusionsResult,\n} from \"../types/api.js\";\nimport { assertExclusionCount, keywordExclusionsInputSchema } from \"./keyword-exclusions-input.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = keywordExclusionsInputSchema(\"exclude\");\n\nexport const addKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_keyword_exclusions\",\n title: \"Exclude Keywords from a Niche\",\n description:\n \"Use this to hide irrelevant keywords from a niche's Master Keyword List — e.g. other brands, wrong \" +\n \"product types. `exact` entries hide the one keyword that matches exactly; `phrase` entries hide every \" +\n \"keyword containing the phrase, so prefer `exact` unless the user wants a whole family of keywords gone. \" +\n \"The excluded keywords drop out of `get_niche_keywords` at once, wherever they come from: the dive, \" +\n \"Outliers, Residue, or ones added with `add_niche_keywords`. Nothing is deleted: \" +\n \"`remove_keyword_exclusions` brings them back. Only the exclusions change — every other niche setting is \" +\n `kept. Send 1–${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` and \\`phrase\\`; a niche ` +\n `can hold at most ${KEYWORD_EXCLUSIONS_MAX_PER_NICHE} exclusions. Free, no tokens, and safe to repeat: ` +\n \"entries already excluded come back under `unchanged`. Returns `added`, `unchanged` and the full `exact` / \" +\n \"`phrase` lists after the change. Limited to 30 calls per minute per niche, so batch entries into one call.\",\n inputSchema,\n // Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n assertExclusionCount(args.exact, args.phrase);\n return await httpPost<AddKeywordExclusionsResult>(\n { config: ctx.config, toolName: \"add_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,\n { exact: args.exact, phrase: args.phrase },\n );\n },\n};\n","import { httpPost } from \"../http/client.js\";\nimport { KEYWORD_EXCLUSIONS_MAX_PER_CALL, type RemoveKeywordExclusionsResult } from \"../types/api.js\";\nimport { assertExclusionCount, keywordExclusionsInputSchema } from \"./keyword-exclusions-input.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = keywordExclusionsInputSchema(\"stop excluding\");\n\nexport const removeKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_keyword_exclusions\",\n title: \"Remove Keyword Exclusions from a Niche\",\n description:\n \"Use this to undo `add_keyword_exclusions`, so the keywords an exclusion hid show in the niche's Master \" +\n \"Keyword List (`get_niche_keywords`) again. Pass the entries exactly as `list_keyword_exclusions` shows \" +\n \"them, `exact` and `phrase` separately: removing a phrase does not touch an exact entry for the same text, \" +\n \"and a keyword also hidden by another remaining exclusion stays hidden. Only the exclusions change — every \" +\n `other niche setting is kept. Send 1–${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` ` +\n \"and `phrase`. Free, and safe to repeat: entries that are not excluded come back under `unchanged`. \" +\n \"Returns `removed`, `unchanged` and the full `exact` / `phrase` lists after the change. Limited to 30 \" +\n \"calls per minute per niche.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n assertExclusionCount(args.exact, args.phrase);\n return await httpPost<RemoveKeywordExclusionsResult>(\n { config: ctx.config, toolName: \"remove_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions/remove`,\n { exact: args.exact, phrase: args.phrase },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to delete. Get one from `list_niches`.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — deleting a niche destroys its keywords, competitors and dive history \" +\n \"permanently. Confirm with the user first.\",\n ),\n};\n\nexport const deleteNicheTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_niche\",\n title: \"Delete a Niche\",\n description:\n \"Use this to permanently remove a niche the user no longer needs. ⚠️ Deletes the niche and everything \" +\n \"attached to it — keywords, competitors, all dives and diveboxes — and cannot be undone; the dive tokens \" +\n \"already spent on it are NOT refunded. Set `confirm: true` only after the user approves. A niche used by \" +\n \"any Rank Radar cannot be deleted: the call fails with a conflict, so delete or archive those rank radars \" +\n \"first (`list_rank_radars` with the `nicheId` filter shows them).\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this niche removes its keywords, competitors and all dive history permanently. \" +\n \"The dive tokens already spent on it are not refunded.\",\n );\n if (pending) return pending;\n\n // The endpoint answers 204 No Content, so there is no payload to return. Hand the\n // model an explicit result instead of `null`, which reads as a failed call.\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_niche\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}`,\n );\n return { status: \"deleted\", nicheId: args.nicheId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to pause (from `list_rank_radars`).\"),\n};\n\nexport const pauseRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"pause_rank_radar\",\n title: \"Pause a Rank Radar\",\n description:\n \"Use this to pause tracking on a Rank Radar without losing anything — the action the DataDive API calls \" +\n \"'archiving', so when a user asks to archive a Rank Radar, this is the tool they mean. Tracking stops for \" +\n \"all of its keywords and the Daily Tracked Keywords quota they held is freed for other Rank Radars, while \" +\n \"the Rank Radar and its ranking history are kept. Fully reversible with `resume_rank_radar`, so it needs \" +\n \"no `confirm` — prefer it over `delete_rank_radar` whenever the user may want the data back. Paused Rank \" +\n \"Radars show up in `list_rank_radars` under `status: PAUSED` — not `ARCHIVED`, which covers Rank Radars \" +\n \"removed with `delete_rank_radar`. Safe to repeat: pausing an already paused Rank Radar succeeds and \" +\n \"changes nothing.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // 204 No Content — return an explicit result rather than the `null` body.\n await httpPost<null>(\n { config: ctx.config, toolName: \"pause_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/archive`,\n {},\n );\n return { status: \"paused\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to resume (from `list_rank_radars`).\"),\n};\n\nexport const resumeRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"resume_rank_radar\",\n title: \"Resume a Paused Rank Radar\",\n description:\n \"Use this to restart tracking on a Rank Radar that was paused with `pause_rank_radar` (shown by \" +\n \"`list_rank_radars` under `status: PAUSED`; a deleted, `ARCHIVED` one cannot be resumed). Keywords are \" +\n \"resumed as far as the available Daily Tracked Keywords quota allows, most relevant first — so with a \" +\n \"tight quota only part of the original keyword set comes back; check `get_quota` first if that matters. \" +\n \"Reversible with `pause_rank_radar`, so it needs no `confirm`. Fails with a bad-request error when \" +\n \"there is no quota left at all. Safe to repeat: resuming an already active Rank Radar changes nothing.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n await httpPost<null>(\n { config: ctx.config, toolName: \"resume_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/resume`,\n {},\n );\n return { status: \"resumed\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to delete (from `list_rank_radars`).\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — deleting a Rank Radar destroys its ranking history permanently. \" +\n \"Confirm with the user first.\",\n ),\n};\n\nexport const deleteRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_rank_radar\",\n title: \"Delete a Rank Radar\",\n description:\n \"Use this to permanently remove a Rank Radar and all of its keyword ranking history. ⚠️ Cannot be undone \" +\n \"— there is no restore endpoint; set `confirm: true` only after the user approves. It does free the Daily \" +\n \"Tracked Keywords quota those keywords held. Deleted Rank Radars are what `list_rank_radars` returns for \" +\n \"`status: ARCHIVED`. If the user only wants to stop tracking for a while and keep the history — or asks \" +\n \"to 'archive' the Rank Radar, which is what the DataDive API calls pausing — use `pause_rank_radar` \" +\n \"instead: it frees the same quota and is reversible with `resume_rank_radar`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this Rank Radar removes its keyword ranking history permanently. It frees the Daily \" +\n \"Tracked Keywords quota, but the history cannot be recovered — `pause_rank_radar` frees the \" +\n \"same quota and is reversible.\",\n );\n if (pending) return pending;\n\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}`,\n );\n return { status: \"deleted\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ADD_SEARCH_TERMS_MAX, type AddSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID to add the search terms to (from `list_rank_radars`).\"),\n searchTerms: z\n .array(z.string().min(1))\n .min(1)\n .max(ADD_SEARCH_TERMS_MAX)\n .describe(\n `The keywords to start tracking, 1–${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored ` +\n \"special characters (@, #, _, parentheses, …) are rejected.\",\n ),\n};\n\nexport const addRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_rank_radar_search_terms\",\n title: \"Add Search Terms to a Rank Radar\",\n description:\n \"Use this to start tracking extra keywords on an existing Rank Radar, instead of creating a new one with \" +\n \"`create_rank_radar`. New keywords are added to the niche and tracked from now on; any matching keyword \" +\n \"that was previously paused is resumed. Each newly tracked keyword takes one Daily Tracked Keywords slot, \" +\n \"and the call fails if the quota is exhausted — check `get_quota` first. Reversible with \" +\n \"`pause_rank_radar_search_terms`, which frees the slots again, so it needs no `confirm`. Returns \" +\n \"`originBreakdown` (where each submitted term ended up) and `keywordToRankRadarKeywordIdMap`, whose ids \" +\n \"are what the pause/resume search-term tools take.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<AddSearchTermsResult>(\n { config: ctx.config, toolName: \"add_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms`,\n { searchTerms: args.searchTerms },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID the search terms belong to.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .describe(\n \"The Rank Radar keyword ids to pause. These are the `id` values from `get_rank_radar_data`, or the \" +\n \"values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` — not the \" +\n \"keyword text.\",\n ),\n};\n\nexport const pauseRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"pause_rank_radar_search_terms\",\n title: \"Pause Search Terms of a Rank Radar\",\n description:\n \"Use this to stop tracking individual keywords on a Rank Radar while keeping the Rank Radar itself \" +\n \"active — the way to trim a keyword set the user finds too broad. The DataDive API calls this \" +\n \"'archiving' the search terms; nothing is lost. The paused keywords keep their history and their Daily \" +\n \"Tracked Keywords slots are freed for other keywords. Reversible with `resume_rank_radar_search_terms`, \" +\n \"so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. \" +\n \"Safe to repeat: already-paused terms are left unchanged.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // 204 No Content — return an explicit result rather than the `null` body.\n await httpPost<null>(\n { config: ctx.config, toolName: \"pause_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms/archive`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n return {\n status: \"paused\",\n rankRadarId: args.rankRadarId,\n rankRadarKeywordIds: args.rankRadarKeywordIds,\n };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID the search terms belong to.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .describe(\n \"The Rank Radar keyword ids to resume. These are the `id` values from `get_rank_radar_data`, or the \" +\n \"values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` — not the \" +\n \"keyword text.\",\n ),\n};\n\nexport const resumeRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"resume_rank_radar_search_terms\",\n title: \"Resume Paused Search Terms of a Rank Radar\",\n description:\n \"Use this to start tracking keywords again that were paused with `pause_rank_radar_search_terms`. Each \" +\n \"resumed keyword takes back one Daily Tracked Keywords slot, and the call fails if the quota is \" +\n \"exhausted — check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, so it needs no \" +\n \"`confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: \" +\n \"already-active terms are left unchanged.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n await httpPost<null>(\n { config: ctx.config, toolName: \"resume_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms/resume`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n return {\n status: \"resumed\",\n rankRadarId: args.rankRadarId,\n rankRadarKeywordIds: args.rankRadarKeywordIds,\n };\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { RankRadarTagList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; a larger value is not honoured there. */\nexport const LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID whose tags to list (from `list_rank_radars`).\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\nexport const listRankRadarTagsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_rank_radar_tags\",\n title: \"List Tags of a Rank Radar\",\n description:\n \"Use this to find a `tagId` before calling `update_rank_radar_tag`, `delete_rank_radar_tag`, \" +\n \"`add_rank_radar_tag_search_terms` or `remove_rank_radar_tag_search_terms`. Returns the user \" +\n \"tags (\\\"highlights\\\") on the specified Rank Radar, ordered by title (case-insensitive, \" +\n \"ascending). Each row carries `id`, `title`, `color`, `description`, `startAt`, `endAt`, \" +\n \"`createdAt`, and `keywordCount` — the number of Rank Radar keywords this tag is applied to. \" +\n \"The system \\\"paused\\\" tag (the one the quota pauser writes with a reserved colour) is not \" +\n \"listed and cannot be addressed from these tools. Supports pagination metadata \" +\n \"(currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most \" +\n `${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE} tags.`,\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<RankRadarTagList> => {\n return await httpGet<RankRadarTagList>(\n { config: ctx.config, toolName: \"list_rank_radar_tags\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport {\n RankRadarTag,\n TAG_HEX_COLOR_PATTERN,\n TAG_KEYWORD_IDS_MAX,\n TAG_RESERVED_COLOR,\n TAG_TITLE_MAX_LENGTH,\n} from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID to create the tag on (from `list_rank_radars`).\"),\n title: z\n .string()\n .min(1)\n .max(TAG_TITLE_MAX_LENGTH)\n .describe(\n `Display title (1–${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on ` +\n \"this Rank Radar, case-insensitive. A clash returns 409 with \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }` — reuse that id instead of retrying.\",\n ),\n color: z\n .string()\n .regex(TAG_HEX_COLOR_PATTERN, \"color must be in #RRGGBB form\")\n .optional()\n .describe(\n \"Hex colour in `#RRGGBB` form. Defaults to the UI grey when omitted. The reserved colour \" +\n `\\`${TAG_RESERVED_COLOR}\\` (used by the system \"paused\" tag) is rejected with 400.`,\n ),\n description: z.string().optional().describe(\"Free-form description, e.g. 'Prime Day run-up'.\"),\n startAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`.\"),\n endAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .optional()\n .describe(\n `Optional: Rank Radar keyword ids to tag on creation (1–${TAG_KEYWORD_IDS_MAX}). These are ` +\n \"the `id` values from `get_rank_radar_data`; the ids must all belong to this Rank Radar. \" +\n \"To attach more later, use `add_rank_radar_tag_search_terms`.\",\n ),\n};\n\nexport const createRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_rank_radar_tag\",\n title: \"Create a Rank Radar Tag\",\n description:\n \"Use this to create a new user tag (\\\"highlight\\\") on a Rank Radar — the way the UI marks a \" +\n \"window on the ranking chart. Writing a tag costs nothing and is reversible with \" +\n \"`delete_rank_radar_tag`, so no confirmation is required. Returns the new tag row \" +\n \"(`id`, `title`, `color`, `description`, `startAt`, `endAt`, `keywordCount`, `createdAt`); \" +\n \"the `id` is the `tagId` the other tag tools take. If the title clashes with an existing tag \" +\n \"on this Rank Radar, the API answers 409 `{ error: 'TAG_TITLE_EXISTS', existingTagId }` so \" +\n \"callers can reuse the existing tag without a follow-up `list_rank_radar_tags`. The reserved \" +\n `colour \\`${TAG_RESERVED_COLOR}\\` is rejected with 400. Pass \\`rankRadarKeywordIds\\` to tag ` +\n \"a batch of keywords in the same call; otherwise create the tag and attach keywords later \" +\n \"with `add_rank_radar_tag_search_terms`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const body: Record<string, unknown> = { title: args.title };\n if (args.color !== undefined) body.color = args.color;\n if (args.description !== undefined) body.description = args.description;\n if (args.startAt !== undefined) body.startAt = args.startAt;\n if (args.endAt !== undefined) body.endAt = args.endAt;\n if (args.rankRadarKeywordIds !== undefined) body.rankRadarKeywordIds = args.rankRadarKeywordIds;\n\n return await httpPost<RankRadarTag>(\n { config: ctx.config, toolName: \"create_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,\n body,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPatch } from \"../http/client.js\";\nimport {\n RankRadarTag,\n TAG_HEX_COLOR_PATTERN,\n TAG_RESERVED_COLOR,\n TAG_TITLE_MAX_LENGTH,\n} from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`).\"),\n title: z\n .string()\n .min(1)\n .max(TAG_TITLE_MAX_LENGTH)\n .optional()\n .describe(\n `New title (1–${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this ` +\n \"Rank Radar, case-insensitive. A clash returns 409 with \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }`.\",\n ),\n color: z\n .string()\n .regex(TAG_HEX_COLOR_PATTERN, \"color must be in #RRGGBB form\")\n .optional()\n .describe(`Hex colour in \\`#RRGGBB\\` form. The reserved colour \\`${TAG_RESERVED_COLOR}\\` is rejected with 400.`),\n description: z\n .string()\n .optional()\n .describe(\"Free-form description. Pass an empty string to clear.\"),\n startAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`.\"),\n endAt: z.string().optional().describe(\"ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.\"),\n};\n\nexport const updateRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"update_rank_radar_tag\",\n title: \"Update a Rank Radar Tag\",\n description:\n \"Use this to change a tag's title, colour, description, or date window. Partial update: \" +\n \"only the fields you pass are modified, the rest are left as they are. The system \" +\n \"\\\"paused\\\" tag (reserved colour, written by the quota pauser) cannot be addressed from \" +\n \"here — the API answers 404. Writing a tag costs nothing and is reversible, so no \" +\n \"confirmation is required. Returns the updated tag row. Title clashes return 409 \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }`; the reserved colour returns 400; and \" +\n \"`endAt < startAt` returns 400 (for whichever side the body omits, the comparison uses the \" +\n \"stored value).\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const body: Record<string, unknown> = {};\n if (args.title !== undefined) body.title = args.title;\n if (args.color !== undefined) body.color = args.color;\n if (args.description !== undefined) body.description = args.description;\n if (args.startAt !== undefined) body.startAt = args.startAt;\n if (args.endAt !== undefined) body.endAt = args.endAt;\n\n return await httpPatch<RankRadarTag>(\n { config: ctx.config, toolName: \"update_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,\n body,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to delete (the `id` from `list_rank_radar_tags`).\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed. Confirm with the user first — the tag's keyword assignments are \" +\n \"removed too and the tag cannot be recovered.\",\n ),\n};\n\nexport const deleteRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_rank_radar_tag\",\n title: \"Delete a Rank Radar Tag\",\n description:\n \"Use this to permanently remove a tag from a Rank Radar. ⚠️ The tag itself is gone and the \" +\n \"assignments linking it to each tagged keyword are removed with it; the keywords themselves \" +\n \"keep tracking. The deletion cannot be undone from this tool, so set `confirm: true` only \" +\n \"after the user approves. The system \\\"paused\\\" tag (reserved colour, written by the quota \" +\n \"pauser) cannot be addressed from here — the API answers 404. To remove just the assignments \" +\n \"and keep the tag, use `remove_rank_radar_tag_search_terms` instead.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this tag removes it from every keyword it was applied to. The tag cannot be \" +\n \"recovered — if the user only wants to clear its keyword list, use \" +\n \"`remove_rank_radar_tag_search_terms`.\",\n );\n if (pending) return pending;\n\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,\n );\n return { status: \"deleted\", rankRadarId: args.rankRadarId, tagId: args.tagId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { TAG_KEYWORD_IDS_MAX, type TagSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`).\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .describe(\n `Rank Radar keyword ids to attach to this tag (1–${TAG_KEYWORD_IDS_MAX} per call, unique). ` +\n \"These are the `id` values from `get_rank_radar_data`; all ids must belong to this Rank \" +\n \"Radar. Safe to repeat: keywords already attached are left unchanged.\",\n ),\n};\n\nexport const addRankRadarTagSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_rank_radar_tag_search_terms\",\n title: \"Attach Search Terms to a Rank Radar Tag\",\n description:\n \"Use this to tag a batch of keywords on a Rank Radar — the same relation \" +\n \"`create_rank_radar_tag` writes when `rankRadarKeywordIds` is passed on creation, so this is \" +\n \"how you keep extending a tag's keyword set over time. Reversible with \" +\n \"`remove_rank_radar_tag_search_terms`, so no confirmation is required. Idempotent: \" +\n \"already-attached keywords are left unchanged, so re-sending the same ids after a timeout \" +\n `is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call — split larger batches into multiple ` +\n \"calls. The system \\\"paused\\\" tag (reserved colour, written by the quota pauser) is not \" +\n \"addressable: the API answers 404.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<TagSearchTermsResult>(\n { config: ctx.config, toolName: \"add_rank_radar_tag_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { TAG_KEYWORD_IDS_MAX, type TagSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`).\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .describe(\n `Rank Radar keyword ids to detach from this tag (1–${TAG_KEYWORD_IDS_MAX} per call, unique). ` +\n \"These are the `id` values from `get_rank_radar_data`. Safe to repeat: keywords that are \" +\n \"not currently attached are left unchanged.\",\n ),\n};\n\nexport const removeRankRadarTagSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_rank_radar_tag_search_terms\",\n title: \"Detach Search Terms from a Rank Radar Tag\",\n description:\n \"Use this to remove keywords from a tag without deleting the tag itself. The tag keeps its \" +\n \"title, colour and date window; only the keyword assignments listed are cleared. Reversible \" +\n \"with `add_rank_radar_tag_search_terms`, so no confirmation is required. Idempotent: ids \" +\n \"that are not currently attached are left unchanged, so re-sending the same ids after a \" +\n `timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call — split larger batches into ` +\n \"multiple calls. To remove the tag itself, use `delete_rank_radar_tag`. The system \" +\n \"\\\"paused\\\" tag (reserved colour, written by the quota pauser) is not addressable: the API \" +\n \"answers 404.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<TagSearchTermsResult>(\n { config: ctx.config, toolName: \"remove_rank_radar_tag_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms/remove`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { LISTING_COPY_STRATEGIES, type CreateListingCopyResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z\n .string()\n .min(1)\n .describe(\"The niche whose keyword data the copy is written from. Get one from `list_niches`.\"),\n strategy: z\n .enum(LISTING_COPY_STRATEGIES)\n .describe(\n \"Which generation strategy to use. `cosmo` targets Amazon's COSMO relevance model, `ranking-juice` \" +\n \"optimises for DataDive's ranking-juice score, `nlp` writes for keyword coverage, and `cosmo-rufus` \" +\n \"targets COSMO plus the Rufus shopping assistant. Ask the user if they have no preference.\",\n ),\n currentListing: z\n .record(z.union([z.string(), z.array(z.string())]))\n .describe(\n \"The seller's existing listing to rewrite, as an object — e.g. \" +\n '`{ \"title\": \"...\", \"description\": \"...\", \"bullets\": [\"...\", \"...\"] }`. Pass what the user has; ' +\n \"it is the starting point the generated copy builds on.\",\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — each generation spends one AI Copywriter prompt from the quota. \" +\n \"Confirm the cost with the user first.\",\n ),\n};\n\nexport const generateListingCopyTool: ToolDefinition<typeof inputSchema> = {\n name: \"generate_listing_copy\",\n title: \"Generate Listing Copy for a Niche\",\n description:\n \"Use this to draft an optimised Amazon listing — title, bullets and description — from a niche's keyword \" +\n \"research and the seller's current listing. It writes text only: nothing is published to Amazon, and the \" +\n \"user still has to paste the result into Seller Central. ⚠️ Spends one AI Copywriter prompt from the \" +\n \"quota per call and cannot be undone — set `confirm: true` only after the user approves. Not safe to \" +\n \"retry: each call spends another prompt, so if a call errors or times out, poll \" +\n \"`get_listing_copy_generation_status` instead of re-calling. Runs asynchronously: returns a \" +\n \"`generationId` — poll `get_listing_copy_generation_status` with it until the status is `complete`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Generating listing copy consumes one AI Copywriter prompt from the organization's quota. \" +\n \"Check the remaining balance with `get_quota` if the user needs it.\",\n );\n if (pending) return pending;\n\n // The API field is still named `prompt`/`listingToInclude`; the tool surface uses\n // `strategy`/`currentListing` because those read as what they are to a model.\n return await httpPost<CreateListingCopyResult>(\n { config: ctx.config, toolName: \"generate_listing_copy\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ai-copywriter`,\n { prompt: args.strategy, listingToInclude: args.currentListing },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { ListingCopyGenerationStatus } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche the generation was started for.\"),\n generationId: z.string().min(1).describe(\"The `generationId` returned by `generate_listing_copy`.\"),\n};\n\nexport const getListingCopyGenerationStatusTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_listing_copy_generation_status\",\n title: \"Get Listing Copy Generation Status\",\n description:\n \"Use this to poll a listing-copy draft started with `generate_listing_copy` until it finishes. Returns \" +\n \"one of three shapes keyed by `status`: `generating` (still running — poll again in a few seconds), \" +\n \"`complete` (with `result`, carrying the generated `title`, `bullets`, `description`, `itemHighlights` \" +\n \"and its `rankingJuice` score), or `failed` (with an `error` message). Polling is free — it does not \" +\n \"spend another AI Copywriter prompt, so always poll rather than re-calling `generate_listing_copy`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<ListingCopyGenerationStatus>(\n { config: ctx.config, toolName: \"get_listing_copy_generation_status\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ai-copywriter/${encodeURIComponent(args.generationId)}`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { SellerProfileList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const listSellerProfilesTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_seller_profiles\",\n title: \"List Connected Amazon Seller Profiles\",\n description:\n \"Use this when the user asks which Amazon seller accounts are connected, or as the discovery \" +\n \"step to find the `sellerId` and `marketplace` required by `get_asin_inventory_distribution`, \" +\n \"`get_seller_catalog`, `get_seller_listing_changes`, and the alert tools. \" +\n \"Returns a paginated list of the organization's connected seller profiles — each item has \" +\n \"sellerId, sellerName, marketplace (e.g. \\\"com\\\", \\\"co.uk\\\"), hasAdApi (whether Advertising API \" +\n \"credentials are connected), and createdAt — plus pagination metadata (currentPage, pageSize, \" +\n \"total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<SellerProfileList>(\n { config: ctx.config, toolName: \"list_seller_profiles\" },\n \"/v1/seller_profiles\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { CATALOG_STATUSES, type SellerCatalogList } from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on product title or brand.\"),\n brand: z.string().min(1).optional().describe(\"Case-insensitive partial match on brand name.\"),\n status: z\n .enum(CATALOG_STATUSES)\n .optional()\n .describe('Listing status filter. \"Active\" (default) or \"all\".'),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const getSellerCatalogTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_seller_catalog\",\n title: \"List Catalog ASINs for a Seller\",\n description:\n \"Use this when the user wants to browse or search a seller's own Amazon catalog — the products \" +\n \"they sell on a given marketplace. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` \" +\n \"to discover them). Filter by `search` (title/brand), `brand`, and `status` (Active by default, or \" +\n \"all). Returns a paginated list where each item has asin, title, parentAsin, brand, status, imageUrl, \" +\n \"and hasVariations, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<SellerCatalogList>(\n { config: ctx.config, toolName: \"get_seller_catalog\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"catalog\"),\n {\n search: args.search,\n brand: args.brand,\n status: args.status,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","/**\n * Shared input fields + path builder for the seller+marketplace-scoped tools\n * (/v1/seller_profiles/:sellerId/marketplaces/:marketplace/*). Defined once here\n * and spread into each tool's inputSchema, mirroring alert-query.ts.\n */\n\nimport { z } from \"zod\";\nimport { SUPPORTED_MARKETPLACES } from \"../types/api.js\";\n\nexport const sellerScopedInputSchema = {\n sellerId: z\n .string()\n .min(1)\n .describe(\n \"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools.\",\n ),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe('Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n};\n\n/** Builds a `/v1/seller_profiles/:sellerId/marketplaces/:marketplace/<suffix>` path with both params URL-encoded. */\nexport function sellerMarketplacePath(sellerId: string, marketplace: string, suffix: string): string {\n return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(\n marketplace,\n )}/${suffix}`;\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport {\n ISO_8601,\n LISTING_CHANGE_SORT_BY,\n LISTING_CHANGE_TYPES,\n SORT_ORDER,\n type ListingChangeList,\n} from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n types: z\n .array(z.enum(LISTING_CHANGE_TYPES))\n .min(1)\n .optional()\n .describe('Filter to specific change types. Omit for all. Any of \"Price\", \"Content\", \"Image\".'),\n asin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Filter to an exact ASIN or parent ASIN.\"),\n brand: z.string().min(1).optional().describe(\"Case-insensitive partial match on brand name.\"),\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on product title or brand.\"),\n startDate: z\n .string()\n .regex(ISO_8601, \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only changes detected on or after this time.\"),\n endDate: z\n .string()\n .regex(ISO_8601, \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only changes detected on or before this time.\"),\n sortBy: z\n .enum(LISTING_CHANGE_SORT_BY)\n .optional()\n .describe('Sort field. \"date\" (default) or \"type\".'),\n sortOrder: z.enum(SORT_ORDER).optional().describe('Sort direction. \"DESC\" (default) or \"ASC\".'),\n includeCorrelations: z\n .boolean()\n .optional()\n .describe(\n \"When true, include the ranking/conversion `correlation` object per change (CVR and search-term \" +\n \"movement before vs after). Defaults to false.\",\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const getSellerListingChangesTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_seller_listing_changes\",\n title: \"List Listing Changes for a Seller\",\n description:\n \"Use this when the user asks what changed on their Amazon listings — price, content (title/bullets/\" +\n \"description), or image edits — for a seller account. Requires a `sellerId` + `marketplace` (use \" +\n \"`list_seller_profiles` to discover them). Filter by `types`, `asin`/parent ASIN, `brand`, `search`, \" +\n \"and a `startDate`/`endDate` range; sort with `sortBy`/`sortOrder`. Set `includeCorrelations: true` to \" +\n \"attach the ranking/conversion impact per change. Returns a paginated list where each item has asin, \" +\n \"title, imageUrl, date, type, contentType, description, previousValue, newValue, and (optionally) \" +\n \"correlation, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<ListingChangeList>(\n { config: ctx.config, toolName: \"get_seller_listing_changes\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"listing-changes\"),\n {\n // The API accepts `types` as a comma-separated list.\n types: args.types?.join(\",\"),\n asin: args.asin,\n brand: args.brand,\n search: args.search,\n startDate: args.startDate,\n endDate: args.endDate,\n sortBy: args.sortBy,\n sortOrder: args.sortOrder,\n includeCorrelations: args.includeCorrelations,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport {\n ISO_8601,\n PPC_CAMPAIGN_BASE_SORT_BY,\n PPC_CAMPAIGN_PLACEMENT_SORT_BY,\n PPC_CAMPAIGN_STATES,\n SORT_ORDER,\n type PpcCampaignList,\n} from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `MAX_PPC_CAMPAIGNS_WINDOW_DAYS`; a longer window is refused with a 400. */\nexport const PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;\n\n/** Mirrors the API's `@Max(50)` on `pageSize`. */\nexport const PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n startDate: z\n .string()\n .regex(\n ISO_8601,\n \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\",\n )\n .optional()\n .describe(\"Start of the reporting window, ISO-8601. Defaults to 30 days before endDate.\"),\n endDate: z\n .string()\n .regex(\n ISO_8601,\n \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\",\n )\n .optional()\n .describe(\n `End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`,\n ),\n asin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Only campaigns advertising this ASIN. Cannot be combined with parentAsin.\"),\n parentAsin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin.\"),\n search: z\n .string()\n .min(3)\n .max(200)\n .optional()\n .describe(\"Partial match on campaign name or advertised ASIN. At least 3 characters.\"),\n includePlacements: z\n .boolean()\n .optional()\n .describe(\n \"Include the per-placement breakdown (topOfSearch, restOfSearch, productPage, offAmazon, each with its \" +\n \"bid adjustment). Defaults to true; set false for a smaller response when placements are not needed.\",\n ),\n sortBy: z\n .enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY])\n .optional()\n .describe(\n `Sort field. Defaults to \"name\". Any of ${PPC_CAMPAIGN_BASE_SORT_BY.join(\", \")}. The placement fields ` +\n '(e.g. \"tosSpend\", \"tosAcos\", \"ppBidAdjustment\") are only accepted when includePlacements is not false.',\n ),\n sortOrder: z.enum(SORT_ORDER).optional().describe('Sort direction. \"ASC\" (default) or \"DESC\".'),\n state: z\n .enum(PPC_CAMPAIGN_STATES)\n .optional()\n .describe(\"Only ENABLED or only PAUSED campaigns. Omit for both.\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(PPC_CAMPAIGNS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\nexport const listPpcCampaignsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_ppc_campaigns\",\n title: \"List PPC Campaigns for a Seller\",\n description:\n \"Use this when the user asks about their Sponsored Products campaigns — which campaigns spend the most, \" +\n \"which have a high ACOS, how placements (top of search, rest of search, product pages) perform, or which \" +\n \"campaigns advertise a given product. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` \" +\n \"to discover them). Narrow to one product with `asin`, or to a variation family with `parentAsin` (not \" +\n \"both). The reporting window defaults to the last 30 days and cannot exceed \" +\n `${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days. Returns a paginated list where each campaign has campaignId, name, ` +\n \"type, state, targetingType, bidStrategy, budget { type, amount, currencyCode }, window totals \" +\n \"(impressions, clicks, ctr, cpc, cvr, spend, unitsSold, orders, sales, tosImpressionShare, acos, roas, \" +\n \"tacos), asinCount and, unless includePlacements is false, `placements`; plus pagination metadata \" +\n \"(currentPage, pageSize, total, lastPage, hasNext, hasPrev). To find the biggest spenders, sort with \" +\n '`sortBy: \"spend\", sortOrder: \"DESC\"` rather than paging through every campaign. Each call counts toward ' +\n \"API usage and is rate limited (about 60 requests/minute). For per-keyword ad data of a Rank Radar, use \" +\n \"`get_rank_radar_ppc_data`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n if (args.asin && args.parentAsin) {\n // The API refuses the pair too; checking here saves a counted, rate-limited call.\n throw new ApiError(\n \"bad_request\",\n 400,\n \"Pass either `asin` (one product) or `parentAsin` (its whole variation family), not both.\",\n );\n }\n\n return await httpGet<PpcCampaignList>(\n { config: ctx.config, toolName: \"list_ppc_campaigns\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"ppc/campaigns\"),\n {\n startDate: args.startDate,\n endDate: args.endDate,\n asin: args.asin,\n parentAsin: args.parentAsin,\n search: args.search,\n includePlacements: args.includePlacements,\n sortBy: args.sortBy,\n sortOrder: args.sortOrder,\n state: args.state,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type InventoryByFcResponse } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n sellerId: z\n .string()\n .min(1)\n .describe(\n \"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.\",\n ),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe('Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n asin: z\n .string()\n .regex(/^[A-Z0-9]{10}$/, \"ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).\")\n .describe(\n \"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234).\",\n ),\n};\n\nexport const getAsinInventoryDistributionTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_asin_inventory_distribution\",\n title: \"Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN\",\n description:\n \"Use this when the user asks about an ASIN's inventory: stock levels, days of stock or when it will run out, \" +\n \"low-inventory or aged-inventory risk, low-inventory-level (LILF) or long-term storage (LTSF) fees, or where \" +\n \"their units sit across Amazon's fulfillment network. Requires the Amazon sellerId (from `list_seller_profiles` \" +\n \"or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. \" +\n \"Returns the metrics the DataDive Inventory page shows — fulfillableStock, totalStock, reservedStock, \" +\n \"inboundStock, researchingStock, unfulfillableStock, daysOfStock (with daysOfStockSource AMAZON or ESTIMATE), \" +\n \"daysOfStock30, daysOfStock90, inLowInventoryState, estWeeklyLilf, estLtsfNextAssessment, ltsfLastMonthActual, \" +\n \"agedUnits, oldestUnitAgeDays, with fees in `currency` — plus totalSellableUnits and a per-FC `distribution` \" +\n \"array (fc, state, availableStock, availableStockPercentage). A metric is null, never 0, when Amazon has no data \" +\n \"(e.g. merchant-fulfilled); metrics the user's plan does not include are null and listed in `lockedFields` \" +\n \"(say they need a plan upgrade, do not report them as zero). `lastUpdatedAt` dates the stock and metrics; \" +\n \"`distributionLastUpdatedAt` dates the distribution, which is usually up to 2 days behind. Either may be null \" +\n \"when no successful refresh happened in the last 30 days.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<InventoryByFcResponse>(\n { config: ctx.config, toolName: \"get_asin_inventory_distribution\" },\n `/v1/sellers/${encodeURIComponent(args.sellerId)}/marketplaces/${encodeURIComponent(\n args.marketplace,\n )}/asins/${encodeURIComponent(args.asin)}/inventory`,\n );\n },\n};\n","/**\n * Shared input schema for the /v1/alerts/* list tools. Both alert endpoints\n * accept the same backend query DTO (ExternalAlertsQueryDto), so the input\n * contract is defined once here and imported by each tool.\n */\n\nimport { z } from \"zod\";\nimport { ALERT_STATUSES, ISO_8601, SUPPORTED_MARKETPLACES } from \"../types/api.js\";\n\nexport const alertsQueryInputSchema = {\n sellerId: z\n .string()\n .min(1)\n .max(50)\n .optional()\n .describe('Filter to a single connected Amazon seller account ID, e.g. \"A1B2C3D4E5\".'),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .optional()\n .describe('Filter to a single Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n status: z\n .enum(ALERT_STATUSES)\n .optional()\n .describe(\n \"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, \" +\n \"`all` returns both. Dismissed alerts are never returned.\",\n ),\n updatedSince: z\n .string()\n .regex(ISO_8601, \"updatedSince must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\n \"ISO-8601 timestamp; return only alerts surfaced at or after this time (filters on `lastAlertedAt`). \" +\n \"Use it to incrementally sync since your last poll. Defaults to the last 30 days when omitted.\",\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { IndexingIssueAlertList } from \"../types/api.js\";\nimport { alertsQueryInputSchema } from \"./alert-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nexport const listIndexingIssueAlertsTool: ToolDefinition<typeof alertsQueryInputSchema> = {\n name: \"list_indexing_issue_alerts\",\n title: \"List Indexing-Issue Alerts\",\n description:\n \"Use this to find products that may have lost search visibility. Retrieves a paginated list of \" +\n \"indexing-issue alerts across the user's connected Amazon seller accounts — an alert fires when one of \" +\n \"their ASINs is no longer indexed for its tracked keywords. By default returns active (unresolved) alerts \" +\n \"from the last 30 days; filter by `sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental \" +\n \"polling). Each item includes id, asin, title, imageUrl, isParent, sellerId, marketplace, lastAlertedAt, \" +\n \"and resolvedAt. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema: alertsQueryInputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<IndexingIssueAlertList>(\n { config: ctx.config, toolName: \"list_indexing_issue_alerts\" },\n \"/v1/alerts/indexing-issues\",\n {\n sellerId: args.sellerId,\n marketplace: args.marketplace,\n status: args.status,\n updatedSince: args.updatedSince,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { BlindSpendAlertList } from \"../types/api.js\";\nimport { alertsQueryInputSchema } from \"./alert-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nexport const listBlindSpendAlertsTool: ToolDefinition<typeof alertsQueryInputSchema> = {\n name: \"list_blind_spend_alerts\",\n title: \"List Blind-Spend (Wasted-Spend) Alerts\",\n description:\n \"Use this to find wasted PPC ad spend. Retrieves a paginated list of blind-spend alerts across the \" +\n \"user's connected Amazon seller accounts — an alert flags ad spend on customer search terms that produced \" +\n \"little or no sales. By default returns active (unresolved) alerts from the last 30 days; filter by \" +\n \"`sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental polling). Each item includes id, \" +\n \"asin, title, imageUrl, sellerId, marketplace, lastAlertedAt, resolvedAt, wastedSpend (total ad spend \" +\n \"across unresolved terms), totalKeywordCount, unresolvedKeywordCount, and searchTerms — the unresolved \" +\n \"wasted-spend search terms, each with term, spend, sales, clicks, cvr (0-1), and impressions. Supports \" +\n \"pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema: alertsQueryInputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<BlindSpendAlertList>(\n { config: ctx.config, toolName: \"list_blind_spend_alerts\" },\n \"/v1/alerts/blind-spend\",\n {\n sellerId: args.sellerId,\n marketplace: args.marketplace,\n status: args.status,\n updatedSince: args.updatedSince,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { Quota } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {};\n\nexport const getQuotaTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_quota\",\n title: \"Get Quota Usage\",\n description:\n \"Use this when the user asks how much quota or how many tokens they have left, or before creating a \" +\n \"Niche Dive or Rank Radar to check remaining headroom. \" +\n \"Returns current quota usage and capacity for each billable feature — DIVED_ASINS (Dive tokens), \" +\n \"RANK_RADAR_KEYWORDS (tracked keywords), PRODUCT_BRIEF_ASINS, and AI_COPYWRITER_PROMPTS — each with \" +\n \"`used` and `capacity` (null when unlimited / not applicable), plus `nextRefreshDate` (ISO-8601 \" +\n \"timestamp of the next quota reset, or null). Takes no arguments.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (_args, ctx) => {\n return await httpGet<Quota>({ config: ctx.config, toolName: \"get_quota\" }, \"/v1/quota\");\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { BILLABLE_FEATURE_TYPES, ISO_8601, type UsageLogList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n type: z\n .enum(BILLABLE_FEATURE_TYPES)\n .optional()\n .describe(\"Filter to a single billable feature type. Omit to return all types.\"),\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on the user's name or email.\"),\n startDate: z\n .string()\n .regex(ISO_8601, \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only usage logs recorded on or after this time.\"),\n endDate: z\n .string()\n .regex(ISO_8601, \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only usage logs recorded on or before this time.\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe(\"Items per page (max 200). Defaults to 50.\"),\n};\n\nexport const listUsageTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_usage\",\n title: \"List Billable Usage Logs\",\n description:\n \"Use this when the user asks who consumed tokens, how their quota was spent, or wants an audit of \" +\n \"billable activity over a date range. \" +\n \"Retrieves a paginated list of billable feature usage logs for the organization — each entry is a \" +\n \"token-consumption event (a dive, rank-radar creation, AI copywriter prompt, etc.). Filter by `type` \" +\n \"(billable feature), `search` (user name/email), and `startDate`/`endDate`. Each item includes name, \" +\n \"email, qty (tokens consumed), type, action (specific operation), nicheId/nicheName, rankRadarId, and \" +\n \"date, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<UsageLogList>(\n { config: ctx.config, toolName: \"list_usage\" },\n \"/v1/usage\",\n {\n type: args.type,\n search: args.search,\n startDate: args.startDate,\n endDate: args.endDate,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","/**\n * Tool registry. The order here is the order tools appear in `tools/list`\n * over the MCP transport, which biases LLM discovery — keep \"discovery\" tools\n * (the ones that return IDs needed by other tools) at the top.\n */\n\nimport { listNichesTool } from \"./list-niches.js\";\nimport { getNicheKeywordsTool } from \"./get-niche-keywords.js\";\nimport { listKeywordExclusionsTool } from \"./list-keyword-exclusions.js\";\nimport { getNicheRootsTool } from \"./get-niche-roots.js\";\nimport { getNicheCompetitorsTool } from \"./get-niche-competitors.js\";\nimport { getRankingJuiceTool } from \"./get-ranking-juice.js\";\nimport { listRankRadarsTool } from \"./list-rank-radars.js\";\nimport { getRankRadarDataTool } from \"./get-rank-radar-data.js\";\nimport { getRankRadarSqpDataTool } from \"./get-rank-radar-sqp-data.js\";\nimport { getRankRadarPpcDataTool } from \"./get-rank-radar-ppc-data.js\";\nimport { createRankRadarTool } from \"./create-rank-radar.js\";\nimport { createNicheDiveTool } from \"./create-niche-dive.js\";\nimport { createNicheDiveFromCompetitorsListTool } from \"./create-niche-dive-from-competitors-list.js\";\nimport { rediveNicheTool } from \"./redive-niche.js\";\nimport { getDiveStatusTool } from \"./get-dive-status.js\";\nimport { addNicheKeywordsTool } from \"./add-niche-keywords.js\";\nimport { removeNicheKeywordsTool } from \"./remove-niche-keywords.js\";\nimport { addKeywordExclusionsTool } from \"./add-keyword-exclusions.js\";\nimport { removeKeywordExclusionsTool } from \"./remove-keyword-exclusions.js\";\nimport { deleteNicheTool } from \"./delete-niche.js\";\nimport { pauseRankRadarTool } from \"./pause-rank-radar.js\";\nimport { resumeRankRadarTool } from \"./resume-rank-radar.js\";\nimport { deleteRankRadarTool } from \"./delete-rank-radar.js\";\nimport { addRankRadarSearchTermsTool } from \"./add-rank-radar-search-terms.js\";\nimport { pauseRankRadarSearchTermsTool } from \"./pause-rank-radar-search-terms.js\";\nimport { resumeRankRadarSearchTermsTool } from \"./resume-rank-radar-search-terms.js\";\nimport { listRankRadarTagsTool } from \"./list-rank-radar-tags.js\";\nimport { createRankRadarTagTool } from \"./create-rank-radar-tag.js\";\nimport { updateRankRadarTagTool } from \"./update-rank-radar-tag.js\";\nimport { deleteRankRadarTagTool } from \"./delete-rank-radar-tag.js\";\nimport { addRankRadarTagSearchTermsTool } from \"./add-rank-radar-tag-search-terms.js\";\nimport { removeRankRadarTagSearchTermsTool } from \"./remove-rank-radar-tag-search-terms.js\";\nimport { generateListingCopyTool } from \"./generate-listing-copy.js\";\nimport { getListingCopyGenerationStatusTool } from \"./get-listing-copy-generation-status.js\";\nimport { listSellerProfilesTool } from \"./list-seller-profiles.js\";\nimport { getSellerCatalogTool } from \"./get-seller-catalog.js\";\nimport { getSellerListingChangesTool } from \"./get-seller-listing-changes.js\";\nimport { listPpcCampaignsTool } from \"./list-ppc-campaigns.js\";\nimport { getAsinInventoryDistributionTool } from \"./get-asin-inventory-distribution.js\";\nimport { listIndexingIssueAlertsTool } from \"./list-indexing-issue-alerts.js\";\nimport { listBlindSpendAlertsTool } from \"./list-blind-spend-alerts.js\";\nimport { getQuotaTool } from \"./get-quota.js\";\nimport { listUsageTool } from \"./list-usage.js\";\nimport type { AnyTool } from \"./types.js\";\n\n// Note on the cast: each ToolDefinition<S> has a handler typed to its specific\n// schema; the registry needs a uniform shape. The MCP SDK validates incoming\n// args against `inputSchema` before calling the handler, so the cast is safe.\nexport const allTools: ReadonlyArray<AnyTool> = [\n listNichesTool,\n getNicheKeywordsTool,\n listKeywordExclusionsTool,\n getNicheRootsTool,\n getNicheCompetitorsTool,\n getRankingJuiceTool,\n listRankRadarsTool,\n getRankRadarDataTool,\n getRankRadarSqpDataTool,\n getRankRadarPpcDataTool,\n listRankRadarTagsTool,\n createRankRadarTool,\n createNicheDiveTool,\n createNicheDiveFromCompetitorsListTool,\n rediveNicheTool,\n getDiveStatusTool,\n addNicheKeywordsTool,\n removeNicheKeywordsTool,\n addKeywordExclusionsTool,\n removeKeywordExclusionsTool,\n addRankRadarSearchTermsTool,\n pauseRankRadarSearchTermsTool,\n resumeRankRadarSearchTermsTool,\n createRankRadarTagTool,\n updateRankRadarTagTool,\n addRankRadarTagSearchTermsTool,\n removeRankRadarTagSearchTermsTool,\n deleteRankRadarTagTool,\n pauseRankRadarTool,\n resumeRankRadarTool,\n deleteRankRadarTool,\n deleteNicheTool,\n generateListingCopyTool,\n getListingCopyGenerationStatusTool,\n listSellerProfilesTool,\n getSellerCatalogTool,\n getSellerListingChangesTool,\n listPpcCampaignsTool,\n getAsinInventoryDistributionTool,\n listIndexingIssueAlertsTool,\n listBlindSpendAlertsTool,\n getQuotaTool,\n listUsageTool,\n] as unknown as ReadonlyArray<AnyTool>;\n\nexport type { ToolDefinition, AnyTool } from \"./types.js\";\n","/**\n * \"What's new\" for connected assistants and users (RS-11631).\n *\n * MCP has no message for \"a new version with new features is out\": `tools/list_changed` only\n * reaches a live session, and the hosted server is stateless, so no session survives a deploy.\n * Two protocol features carry the news instead, both built from the list below:\n *\n * - `SERVER_INSTRUCTIONS`, sent to the client in the `initialize` result on every connection,\n * so the assistant knows the latest additions without the user asking.\n * - The `whats_new` prompt, which the user picks by hand (e.g. `/mcp__datadive__whats_new` in\n * Claude Code), for the full recent list.\n *\n * Both the stdio server (`buildServer`) and the hosted server register them from these exports,\n * so there is one copy. Add an entry here with every release that changes what a user can do;\n * keep the wording for users, not developers. Newest first.\n */\n\nexport interface WhatsNewEntry {\n /** Package version that shipped the change. */\n version: string;\n /** yyyy-mm-dd the change reached the hosted server. */\n released: string;\n items: string[];\n}\n\nexport const WHATS_NEW: ReadonlyArray<WhatsNewEntry> = [\n {\n version: \"0.17.1\",\n released: \"2026-10-13\",\n items: [\n \"Rank Radar tags end-to-end: list, create, update, delete tags, and attach or detach up to 1000 \" +\n \"keywords per call (`list_rank_radar_tags`, `create_rank_radar_tag`, `update_rank_radar_tag`, \" +\n \"`delete_rank_radar_tag`, `add_rank_radar_tag_search_terms`, `remove_rank_radar_tag_search_terms`). \" +\n \"The highlights on each keyword in `get_rank_radar_data` are the same objects, so you can go \" +\n \"from a ranking view to editing its tags without a round trip.\",\n ]},\n {\n version: \"0.17.0\",\n released: \"2026-10-12\",\n items: [\n \"Add your own keywords to a niche's Master Keyword List, or take them back out (`add_niche_keywords`, \" +\n \"`remove_niche_keywords`). Free while the niche is fresh; on an older niche you are asked to confirm \" +\n \"the Dive tokens first. `get_niche_keywords` now shows where each keyword came from.\",\n \"See and change which keywords a niche hides (`list_keyword_exclusions`, `add_keyword_exclusions`, \" +\n \"`remove_keyword_exclusions`): exclude one exact keyword, or every keyword containing a phrase. Free.\",\n ],\n },\n {\n version: \"0.16.1\",\n released: \"2026-09-29\",\n items: [\n \"`get_rank_radar_data` returns every tracked keyword of a Rank Radar in one answer again, also in AI \" +\n \"apps that still show its older inputs. Pass `currentPage` / `pageSize` to read one page at a time.\",\n ],\n },\n {\n version: \"0.16.0\",\n released: \"2026-09-28\",\n items: [\n \"Niche research from your own competitor list (`create_niche_dive_from_competitors_list`): give 2 to 200 \" +\n \"ASINs and the niche is built from exactly those products, with no automatic competitor search.\",\n ],\n },\n {\n version: \"0.15.0\",\n released: \"2026-09-28\",\n items: [\n \"Search Query Performance for Rank Radar keywords (`get_rank_radar_sqp_data`): how shoppers search, \" +\n \"click, add to cart and buy, for the whole market and for your product.\",\n \"PPC data for Rank Radar keywords (`get_rank_radar_ppc_data`): sponsored rank, spend, sales, ACOS and \" +\n \"CPC per keyword, optionally broken down by campaign.\",\n \"Your Sponsored Products campaigns (`list_ppc_campaigns`): totals and placement performance per \" +\n \"campaign, sortable by any metric, e.g. the top spenders first.\",\n ],\n },\n {\n version: \"0.14.0\",\n released: \"2026-09-25\",\n items: [\n \"Rank Radar keywords are now read page by page (`get_rank_radar_data`, up to 100 keywords per page), \" +\n \"so large Rank Radars load reliably.\",\n ],\n },\n {\n version: \"0.13.0\",\n released: \"2026-09-09\",\n items: [\n \"Before creating a Rank Radar, you are warned when the same product family is already tracked by \" +\n \"another of your Rank Radars.\",\n ],\n },\n {\n version: \"0.12.0\",\n released: \"2026-09-07\",\n items: [\"Search and sort your niches by name, hero keyword or ASIN (`list_niches`).\"],\n },\n];\n\n/** How many releases the connection-time instructions mention; the prompt lists them all. */\nconst INSTRUCTIONS_RELEASES = 2;\n\nfunction formatEntries(entries: ReadonlyArray<WhatsNewEntry>): string {\n return entries\n .map((e) => `${e.released} (v${e.version}):\\n${e.items.map((i) => `- ${i}`).join(\"\\n\")}`)\n .join(\"\\n\\n\");\n}\n\nexport const SERVER_INSTRUCTIONS =\n \"DataDive tools for Amazon sellers: niche research (niches, keywords, roots, competitors, Ranking Juice), \" +\n \"Rank Radar keyword rank tracking with SQP and PPC data, the seller's catalog, listing changes, inventory, \" +\n \"PPC campaigns and alerts. Start with a discovery tool: `list_niches`, `list_rank_radars` or \" +\n \"`list_seller_profiles`. Tools that spend tokens ask for confirmation first.\\n\\n\" +\n `Recently added:\\n${formatEntries(WHATS_NEW.slice(0, INSTRUCTIONS_RELEASES))}\\n\\n` +\n \"When one of these additions fits what the user is doing, mention it once. The user can see the full \" +\n \"list with the `whats_new` prompt.\";\n\nexport const WHATS_NEW_PROMPT = {\n name: \"whats_new\",\n title: \"What's new in DataDive\",\n description: \"Recent additions to the DataDive tools, newest first.\",\n};\n\n/** The prompt's single user message: the list plus a request to explain it briefly. */\nexport function whatsNewPromptText(): string {\n return (\n \"Here is what is new in the DataDive tools, newest first:\\n\\n\" +\n `${formatEntries(WHATS_NEW)}\\n\\n` +\n \"Summarize this for me in a few short lines, and for each addition give one example question I could ask you.\"\n );\n}\n"],"mappings":"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","names":["KEY_HELP_URL","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","inputSchema","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","z","inputSchema","z","z","inputSchema","z","z","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","inputSchema","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","z","inputSchema","z","entries"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/server.ts","../src/tools/list-niches.ts","../src/http/errors.ts","../package.json","../src/http/client.ts","../src/tools/niche-tag-ids.ts","../src/types/api.ts","../src/tools/list-fast-keywords.ts","../src/tools/get-fast-keywords.ts","../src/tools/get-fast-keywords-keywords.ts","../src/tools/run-fast-keywords.ts","../src/tools/confirm.ts","../src/tools/fast-keywords-run.ts","../src/tools/refresh-fast-keywords.ts","../src/tools/list-spaces.ts","../src/tools/list-labels.ts","../src/tools/add-niche-spaces.ts","../src/tools/remove-niche-spaces.ts","../src/tools/add-niche-labels.ts","../src/tools/remove-niche-labels.ts","../src/tools/get-niche-keywords.ts","../src/tools/list-keyword-exclusions.ts","../src/tools/get-niche-roots.ts","../src/tools/get-niche-competitors.ts","../src/tools/get-ranking-juice.ts","../src/tools/list-rank-radars.ts","../src/tools/rank-radar-keyword-query.ts","../src/tools/get-rank-radar-data.ts","../src/tools/get-rank-radar-sqp-data.ts","../src/tools/get-rank-radar-ppc-data.ts","../src/tools/create-rank-radar.ts","../src/tools/create-niche-dive.ts","../src/tools/create-niche-dive-from-competitors-list.ts","../src/tools/redive-niche.ts","../src/tools/get-dive-status.ts","../src/tools/add-niche-keywords.ts","../src/tools/niche-keyword-list.ts","../src/tools/remove-niche-keywords.ts","../src/tools/keyword-exclusions-input.ts","../src/tools/add-keyword-exclusions.ts","../src/tools/remove-keyword-exclusions.ts","../src/tools/delete-niche.ts","../src/tools/pause-rank-radar.ts","../src/tools/resume-rank-radar.ts","../src/tools/delete-rank-radar.ts","../src/tools/add-rank-radar-search-terms.ts","../src/tools/pause-rank-radar-search-terms.ts","../src/tools/resume-rank-radar-search-terms.ts","../src/tools/list-rank-radar-tags.ts","../src/tools/create-rank-radar-tag.ts","../src/tools/update-rank-radar-tag.ts","../src/tools/delete-rank-radar-tag.ts","../src/tools/add-rank-radar-tag-search-terms.ts","../src/tools/remove-rank-radar-tag-search-terms.ts","../src/tools/generate-listing-copy.ts","../src/tools/get-listing-copy-generation-status.ts","../src/tools/list-seller-profiles.ts","../src/tools/get-seller-catalog.ts","../src/tools/seller-scoped.ts","../src/tools/get-seller-listing-changes.ts","../src/tools/list-ppc-campaigns.ts","../src/tools/get-asin-inventory-distribution.ts","../src/tools/alert-query.ts","../src/tools/list-indexing-issue-alerts.ts","../src/tools/list-blind-spend-alerts.ts","../src/tools/get-quota.ts","../src/tools/list-usage.ts","../src/tools/index.ts","../src/whats-new.ts"],"sourcesContent":["/**\n * Entry point for the datadive-mcp stdio binary.\n *\n * Wired via package.json#bin -> dist/index.js so `npx -y @datadive-tools/mcp` runs it.\n * The shebang is added at build time by tsup.config.ts (banner.js).\n */\n\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { loadConfig } from \"./config.js\";\nimport { buildServer } from \"./server.js\";\n\nasync function main() {\n let config;\n try {\n config = loadConfig();\n } catch (err) {\n // Config errors (e.g. missing API key) are user-facing; print to stderr and exit\n // non-zero so the MCP client surfaces a real error rather than silently disconnecting.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[datadive-mcp] ${msg}\\n`);\n process.exit(1);\n }\n\n const server = buildServer(config);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`[datadive-mcp] fatal: ${msg}\\n`);\n process.exit(1);\n});\n","/**\n * Loads and validates the server's runtime configuration from environment.\n *\n * Required:\n * DATADIVE_API_KEY - your DataDive API key (generate at 2.datadive.tools/api-key)\n *\n * Optional:\n * DATADIVE_API_BASE_URL - override the API base URL (defaults to https://api.datadive.tools)\n * DATADIVE_AUTO_CONFIRM_WRITES - skip the confirm gate on token-spending write tools\n * (create_niche_dive, create_rank_radar). Truthy =\n * \"1\"/\"true\"/\"yes\" (case-insensitive). Defaults to false.\n *\n * The env-based loader always produces api-key credentials (the local stdio path).\n * A remote host embedding this package builds a Config directly with bearer\n * credentials (the OAuth path) — see Credentials.\n */\n\n/** OAuth scope covering the read tools. */\nexport const SCOPE_READ = \"datadive.read\";\n/** OAuth scope covering the token-spending write tools. */\nexport const SCOPE_WRITE = \"datadive.write\";\n\n/**\n * How the HTTP client authenticates against the DataDive /v1 API.\n * - \"api-key\": the local stdio path — sends `x-api-key` (DATADIVE_API_KEY).\n * - \"bearer\": the remote/OAuth path — sends `Authorization: Bearer <token>`.\n * The token is read at request time, so a host may mutate the\n * credentials object to rotate a refreshed access token in place.\n */\nexport type Credentials = { kind: \"api-key\"; apiKey: string } | { kind: \"bearer\"; token: string };\n\nexport interface Config {\n credentials: Credentials;\n baseUrl: string;\n /**\n * When true, the token-spending write tools proceed without requiring `confirm: true`.\n * A user-controlled \"don't ask me again\" opt-out, set via DATADIVE_AUTO_CONFIRM_WRITES.\n */\n autoConfirmWrites: boolean;\n /**\n * OAuth scopes granted to this session. When set, tool calls are gated:\n * read tools require SCOPE_READ and write tools SCOPE_WRITE (see server.ts).\n * Undefined (the stdio/api-key path) means no scope gating.\n */\n scopes?: readonly string[];\n /**\n * The end user's IP address, sent as `x-forwarded-for` so the DataDive API logs and rate-limits\n * the person behind the call rather than the host making it. Set only by the hosted (remote)\n * MCP server, which takes it from its own load balancer; the stdio path leaves it unset.\n */\n forwardedFor?: string;\n /**\n * Extra headers the hosted (remote) MCP server sends on every DataDive API call, e.g. the\n * proxy secret that proves to the API the call came through that server. They can never\n * replace the credential, `accept`, `content-type` or `user-agent` headers this package sets.\n * The stdio path leaves it unset.\n */\n extraHeaders?: Readonly<Record<string, string>>;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.datadive.tools\";\nconst KEY_HELP_URL = \"https://2.datadive.tools/api-key\";\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {\n const apiKey = (env.DATADIVE_API_KEY ?? \"\").trim();\n if (!apiKey) {\n throw new Error(\n `DATADIVE_API_KEY environment variable is required. Generate one at ${KEY_HELP_URL} ` +\n `and add it to your MCP client config under env.DATADIVE_API_KEY.`,\n );\n }\n\n const rawBase = (env.DATADIVE_API_BASE_URL ?? DEFAULT_BASE_URL).trim();\n // Normalize: strip trailing slashes so we can append \"/v1/...\" paths cleanly.\n const baseUrl = rawBase.replace(/\\/+$/, \"\");\n\n const autoConfirmWrites = [\"1\", \"true\", \"yes\"].includes(\n (env.DATADIVE_AUTO_CONFIRM_WRITES ?? \"\").trim().toLowerCase(),\n );\n\n return { credentials: { kind: \"api-key\", apiKey }, baseUrl, autoConfirmWrites };\n}\n","/**\n * Builds the MCP server: registers each tool from the registry with the SDK.\n *\n * Error handling: if a tool handler throws (most commonly an ApiError from the\n * HTTP client), we surface the message back through MCP as a tool-level error\n * (`isError: true`). The LLM sees the message and can react — e.g. tell the\n * user to regenerate their API key on a 401.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { AnyTool } from \"./tools/types.js\";\nimport { SCOPE_READ, SCOPE_WRITE, type Config } from \"./config.js\";\nimport { allTools } from \"./tools/index.js\";\nimport { ApiError } from \"./http/errors.js\";\nimport { PKG_VERSION, takeUpgradeNotice } from \"./http/client.js\";\nimport { SERVER_INSTRUCTIONS, WHATS_NEW_PROMPT, whatsNewPromptText } from \"./whats-new.js\";\n\n/**\n * Returns the OAuth scope a tool call needs: SCOPE_READ for read-only tools,\n * SCOPE_WRITE for the token-spending writes. Classification comes from the\n * `readOnlyHint` annotation, which every tool in the registry carries.\n */\nexport function requiredScope(tool: Pick<AnyTool, \"annotations\">): string {\n return tool.annotations?.readOnlyHint === true ? SCOPE_READ : SCOPE_WRITE;\n}\n\nexport function buildServer(config: Config): McpServer {\n const server = new McpServer(\n {\n name: \"datadive\",\n version: PKG_VERSION,\n },\n { instructions: SERVER_INSTRUCTIONS },\n );\n\n server.registerPrompt(\n WHATS_NEW_PROMPT.name,\n { title: WHATS_NEW_PROMPT.title, description: WHATS_NEW_PROMPT.description },\n () => ({\n messages: [{ role: \"user\" as const, content: { type: \"text\" as const, text: whatsNewPromptText() } }],\n }),\n );\n\n for (const tool of allTools) {\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema,\n annotations: tool.annotations,\n },\n async (args: Record<string, unknown>) => {\n // OAuth scope gate (remote path only — config.scopes is unset over stdio).\n // The backend enforces scopes on /v1 as well; failing here just gives the\n // model a clearer error without a round-trip.\n const scope = requiredScope(tool);\n if (config.scopes && !config.scopes.includes(scope)) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text:\n `Insufficient scope: this tool requires the \"${scope}\" scope, which was not granted ` +\n `to this connection. Reconnect DataDive and enable the missing permission to use it.`,\n },\n ],\n };\n }\n try {\n const data = await tool.handler(args, { config });\n const content = [\n {\n type: \"text\" as const,\n text: JSON.stringify(data, null, 2),\n },\n ];\n // If the backend signaled a newer release, append a one-time upgrade\n // nudge so the model relays it to the user. takeUpgradeNotice() returns\n // null after the first time, so this fires at most once per session.\n const notice = takeUpgradeNotice();\n if (notice) {\n content.push({\n type: \"text\" as const,\n text: `⚠️ datadive-mcp update available — ${notice}`,\n });\n }\n return { content };\n } catch (err) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text:\n err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err),\n },\n ],\n };\n }\n },\n );\n }\n\n return server;\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheList } from \"../types/api.js\";\nimport { tagFilterIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; larger values are rejected there with 400. */\nexport const LIST_NICHES_MAX_PAGE_SIZE = 50;\n\n/**\n * `searchText`, `searchAsin`, `orderBy` and `sortOrder` are forwarded to `GET /v1/niches`, which\n * accepts them since the 2026-09-07 API release. They were withdrawn on 2026-08-28 because the\n * endpoint bound only `currentPage`/`pageSize` and dropped unknown query params silently, so they\n * filtered nothing; the same release also fixed paging, which TypeORM had been discarding on the\n * joined builder (RS-11494). Do not re-add a filter here before the route reads it. `spaces` and `labels`\n * (RS-12029) are read by the route and sent as repeated keys, which is what its array parsing accepts.\n */\nconst inputSchema = {\n currentPage: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(\"Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_NICHES_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`),\n searchText: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe(\n \"Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging \" +\n \"through everything when the user names a niche.\",\n ),\n searchAsin: z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9]{10}$/, \"must be a 10-character ASIN\")\n .optional()\n .describe(\"Only niches whose competitor set contains this ASIN (10 characters, e.g. B08N5WRWNW).\"),\n orderBy: z\n .enum([\"lastDived\", \"name\"])\n .optional()\n .describe(\n \"Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` \" +\n \"(by nicheLabel, A→Z by default). Defaults to `lastDived`.\",\n ),\n sortOrder: z\n .enum([\"ASC\", \"DESC\"])\n .optional()\n .describe(\"Sort direction. Defaults to DESC for `lastDived` and ASC for `name`.\"),\n spaces: tagFilterIdList(\"spaces\")\n .optional()\n .describe(\n \"Only niches in at least one of these Spaces (ids from `list_spaces`, up to 100). Combines with \" +\n \"`labels` and the other filters with AND.\",\n ),\n labels: tagFilterIdList(\"labels\")\n .optional()\n .describe(\n \"Only niches with at least one of these labels (ids from `list_labels`, up to 100). Combines with \" +\n \"`spaces` and the other filters with AND.\",\n ),\n};\n\nexport const listNichesTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_niches\",\n title: \"List DataDive Niches\",\n description:\n \"Use this first when the user asks about their niches, or to find a `nicheId` for use with \" +\n \"`get_niche_keywords`, `get_niche_competitors`, or `get_ranking_juice`. \" +\n \"Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment \" +\n \"or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace \" +\n \"(com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, \" +\n \"pageSize, total, lastPage, hasNext, hasPrev). Narrow with `searchText` (label/keyword) or \" +\n \"`searchAsin` (a competitor ASIN), `spaces` or `labels` (ids from `list_spaces` / `list_labels`) instead of \" +\n \"scanning pages; each niche lists the ids of its `spaces` and `labels`. A page holds at most \" +\n `${LIST_NICHES_MAX_PAGE_SIZE} niches, so the full list of a large account needs several calls.`,\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheList>(\n { config: ctx.config, toolName: \"list_niches\" },\n \"/v1/niches\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n searchText: args.searchText,\n searchAsin: args.searchAsin,\n orderBy: args.orderBy,\n sortOrder: args.sortOrder,\n spaces: args.spaces,\n labels: args.labels,\n },\n );\n },\n};\n","/**\n * Error mapping for the DataDive /v1 API.\n *\n * The MCP server is a thin HTTP shim — when api.datadive.tools returns an error,\n * we want the LLM (and ultimately the user) to see a useful, actionable message\n * rather than \"Request failed\". `ApiError.fromHttp` turns an HTTP status + body\n * into one of those messages.\n *\n * Source of truth for status semantics:\n * datadive-backend/src/external-api/auth/api-key.guard.ts (401/402 logic)\n * datadive-backend/src/external-api/external-api-v1.controller.ts (per-route docs)\n * datadive-backend/src/microservices/token/quota-exceeded.error.ts (QUOTA_EXCEEDED body)\n */\n\nconst KEY_HELP_URL = \"https://2.datadive.tools/api-key\";\n\nexport type ApiErrorKind =\n | \"auth\"\n | \"payment\"\n | \"quota\"\n | \"forbidden\"\n | \"not_found\"\n | \"bad_request\"\n | \"rate_limit\"\n | \"server\"\n | \"network\"\n | \"http\";\n\n/**\n * Body the backend returns whenever a billable action would exceed the subscription's quota,\n * regardless of the HTTP status (400 for most features, 403 for the AI Copywriter). `error` is the\n * stable discriminant; the usage fields are present only when the throw site knows them.\n */\nexport interface QuotaExceededBody {\n error: \"QUOTA_EXCEEDED\";\n message: string;\n subscriptionUrl: string;\n feature?: string;\n used?: number | null;\n capacity?: number | null;\n nextRefreshDate?: string | null;\n}\n\n/** Display names for the backend's `BillableFeature` values; unknown values fall through verbatim. */\nconst FEATURE_LABELS: Record<string, string> = {\n DIVED_ASINS: \"Dive tokens\",\n RANK_RADAR_KEYWORDS: \"Rank Radar tracked keywords\",\n PRODUCT_BRIEF_ASINS: \"Product Brief ASINs\",\n AI_COPYWRITER_PROMPTS: \"AI Copywriter prompts\",\n INDEXING_DIAGNOSIS: \"Indexing Diagnosis checks\",\n};\n\nexport class ApiError extends Error {\n public readonly kind: ApiErrorKind;\n public readonly status: number;\n public readonly body: unknown;\n\n constructor(kind: ApiErrorKind, status: number, message: string, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.kind = kind;\n this.status = status;\n this.body = body;\n }\n\n static fromHttp(status: number, body: unknown): ApiError {\n // Checked before the status branches: the backend uses 400 or 403 depending on the\n // feature, and neither generic mapping would tell the model what ran out or where to go.\n if (isQuotaExceededBody(body)) {\n return new ApiError(\"quota\", status, quotaExceededMessage(body), body);\n }\n\n const serverMsg = extractMessage(body);\n\n if (status === 400) {\n return new ApiError(\n \"bad_request\",\n 400,\n serverMsg ? `Bad request: ${serverMsg}` : \"Bad request: invalid parameters.\",\n body,\n );\n }\n if (status === 401) {\n return new ApiError(\n \"auth\",\n 401,\n `Authentication failed: your DATADIVE_API_KEY is invalid or expired. Generate a new key at ${KEY_HELP_URL}.`,\n body,\n );\n }\n if (status === 402) {\n return new ApiError(\n \"payment\",\n 402,\n \"Subscription is inactive or paused. Resume billing at https://2.datadive.tools to use the API.\",\n body,\n );\n }\n if (status === 403) {\n return new ApiError(\n \"forbidden\",\n 403,\n serverMsg\n ? `Forbidden: ${serverMsg}`\n : \"Forbidden: this API key cannot access the requested resource.\",\n body,\n );\n }\n if (status === 404) {\n return new ApiError(\"not_found\", 404, serverMsg ? `Not found: ${serverMsg}` : \"Resource not found.\", body);\n }\n if (status === 429) {\n return new ApiError(\"rate_limit\", 429, \"Rate limit exceeded. Wait a few seconds and retry.\", body);\n }\n if (status >= 500) {\n return new ApiError(\n \"server\",\n status,\n `DataDive API error (${status}). ${serverMsg ?? \"Please try again.\"}`.trim(),\n body,\n );\n }\n return new ApiError(\"http\", status, serverMsg ?? `HTTP ${status}`, body);\n }\n}\n\nexport function isQuotaExceededBody(body: unknown): body is QuotaExceededBody {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return b.error === \"QUOTA_EXCEEDED\" && typeof b.subscriptionUrl === \"string\";\n}\n\n/**\n * Deterministic, server-data-only wording for a quota error. The model relays this instead of\n * improvising: it names the exhausted feature, states usage when known, points at the\n * informational subscription page, and tells the model not to retry. It deliberately does not\n * quote prices or push a specific upgrade — the connector directories only allow explaining that\n * a feature needs a different plan and linking to a plan page.\n */\nexport function quotaExceededMessage(body: QuotaExceededBody): string {\n const lead = typeof body.message === \"string\" && body.message.length > 0 ? body.message : \"Quota exceeded\";\n const feature = body.feature ? (FEATURE_LABELS[body.feature] ?? body.feature) : null;\n\n const parts: string[] = [];\n if (feature) {\n parts.push(`This subscription has used up its ${feature} quota${usageSuffix(body)}.`);\n } else {\n parts.push(`This subscription has used up its quota for this action${usageSuffix(body)}.`);\n }\n if (body.nextRefreshDate) {\n parts.push(`It refreshes on ${body.nextRefreshDate.slice(0, 10)}.`);\n }\n parts.push(\n `Tell the user which quota ran out and that they can review or raise it on their subscription page: ${body.subscriptionUrl}`,\n );\n parts.push(\"Do not retry this call until the quota has been raised.\");\n\n return `${lead}. ${parts.join(\" \")}`;\n}\n\nfunction usageSuffix(body: QuotaExceededBody): string {\n if (typeof body.used === \"number\" && typeof body.capacity === \"number\") {\n return ` (${body.used} of ${body.capacity} used)`;\n }\n return \"\";\n}\n\nfunction extractMessage(body: unknown): string | null {\n if (!body || typeof body !== \"object\") return null;\n const b = body as Record<string, unknown>;\n // NestJS conventional error shapes: { message: string | string[] } or { error, message }.\n if (typeof b.message === \"string\") return b.message;\n if (Array.isArray(b.message) && b.message.every((m) => typeof m === \"string\")) {\n return (b.message as string[]).join(\"; \");\n }\n if (typeof b.error === \"string\") return b.error;\n return null;\n}\n","{\n \"name\": \"@datadive-tools/mcp\",\n \"version\": \"0.20.0\",\n \"description\": \"MCP server for DataDive — query your niches, keywords, competitors, and Rank Radar data from Claude Desktop, Claude Code, or Cursor.\",\n \"type\": \"module\",\n \"bin\": {\n \"datadive-mcp\": \"./dist/index.js\"\n },\n \"main\": \"./dist/lib.js\",\n \"types\": \"./dist/lib.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"engines\": {\n \"node\": \">=22\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:coverage\": \"vitest run --coverage --coverage.include=\\\"src/**\\\" --coverage.reporter=json-summary --coverage.reporter=text\",\n \"test:watch\": \"vitest\",\n \"test:smoke\": \"DATADIVE_SMOKE=1 vitest run test/smoke.test.ts\",\n \"lint\": \"eslint .\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run lint && npm run typecheck && npm run test && npm run build\",\n \"prepare\": \"npm run build\"\n },\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"datadive\",\n \"amazon\",\n \"claude\"\n ],\n \"author\": \"DataDive\",\n \"license\": \"MIT\",\n \"homepage\": \"https://github.com/Data-Dive-Tools/datadive-mcp\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Data-Dive-Tools/datadive-mcp.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Data-Dive-Tools/datadive-mcp/issues\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.0.0\",\n \"zod\": \"^3.23.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.0\",\n \"@types/node\": \"^20.0.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n \"@typescript-eslint/parser\": \"^8.0.0\",\n \"@vitest/coverage-v8\": \"4.1.8\",\n \"eslint\": \"^9.0.0\",\n \"prettier\": \"^3.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"~5.9.0\",\n \"vitest\": \"4.1.8\"\n }\n}\n","/**\n * HTTP client for the DataDive /v1 API.\n *\n * Responsibilities:\n * 1. Attach auth from config.credentials to every request — `x-api-key` (local\n * stdio path) or `Authorization: Bearer` (remote/OAuth path).\n * 2. Tag the User-Agent with the package version + tool name so backend access\n * logs can attribute usage per tool (no separate telemetry endpoint needed).\n * 3. Map HTTP errors via `ApiError.fromHttp`.\n * 4. Unwrap the `ResponseDto<T>` envelope (`{ message?, success?, data }`)\n * while preserving bare `PaginationResponse<T>` bodies (which need their\n * pagination metadata to reach the LLM).\n *\n * Envelope detection (verified against datadive-backend):\n * - GET /v1/niches -> NicheList extends PaginationResponse<NicheItem> (BARE)\n * - GET /v1/niches/rank-radars -> ResponseDto<ExternalRankRadarListResponseDto>\n * where the inner DTO IS itself paginated.\n * - All other read endpoints -> ResponseDto<T> (UNWRAP)\n *\n * Heuristic: if the body has `data` AND any pagination key (currentPage, lastPage,\n * hasNext, hasPrev, pageSize, total), it's a PaginationResponse — return as-is.\n * Otherwise if the body has `data`, it's a ResponseDto envelope — return body.data.\n *\n * Cite: datadive-backend/src/common/pagination/pagination.dto.ts (pagination keys)\n * datadive-backend/src/common/dto/response.dto.ts (envelope shape)\n */\n\nimport type { Config } from \"../config.js\";\nimport { ApiError } from \"./errors.js\";\nimport { version } from \"../../package.json\";\n\n/**\n * The running package version, inlined from package.json at build time by\n * tsup/esbuild (and by vite under vitest). Single source of truth — no manual\n * bump here, so it can never drift from what's published to npm.\n *\n * Written into the User-Agent header, and compared against the backend's\n * advertised latest version (see noteLatestVersion) to nudge stale installs.\n */\nexport const PKG_VERSION: string = version;\n\n/** Response header the DataDive backend uses to advertise the latest published MCP version. */\nconst LATEST_VERSION_HEADER = \"x-datadive-mcp-latest\";\n\n/**\n * One-time upgrade notice. Armed when the backend advertises a version newer than\n * the one we're running; consumed (and then suppressed for the rest of the session)\n * by takeUpgradeNotice(). We surface it once per process so we don't nag on every call.\n */\nlet pendingUpgradeNotice: string | null = null;\nlet hasWarned = false;\n\n/**\n * Compares dotted version strings (major.minor.patch). Pre-release / build suffixes\n * are stripped — a stable release is the signal we act on. Returns true iff `latest`\n * is strictly newer than `current`. Exported for unit testing.\n */\nexport function isNewerVersion(latest: string, current: string): boolean {\n const parse = (v: string) => v.split(/[-+]/)[0]!.split(\".\").map((n) => parseInt(n, 10) || 0);\n const a = parse(latest);\n const b = parse(current);\n for (let i = 0; i < 3; i++) {\n const da = a[i] ?? 0;\n const db = b[i] ?? 0;\n if (da !== db) return da > db;\n }\n return false;\n}\n\n/** Records the advertised latest version; arms a one-time notice if we're behind. Never throws. */\nfunction noteLatestVersion(latest: string | null): void {\n if (!latest || hasWarned) return;\n if (isNewerVersion(latest, PKG_VERSION)) {\n pendingUpgradeNotice =\n `A newer version of datadive-mcp is available (${latest}; you are running ${PKG_VERSION}). ` +\n `Update by reinstalling the latest from npm — e.g. \\`npm install -g @datadive-tools/mcp@latest\\` — ` +\n `or bump the @datadive-tools/mcp version in your MCP client config, then restart the client.`;\n }\n}\n\n/**\n * Returns a pending upgrade notice once, then suppresses further notices for the rest\n * of the session. Called when assembling a tool result so the LLM can relay it to the\n * user. Returns null when there's nothing to surface.\n */\nexport function takeUpgradeNotice(): string | null {\n if (!pendingUpgradeNotice) return null;\n const notice = pendingUpgradeNotice;\n pendingUpgradeNotice = null;\n hasWarned = true;\n return notice;\n}\n\nconst PAGINATION_KEYS = [\"currentPage\", \"lastPage\", \"hasNext\", \"hasPrev\", \"pageSize\", \"total\"] as const;\n\nexport interface RequestContext {\n config: Config;\n /** Tool name used to tag the User-Agent header for per-tool log attribution. */\n toolName: string;\n}\n\nexport async function httpGet<T>(ctx: RequestContext, path: string, query?: Record<string, unknown>): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path, query);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"GET\",\n headers: {\n ...authHeaders(ctx.config),\n ...hostHeaders(ctx.config),\n accept: \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * POST a JSON body to a /v1 write endpoint. Mirrors httpGet (same auth/UA headers,\n * version notice, error mapping, and envelope unwrapping) but adds a JSON request\n * body. The create endpoints return bare objects (no ResponseDto envelope), which\n * unwrap() passes through untouched.\n */\nexport async function httpPost<T>(ctx: RequestContext, path: string, body: unknown): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...authHeaders(ctx.config),\n ...hostHeaders(ctx.config),\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n body: JSON.stringify(body),\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * PATCH a JSON body to a /v1 write endpoint. Mirrors httpPost (same auth/UA headers,\n * version notice, error mapping, envelope unwrapping) but uses PATCH for partial\n * updates (`update_rank_radar_tag`, RS-11822). Backend returns the updated resource\n * wrapped in the usual ResponseDto envelope.\n */\nexport async function httpPatch<T>(ctx: RequestContext, path: string, body: unknown): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"PATCH\",\n headers: {\n ...authHeaders(ctx.config),\n ...hostHeaders(ctx.config),\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n body: JSON.stringify(body),\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\n/**\n * DELETE a /v1 resource. Same auth/UA headers, version notice and error mapping as\n * httpGet. The delete endpoints answer 204 No Content, so handleResponse resolves to\n * `null` — callers return their own confirmation payload rather than that null.\n */\nexport async function httpDelete<T>(ctx: RequestContext, path: string): Promise<T> {\n const url = buildUrl(ctx.config.baseUrl, path);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"DELETE\",\n headers: {\n ...authHeaders(ctx.config),\n ...hostHeaders(ctx.config),\n accept: \"application/json\",\n \"user-agent\": userAgent(ctx.toolName),\n },\n });\n } catch (e) {\n throw networkError(url, e);\n }\n\n return handleResponse<T>(res);\n}\n\nfunction networkError(url: string, e: unknown): ApiError {\n const msg = e instanceof Error ? e.message : String(e);\n return new ApiError(\"network\", 0, `Network error reaching ${url}: ${msg}`);\n}\n\n/**\n * Shared response handling for httpGet/httpPost: arm the one-time upgrade notice,\n * parse the body, map HTTP errors via ApiError, and unwrap the ResponseDto envelope.\n */\nasync function handleResponse<T>(res: Response): Promise<T> {\n // The backend advertises the latest published MCP version on every response\n // (including errors); arm a one-time upgrade notice if we're behind.\n noteLatestVersion(res.headers.get(LATEST_VERSION_HEADER));\n\n const text = await res.text();\n let body: unknown;\n try {\n body = text.length > 0 ? JSON.parse(text) : null;\n } catch {\n body = text;\n }\n\n if (!res.ok) {\n throw ApiError.fromHttp(res.status, body);\n }\n\n return unwrap<T>(body);\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, unknown>): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n const u = new URL(`${baseUrl}${normalizedPath}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) {\n // One `k=a&k=b` pair per item: `String([1, 2])` would send `k=1,2`, which the API reads as one bad id.\n for (const item of v) u.searchParams.append(k, String(item));\n continue;\n }\n u.searchParams.set(k, String(v));\n }\n }\n return u.toString();\n}\n\nfunction userAgent(toolName: string): string {\n return `datadive-mcp/${PKG_VERSION} tool/${toolName}`;\n}\n\n/**\n * Auth header(s) for the configured credentials: `x-api-key` on the local stdio\n * path, `Authorization: Bearer` on the remote/OAuth path. The credentials object\n * is read per request so a host can rotate a refreshed bearer token in place.\n */\nfunction authHeaders(config: Config): Record<string, string> {\n const c = config.credentials;\n return c.kind === \"bearer\" ? { authorization: `Bearer ${c.token}` } : { \"x-api-key\": c.apiKey };\n}\n\n/** Headers this package always sets itself; `extraHeaders` cannot override them. */\nconst RESERVED_HEADERS = new Set([\"authorization\", \"x-api-key\", \"accept\", \"content-type\", \"user-agent\"]);\n\n/** Headers the hosting server adds: the end user's IP and any `extraHeaders`. Empty on the stdio path. */\nfunction hostHeaders(config: Config): Record<string, string> {\n const extra = Object.entries(config.extraHeaders ?? {}).filter(([name]) => !RESERVED_HEADERS.has(name.toLowerCase()));\n return {\n ...Object.fromEntries(extra),\n ...(config.forwardedFor ? { \"x-forwarded-for\": config.forwardedFor } : {}),\n };\n}\n\n/**\n * Unwraps a ResponseDto<T> envelope while preserving bare PaginationResponse bodies.\n * Exported for unit testing.\n */\nexport function unwrap<T>(body: unknown): T {\n if (!isObject(body)) return body as T;\n if (!(\"data\" in body)) return body as T;\n // If pagination metadata is present at the top level, return as-is.\n for (const key of PAGINATION_KEYS) {\n if (key in body) return body as T;\n }\n // Plain ResponseDto envelope -> unwrap.\n return (body as { data: T }).data;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","/**\n * Id-list input shared by the niche Space and label write tools, and by the `spaces` / `labels`\n * filters of the list tools. The API takes 1-100 unique positive integers; checking that here turns\n * a mistake into a schema error before a call, and these routes allow only 2 writes per minute per niche.\n */\n\nimport { z } from \"zod\";\nimport { NICHE_TAG_IDS_MAX_PER_CALL } from \"../types/api.js\";\n\n/** A fresh schema per use: one instance shared by two fields is emitted as a `$ref`, which some MCP clients do not resolve. */\nconst tagId = () => z.number().int().min(1);\n\n/** Rejects a list holding the same id twice. */\nfunction unique<T extends z.ZodType<number[]>>(schema: T, field: string) {\n return schema.refine((ids) => new Set(ids).size === ids.length, {\n message: `${field} must not contain the same id twice`,\n });\n}\n\n/** 1-100 unique ids, for the write tools. */\nexport const nicheTagIdList = (field: string) =>\n unique(z.array(tagId()).min(1).max(NICHE_TAG_IDS_MAX_PER_CALL), field);\n\n/** Up to 100 unique ids, for the list filters. */\nexport const tagFilterIdList = (field: string) =>\n unique(z.array(tagId()).max(NICHE_TAG_IDS_MAX_PER_CALL), field);\n\n/** The rules every write tool shares, appended to each description. */\nexport const NICHE_TAG_WRITE_RULES =\n \"Only the organization owner or a team admin can change a niche's Spaces and labels; anyone else is \" +\n \"refused with a 403 (INSUFFICIENT_ROLE), so tell the user instead of retrying. A change is all or nothing: \" +\n \"if any id is unknown, or a Space is one the user has only view rights on, the whole call fails and \" +\n \"nothing changes (the error lists the offending ids). Ids already in the wanted state are counted, \" +\n \"never errors, so a repeat is safe. Costs nothing. Limited to 2 calls per minute per niche, so put \" +\n \"every id in one call.\";\n","/**\n * Hand-mirrored DTOs from the DataDive backend (v1 external API).\n *\n * Source of truth (in this priority order):\n * 1. Live Swagger: https://developer.datadive.tools/docs#/v1\n * (machine-readable: https://developer.datadive.tools/docs-json)\n * 2. Backend DTOs: datadive-backend/src/external-api/dto/*.ts\n * datadive-backend/src/niche-research/dto/rank-radar.response.dto.ts\n * datadive-backend/src/common/dto/response.dto.ts\n * datadive-backend/src/common/pagination/pagination.dto.ts\n * 3. Controller: datadive-backend/src/external-api/external-api-v1.controller.ts\n *\n * Last synced: 2026-06-19.\n *\n * The MCP server forwards JSON straight to the LLM, so deeply-nested DTOs are\n * intentionally typed loosely (with `unknown` or pass-through Records) where\n * the TS surface doesn't add safety. Pin tighter as use cases require.\n */\n\n// ─── Envelope + pagination ───────────────────────────────────────────────────\n\n/** Generic NestJS response envelope: { message?, success?, data? }. */\nexport interface ResponseEnvelope<T> {\n message?: string;\n /** @deprecated — use HTTP status as the success indicator. */\n success?: boolean;\n data?: T | null;\n}\n\n/** Standard pagination envelope returned by list endpoints. */\nexport interface PaginationResponse<T> {\n data: T[];\n currentPage: number;\n pageSize: number;\n total: number;\n lastPage: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/**\n * ISO-8601 date or timestamp, mirroring the backend's `@IsISO8601()` (date-only\n * allowed). Shared by the date/timestamp query params across /v1 tools\n * (alerts `updatedSince`, usage `startDate`/`endDate`).\n */\nexport const ISO_8601 = /^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/**\n * Marketplace codes accepted by `marketplace` params across /v1 endpoints\n * (alerts, inventory). Mirrors the backend's `SupportedMarketplaceEnum`.\n */\nexport const SUPPORTED_MARKETPLACES = [\n \"com\",\n \"ca\",\n \"co.uk\",\n \"com.mx\",\n \"in\",\n \"fr\",\n \"de\",\n \"es\",\n \"it\",\n \"co.jp\",\n] as const;\n\n// ─── /v1/niches (NicheList = PaginationResponse<NicheItem>) ─────────────────\n\nexport interface NicheItem {\n nicheId: string;\n heroKeyword: string;\n nicheLabel: string;\n /** Amazon marketplace TLD: \"com\", \"uk\", \"de\", etc. */\n marketplace: string;\n /** ISO date string when the Niche was last researched. Nullable. */\n latestResearchDate: string | null;\n /** Ids of the Spaces the niche is in (see `list_spaces`). */\n spaces: number[];\n /** Ids of the labels on the niche (see `list_labels`). */\n labels: number[];\n}\n\nexport type NicheList = PaginationResponse<NicheItem>;\n\n// ─── Spaces and labels (GET /v1/spaces, GET /v1/labels, ExternalTagNodeDto, wrapped) ─\n\n/** Mirrors the API's cap on ids per request and per list filter. */\nexport const NICHE_TAG_IDS_MAX_PER_CALL = 100;\n\nexport interface TagNode {\n id: number;\n name: string;\n /** The names from the root down to this node, joined with `/`. */\n path: string;\n parentId: number | null;\n children: TagNode[];\n}\n\n// ─── POST /v1/niches/:nicheId/(spaces|labels)[/remove] (wrapped) ─────────────\n\nexport interface AddNicheTagsResult {\n added: number;\n /** Already attached; left untouched. */\n alreadyPresent: number;\n}\n\nexport interface RemoveNicheTagsResult {\n removed: number;\n /** Not attached; left untouched. */\n notPresent: number;\n}\n\n// ─── /v1/niches/:nicheId/keywords (GetMasterKeywordListResult, wrapped) ─────\n\n/** Map of competitor ASIN -> rank (or null when unranked). */\nexport type AsinRanks = Record<string, number | null>;\n\n/**\n * How a keyword got into the Master Keyword List: `mkl` found by the dive, `manual` added by hand,\n * `outlier` / `residue` moved in from those lists. The last three are what `remove_niche_keywords`\n * can take back out.\n */\nexport type MklKeywordOrigin = \"mkl\" | \"manual\" | \"outlier\" | \"residue\";\n\nexport interface MasterKeyword {\n keyword: string;\n searchVolume: number;\n /** \"Outlier\" or a numeric relevancy score. */\n relevancy: string | number;\n asinRanks: AsinRanks;\n origin: MklKeywordOrigin;\n}\n\nexport interface GetMasterKeywordListResult {\n keywords: MasterKeyword[];\n /** ISO date string. */\n latestResearchDate: string;\n}\n\n// ─── POST /v1/niches/:nicheId/keywords (AddNicheKeywordsResponseDto, wrapped) ─\n\n/** Mirrors the API's `ADD_MANUAL_KEYWORDS_MAX_LIMIT` and `MANUAL_KEYWORD_MAX_LENGTH`. */\nexport const NICHE_KEYWORDS_MAX_PER_CALL = 100;\nexport const NICHE_KEYWORD_MAX_LENGTH = 255;\n\nexport interface AddNicheKeywordsResult {\n /** Every submitted keyword, in exactly one group. */\n originBreakdown: {\n alreadyInMkl: string[];\n takenFromOutliers: string[];\n takenFromResidue: string[];\n addedManually: string[];\n };\n /** Dive tokens spent (or, on a dry run, the most the re-dive can cost). 0 for a fresh niche. */\n tokensUsed: number;\n /** Present only when the add re-dived a stale niche; poll `get_dive_status` with `diveId`. */\n refresh?: { diveId: string; estimatedCompletionDate: string };\n}\n\n/** 400 body when a stale-niche add would cost more than `maxTokensUsed`. Nothing is saved or charged. */\nexport interface MaxTokensExceededBody {\n error: \"MAX_TOKENS_EXCEEDED\";\n message: string;\n maxTokensUsed: number;\n tokensUsed: number;\n}\n\n// ─── POST /v1/niches/:nicheId/keywords/remove (RemoveNicheKeywordsResponseDto, wrapped) ─\n\nexport interface RemoveNicheKeywordsResult {\n removedManual: string[];\n sentBackToOutliers: string[];\n sentBackToResidue: string[];\n /** Found by the dive itself, or not in the niche at all. Left unchanged. */\n notRemovable: string[];\n}\n\n// ─── /v1/niches/:nicheId/keyword-exclusions (KeywordExclusionsDto, wrapped) ──\n\n/** Mirrors the API's `KEYWORD_EXCLUSIONS_MAX_PER_REQUEST` and `KEYWORD_EXCLUSIONS_MAX_PER_NICHE`. */\nexport const KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;\nexport const KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1000;\n\nexport interface KeywordExclusions {\n /** Oldest first. `searchVolume` is 0 when the niche has no such keyword. */\n exact: Array<{ keyword: string; searchVolume: number }>;\n /** Oldest first. Volume and count of the keywords the phrase hides, besides exact-excluded ones. */\n phrase: Array<{ phrase: string; searchVolume: number; excludedKeywordsCount: number }>;\n}\n\nexport interface KeywordExclusionEntries {\n exact: string[];\n phrase: string[];\n}\n\n/** POST /v1/niches/:nicheId/keyword-exclusions: the lists after the add, plus what changed. */\nexport interface AddKeywordExclusionsResult extends KeywordExclusions {\n added: KeywordExclusionEntries;\n /** Already excluded; left as is. */\n unchanged: KeywordExclusionEntries;\n}\n\n/** POST /v1/niches/:nicheId/keyword-exclusions/remove: the lists after the remove, plus what changed. */\nexport interface RemoveKeywordExclusionsResult extends KeywordExclusions {\n removed: KeywordExclusionEntries;\n /** Were not excluded; left as is. */\n unchanged: KeywordExclusionEntries;\n}\n\n// ─── /v1/niches/:nicheId/competitors (GetCompetitorListResult, wrapped) ─────\n\n/**\n * A single Competitor row. The fields documented here are the ones worth naming for\n * a consumer; the index signature keeps the rest of the payload (keyword and ads\n * diagnostics, evaluation labels) available to the LLM without enumerating it.\n */\nexport interface Competitor {\n asin: string;\n /** Product title. */\n title: string;\n /** Best Sellers Rank. Null when Amazon publishes none for the product. */\n bsr: number | null;\n /** Amazon category name. Null when unknown. */\n category: string | null;\n /** Full category path, e.g. \"Pet Supplies > Dogs > Apparel & Accessories\". Null when unknown. */\n categoryTree: string | null;\n brand: string | null;\n price: number | null;\n rating: number | null;\n reviewCount: number | null;\n sales: number | null;\n revenue: number | null;\n numberOfVariations: number;\n /**\n * Total Ranking Juice for this listing. Use `get_ranking_juice` for the breakdown\n * by title, bullets and description.\n */\n listingRankingJuice: { value: number };\n [key: string]: unknown;\n}\n\n/**\n * Niche competitors response — several nested DTOs from the backend (MklStatisticsData,\n * OpportunityEvaluation, MklBenchmark, CompetitorsStrength). We type the outer shape and\n * the competitor rows, and leave the niche-level aggregates as opaque records; the LLM\n * sees the full JSON and can reason about it. Tighten if a specific consumer needs it.\n */\nexport interface GetCompetitorListResult {\n /** Marketplace enum (AmazonStore in backend). */\n marketplace: string;\n statistics: Record<string, unknown>;\n opportunityEvaluation: Record<string, unknown>;\n benchmark: Record<string, unknown>;\n competitorsStrength: Record<string, unknown>;\n competitors: Competitor[];\n latestResearchDate: string;\n}\n\n// ─── /v1/niches/:nicheId/ranking-juices (NicheRankingJuices, wrapped) ───────\n\nexport interface RankingJuiceProperty {\n rankingJuice: number;\n}\n\nexport interface ListingRankingJuice {\n rankingJuice: number;\n title: RankingJuiceProperty;\n bullets: RankingJuiceProperty;\n description: RankingJuiceProperty;\n}\n\nexport interface CompetitorRankingJuice {\n asin: string;\n listing: ListingRankingJuice;\n}\n\nexport interface NicheRankingJuices {\n currentListing: ListingRankingJuice;\n optimizedListing: ListingRankingJuice;\n competitors: CompetitorRankingJuice[];\n latestResearchDate: string;\n}\n\n// ─── /v1/niches/rank-radars (ExternalRankRadarListResponseDto, wrapped) ─────\n\n/**\n * States a Rank Radar can be reported in. Matches the backend's `ExternalRankRadarStatus`, so a\n * value read out of a response can be fed straight back into the `status` filter.\n *\n * `PAUSED` is what the API's own archive endpoint produces (tracking stopped, data kept);\n * `ARCHIVED` is a Rank Radar that was deleted.\n */\nexport const RANK_RADAR_STATES = [\"ACTIVE\", \"PAUSED\", \"ARCHIVED\"] as const;\nexport type RankRadarState = (typeof RANK_RADAR_STATES)[number];\n\n/**\n * Accepted values of the `status` query param on /v1/niches/rank-radars. `ALL` is filter-only —\n * it selects active and paused together and never appears as an item's own status.\n *\n * The API rejects anything else with 400 (datadive-backend `ExternalKeywordRankTrackerStatus`).\n * Before RS-11518 it accepted only `ALL`/`PAUSED` and silently answered anything else with the\n * active set, so this list must not be widened ahead of the API.\n */\nexport const RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, \"ALL\"] as const;\nexport type RankRadarStatus = (typeof RANK_RADAR_STATUSES)[number];\n\n/** A row exactly as `/v1/niches/rank-radars` sends it, before the tool minimizes it. */\nexport interface ApiRankRadarItem {\n id: string;\n status: RankRadarState;\n /** Only `asin` carries information; `id`/`krtId` are internal row identifiers. */\n asin: { id: string; krtId: string; asin: string };\n marketplace: string;\n keywordCount: number;\n title: string;\n imageUrl: string;\n top10KW: number | null;\n top10SV: number | null;\n top50KW: number | null;\n top50SV: number | null;\n}\n\n/** A row as `list_rank_radars` returns it: `asin` flattened to the ASIN, no internal identifiers. */\nexport interface RankRadarItem extends Omit<ApiRankRadarItem, \"asin\"> {\n asin: string;\n}\n\nexport type ApiRankRadarList = PaginationResponse<ApiRankRadarItem>;\nexport type RankRadarList = PaginationResponse<RankRadarItem>;\n\n// ─── POST /v1/niches/rank-radars (CreateRankRadarSuccessResponseDto, bare) ──\n\n/**\n * A non-blocking notice the API attaches to an otherwise successful write (RS-11615).\n * `message` is customer-facing prose composed by the backend so every surface — the\n * DataDive UI, the public API and this server — words it identically; show it verbatim\n * rather than paraphrasing it. Treat an unrecognised `code` as informational.\n */\nexport interface ApiWarning {\n /** Machine-readable code, e.g. `FAMILY_ALREADY_TRACKED`. */\n code: string;\n message: string;\n /** Ids of the existing resources the warning refers to, when it names any. */\n existingRankRadarIds?: string[];\n}\n\n/** Result of creating a Rank Radar. */\nexport interface CreateRankRadarResult {\n /** `null` when the request was a dry run, because nothing was created. */\n rankRadarId: string | null;\n /** Non-blocking notices about this creation. Empty or absent when there is nothing to flag. */\n warnings?: ApiWarning[];\n}\n\n// ─── POST /v1/niches/dives (CreateNicheDiveSuccessResponseDto, bare) ────────\n\n/** Result of kicking off a Niche Dive. Poll get_dive_status with `diveId`. */\nexport interface CreateNicheDiveResult {\n diveId: string;\n /** ISO-8601 estimated completion timestamp. */\n estimatedCompletionDate: string;\n}\n\n// ─── POST /v1/niches/:nicheId/redive (RediveNicheResponseDto, wrapped) ──────\n\n/**\n * Result of re-diving an existing niche. Same shape as CreateNicheDiveResult and\n * polled the same way, but the niche keeps its existing `nicheId` — so unlike a new\n * dive, `get_dive_status` success carries no id the caller did not already have.\n */\nexport interface RediveNicheResult {\n diveId: string;\n /** ISO-8601 estimated completion timestamp. */\n estimatedCompletionDate: string;\n}\n\n// ─── GET /v1/niches/dives/:diveId (NicheDiveStatus*ResponseDto, bare oneOf) ──\n\n/**\n * Niche Dive status, discriminated on `status`:\n * - \"in_progress\" → estimatedCompletionDate\n * - \"success\" → nicheId + token usage (feed nicheId to list_niches / get_niche_*)\n * - \"error\" → error message\n */\nexport type DiveStatus =\n | { diveId: string; status: \"in_progress\"; estimatedCompletionDate: string }\n | { diveId: string; status: \"success\"; nicheId: string; tokensUsed: number; tokensLeft: number }\n | { diveId: string; status: \"error\"; error: string };\n\n// ─── /v1/niches/rank-radars/:rankRadarId (KrtKeywordListResponseDto, wrapped) ─\n\nexport interface KrtAsinRank {\n /** yyyy-mm-dd. */\n date: string;\n organicRank?: number | null;\n sponsoredRank?: number | null;\n impressionRank?: number | null;\n}\n\n/**\n * A tag (\"highlight\") attached to a Rank Radar keyword in its ranking timeline. Same object as a\n * row in `list_rank_radar_tags` — the `id` field is the one the tag-management tools (update,\n * delete, add/remove search terms) take as `tagId`. The v1 response field names are frozen, so\n * this type does not rename them: `krtId` is the Rank Radar id, `krtKeywordId` the keyword id.\n * `type` is the backend `KRTHighlightType` enum; the system \"paused\" tag uses the reserved colour\n * `TAG_RESERVED_COLOR` and is also surfaced here.\n */\nexport interface KrtHighlight {\n id: string;\n krtId: string;\n krtKeywordId: string;\n title: string;\n description: string | null;\n color: string | null;\n type: string;\n /** ISO-8601 timestamp or date; null when the tag has no window. */\n startAt: string | null;\n /** ISO-8601 timestamp or date; null when the tag has no window. */\n endAt: string | null;\n}\n\nexport interface KrtKeyword {\n id: string;\n keyword: string;\n searchVolume: number | null;\n /** Phrase relevancy score for this keyword within its Niche. */\n relevancy?: number | null;\n ranks: KrtAsinRank[];\n /** User tags on this keyword — the same objects `list_rank_radar_tags` returns (RS-11822). */\n highlights: KrtHighlight[];\n /** PPC data. Still sent by the API but due for removal (RS-11744); kept opaque. */\n adData?: Record<string, unknown> | null;\n /** SQP data. Still sent by the API but due for removal (RS-11744); kept opaque. */\n sqpData?: Record<string, unknown> | null;\n}\n\n/** One page of a Rank Radar's active keywords (paged since RS-11683); paused keywords are not included. */\nexport type RankRadarKeywordList = PaginationResponse<KrtKeyword>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/tags (RS-11822) ────────────────────────────────────\n\n/** Mirrors backend `TAG_TITLE_MAX_LENGTH`. */\nexport const TAG_TITLE_MAX_LENGTH = 100;\n\n/** Mirrors backend `TAG_KEYWORD_IDS_MAX` — attach/detach cap and initial-assignment cap on create. */\nexport const TAG_KEYWORD_IDS_MAX = 1000;\n\n/**\n * Reserved for the system \"paused\" tag the quota pauser writes. Rejected with 400 on tag\n * create and update; the system tag that uses it is excluded from `list_rank_radar_tags` and\n * returns 404 on update / delete / search-term attach / search-term remove.\n */\nexport const TAG_RESERVED_COLOR = \"#EB5757\";\n\n/** `#RRGGBB` only, matching the UI colour picker's output. Not `#C9D` short form, not `rgba(...)`. */\nexport const TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;\n\n/** A single tag row — `list` returns these, `create` and `update` return one of them. */\nexport interface RankRadarTag {\n id: string;\n title: string;\n color: string | null;\n description: string | null;\n startAt: string | null;\n endAt: string | null;\n keywordCount: number;\n createdAt: string | null;\n}\n\n/** `GET /v1/niches/rank-radars/:rankRadarId/tags` — bare paginated body (follows the backend list pattern). */\nexport type RankRadarTagList = PaginationResponse<RankRadarTag>;\n\n/**\n * `POST .../tags/:tagId/search-terms` (attach) and `.../search-terms/remove` (detach). Kept\n * opaque for now — the backend side ships in RS-11821 and its response shape is not yet frozen.\n * When RS-11821 lands, tighten this to the real DTO.\n */\nexport type TagSearchTermsResult = Record<string, unknown>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/sqp (RankRadarSqpKeywordListResponseDto, bare) ─\n\n/** SQP metrics of one keyword, aggregated over the requested range. Null when Amazon has no SQP data for it. */\nexport interface RankRadarSqpKeyword {\n id: string;\n keyword: string;\n numberOfDaysWithData: number | null;\n searchQueryVolume: number | null;\n searchQueryScore: number | null;\n impressionsTotalCount: number | null;\n impressionsAsinCount: number | null;\n impressionsAsinShare: number | null;\n clicksTotalCount: number | null;\n clicksAsinCount: number | null;\n clicksAsinShare: number | null;\n clicksClickRate: number | null;\n cartAddsTotalCount: number | null;\n cartAddsAsinCount: number | null;\n cartAddsAsinShare: number | null;\n cartAddsCartAddRate: number | null;\n purchasesTotalCount: number | null;\n purchasesAsinCount: number | null;\n purchasesAsinShare: number | null;\n purchasesPurchaseRate: number | null;\n ctrTotal: number | null;\n ctrAsin: number | null;\n cvrTotal: number | null;\n cvrAsin: number | null;\n}\n\nexport type RankRadarSqpKeywordList = PaginationResponse<RankRadarSqpKeyword>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/ppc (RankRadarPpcKeywordListResponseDto, bare) ─\n\n/** One campaign/ad group targeting the keyword; present only with `includeCampaigns=true`. */\nexport interface RankRadarPpcCampaign {\n campaignId: number;\n campaignName: string;\n adGroupName: string;\n matchType: string | null;\n targeting: string | null;\n costPerClicks: number | null;\n totalImpressions: number | null;\n totalOrders: number | null;\n totalClicks: number | null;\n ppcSpend: number | null;\n ppcSales: number | null;\n clickThroughRate: number | null;\n conversionRate: number | null;\n acos: number | null;\n}\n\n/** PPC metrics of one keyword, aggregated over the requested range. */\nexport interface RankRadarPpcKeyword {\n id: string;\n keyword: string;\n /** Median sponsored rank over the range. */\n sponsoredRank: number | null;\n impressionRank: number | null;\n impressionRankShare: number | null;\n exactMatches: number | null;\n phraseMatches: number | null;\n broadMatches: number | null;\n autoMatches: number | null;\n organicSales: number | null;\n ppcSales: number | null;\n ppcSpend: number | null;\n costPerClicks: number | null;\n clickThroughRate: number | null;\n conversionRate: number | null;\n totalClicks: number;\n totalImpressions: number;\n totalOrders: number;\n acos: number;\n campaigns?: RankRadarPpcCampaign[];\n}\n\nexport type RankRadarPpcKeywordList = PaginationResponse<RankRadarPpcKeyword>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/ppc/campaigns ───\n// (PpcCampaignsExternalListDto, bare PaginationResponse)\n\n/** Mirrors the backend's `PpcCampaignsBaseSortBy`. */\nexport const PPC_CAMPAIGN_BASE_SORT_BY = [\n \"name\",\n \"state\",\n \"bidStrategy\",\n \"budget\",\n \"impressions\",\n \"clicks\",\n \"ctr\",\n \"cpc\",\n \"spend\",\n \"unitsSold\",\n \"sales\",\n \"acos\",\n \"cvr\",\n \"tosImpressionShare\",\n] as const;\n\n/** Mirrors the backend's `PpcCampaignsPlacementSortBy`; accepted only with placements included. */\nexport const PPC_CAMPAIGN_PLACEMENT_SORT_BY = [\n ...([\"tos\", \"ros\", \"pp\", \"off\"] as const).flatMap((p) =>\n ([\"Impressions\", \"Clicks\", \"Spend\", \"Sales\", \"UnitsSold\", \"Ctr\", \"Cpc\", \"Acos\"] as const).map(\n (m) => `${p}${m}` as const,\n ),\n ),\n \"tosBidAdjustment\",\n \"rosBidAdjustment\",\n \"ppBidAdjustment\",\n] as const;\n\n/** Mirrors the backend's `CampaignStateFilter`. */\nexport const PPC_CAMPAIGN_STATES = [\"ENABLED\", \"PAUSED\"] as const;\n\nexport interface PpcPlacementMetrics {\n /** Placement bid adjustment, percent. Null for off-Amazon, which has none. */\n bidAdjustment: number | null;\n impressions: number;\n clicks: number;\n ctr: number | null;\n cpc: number | null;\n spend: number;\n unitsSold: number;\n sales: number;\n acos: number | null;\n}\n\nexport interface PpcCampaign {\n campaignId: number;\n name: string;\n type: string | null;\n state: string;\n targetingType: string | null;\n bidStrategy: string | null;\n budget: { type: string | null; amount: number | null; currencyCode: string | null };\n impressions: number;\n clicks: number;\n ctr: number | null;\n cpc: number | null;\n cvr: number | null;\n spend: number;\n unitsSold: number;\n orders: number;\n sales: number;\n tosImpressionShare: number | null;\n acos: number | null;\n roas: number | null;\n tacos: number | null;\n asinCount: number;\n placements?: {\n topOfSearch: PpcPlacementMetrics;\n restOfSearch: PpcPlacementMetrics;\n productPage: PpcPlacementMetrics;\n offAmazon: PpcPlacementMetrics;\n homePageBidAdjustment: number | null;\n };\n}\n\nexport type PpcCampaignList = PaginationResponse<PpcCampaign>;\n\n// ─── /v1/sellers/:sellerId/marketplaces/:marketplace/asins/:asin/inventory ────\n// (InventoryByFcResponseDto, wrapped)\n\nexport interface InventoryByFcItem {\n /** Fulfillment center code. */\n fc: string;\n /** State or region code where the FC is located, when known. */\n state: string;\n /** Units of sellable inventory currently at this FC. May be 0 when stock is out. */\n availableStock: number;\n /** Share of the ASIN's total sellable inventory at this FC, as a 0..1 fraction. */\n availableStockPercentage: number;\n}\n\nexport interface InventoryByFcResponse {\n asin: string;\n sellerId: string;\n /** Marketplace enum: \"com\", \"ca\", \"co.uk\", \"com.mx\", \"in\", \"fr\", \"de\", \"es\", \"it\", \"co.jp\". */\n marketplace: string;\n /** ISO timestamp of the last live-stock refresh, which the metrics come from. Null if none in the last 30 days. */\n lastUpdatedAt: string | null;\n /** ISO timestamp of the last `distribution` refresh, usually up to 2 days behind. Null if none in the last 30 days. */\n distributionLastUpdatedAt: string | null;\n // Metrics: null when Amazon has no data, or when listed in `lockedFields`.\n fulfillableStock: number | null;\n totalStock: number | null;\n reservedStock: number | null;\n inboundStock: number | null;\n researchingStock: number | null;\n unfulfillableStock: number | null;\n daysOfStock: number | null;\n daysOfStockSource: \"AMAZON\" | \"ESTIMATE\" | null;\n daysOfStock30: number | null;\n daysOfStock90: number | null;\n inLowInventoryState: boolean | null;\n /** Fees are in `currency`. */\n estWeeklyLilf: number | null;\n estLtsfNextAssessment: number | null;\n ltsfLastMonthActual: number | null;\n agedUnits: number | null;\n oldestUnitAgeDays: number | null;\n /** ISO 4217 marketplace currency, e.g. \"USD\". */\n currency: string;\n /** Metrics the user's plan does not include; returned as null. */\n lockedFields: string[];\n lockedReason: \"PLAN_UPGRADE_REQUIRED\" | null;\n totalSellableUnits: number;\n distribution: InventoryByFcItem[];\n}\n\n// ─── /v1/alerts/indexing-issues (IndexingIssueAlertListDto, bare) ───────────\n\n/**\n * Allowed values for the `status` query param on the /v1/alerts/* endpoints.\n * `active` is the server-side default; dismissed alerts are never returned.\n */\nexport const ALERT_STATUSES = [\"active\", \"resolved\", \"all\"] as const;\nexport type AlertStatus = (typeof ALERT_STATUSES)[number];\n\nexport interface IndexingIssueAlertItem {\n id: number;\n /** The ASIN that is no longer indexed for its tracked keywords. */\n asin: string;\n title: string | null;\n imageUrl: string | null;\n /** True when the ASIN is a parent of a variation family. */\n isParent: boolean;\n sellerId: string;\n marketplace: string;\n /** ISO timestamp of when the alert was most recently surfaced. Updates each time it re-fires. */\n lastAlertedAt: string;\n /** ISO timestamp of when the alert was resolved, or null if still active. */\n resolvedAt: string | null;\n}\n\nexport type IndexingIssueAlertList = PaginationResponse<IndexingIssueAlertItem>;\n\n// ─── /v1/alerts/blind-spend (BlindSpendAlertListDto, bare) ──────────────────\n\nexport interface BlindSpendSearchTerm {\n /** The customer search term that wasted spend. */\n term: string;\n spend: number | null;\n sales: number | null;\n clicks: number | null;\n /** Conversion rate as a 0..1 fraction. */\n cvr: number | null;\n impressions: number | null;\n}\n\nexport interface BlindSpendAlertItem {\n id: number;\n asin: string | null;\n title: string | null;\n imageUrl: string | null;\n sellerId: string;\n marketplace: string;\n /** ISO timestamp of when the alert was most recently surfaced. Updates each time it re-fires. */\n lastAlertedAt: string;\n /** ISO timestamp of when the alert was resolved, or null if still active. */\n resolvedAt: string | null;\n /** Total ad spend across the unresolved wasted-spend search terms. */\n wastedSpend: number;\n totalKeywordCount: number;\n unresolvedKeywordCount: number;\n /** The unresolved wasted-spend search terms. */\n searchTerms: BlindSpendSearchTerm[];\n}\n\nexport type BlindSpendAlertList = PaginationResponse<BlindSpendAlertItem>;\n\n// ─── Billable features (shared by /v1/quota and /v1/usage) ───────────────────\n\n/**\n * Billable feature types. These are both the keys of the `quota.features` object\n * and the allowed values of the `type` filter on /v1/usage. Mirrors the backend's\n * billable-feature enum.\n */\nexport const BILLABLE_FEATURE_TYPES = [\n \"DIVED_ASINS\",\n \"PRODUCT_BRIEF_ASINS\",\n \"AI_COPYWRITER_PROMPTS\",\n \"RANK_RADAR_KEYWORDS\",\n] as const;\nexport type BillableFeatureType = (typeof BILLABLE_FEATURE_TYPES)[number];\n\n// ─── /v1/niches/:nicheId/roots (ExternalRootResponseDto[], wrapped) ─────────\n\nexport interface RootsTableItem {\n /** A word or word-combination extracted from the master keyword list. */\n root: string;\n /** Count of keywords in the master list that contain this root. */\n frequency: number;\n /** Sum of search volume across all original keywords containing this root. */\n broadSearchVolume: number;\n /** broadSearchVolume / maxBroadSearchVolume, as a 0..1 fraction. */\n broadSearchVolumeRatio: number;\n}\n\n/**\n * Keyword-roots analysis for a Niche. `keywords` and `consolidatedKeywords` are\n * the per-keyword breakdowns (kept opaque — the LLM sees the full JSON); `roots`\n * and `normalizedRoots` are the ranked root tables.\n *\n * Note: the OpenAPI spec types the endpoint's `data` as an array, but the live\n * API returns a single object (verified against api-qa, 2026-06-17). The client's\n * unwrap() strips the `{ data }` envelope, so the tool returns this object directly.\n */\nexport interface NicheRoots {\n keywords: Array<Record<string, unknown>>;\n consolidatedKeywords: Array<Record<string, unknown>>;\n roots: RootsTableItem[];\n normalizedRoots: RootsTableItem[];\n /** ISO date string of the last successful research for the niche. */\n latestResearchDate: string;\n}\n\n// ─── /v1/quota (ExternalQuotaResponseDto) ───────────────────────────────────\n\nexport interface QuotaFeature {\n /** Current usage count. Null when not applicable to the plan. */\n used: number | null;\n /** Quota capacity. Null when unlimited / not applicable. */\n capacity: number | null;\n}\n\nexport interface Quota {\n /** ISO-8601 timestamp of the next quota reset. Null when no reset is scheduled. */\n nextRefreshDate: string | null;\n /** Per-billable-feature usage and capacity. */\n features: Record<BillableFeatureType, QuotaFeature>;\n}\n\n// ─── /v1/usage (ExternalUsageLogListDto, bare PaginationResponse) ───────────\n\nexport interface UsageLogItem {\n /** Name of the user who performed the action. Null when unknown. */\n name: string | null;\n email: string;\n /** Number of tokens consumed. */\n qty: number;\n type: BillableFeatureType;\n /** Specific action performed (e.g. \"RANK_RADAR_CREATE\"). Null when not set. */\n action: string | null;\n nicheId: string | null;\n nicheName: string | null;\n rankRadarId: string | null;\n /** ISO timestamp of when the usage was recorded. */\n date: string;\n}\n\nexport type UsageLogList = PaginationResponse<UsageLogItem>;\n\n// ─── /v1/seller_profiles (SellerProfileListDto, bare PaginationResponse) ─────\n\nexport interface SellerProfileItem {\n /** Amazon seller account ID — the value other seller-scoped tools require. */\n sellerId: string;\n /** Display name of the connected seller account. */\n sellerName: string;\n /** Marketplace code, e.g. \"com\", \"co.uk\" (see SUPPORTED_MARKETPLACES). */\n marketplace: string;\n /** True when Amazon Advertising API credentials are connected for this account. */\n hasAdApi: boolean;\n /** ISO timestamp of when the account was connected to DataDive. */\n createdAt: string;\n}\n\nexport type SellerProfileList = PaginationResponse<SellerProfileItem>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/catalog ──────────\n// (CatalogAsinListDto, bare PaginationResponse)\n\n/** Allowed values for the `status` query param on the seller catalog endpoint. */\nexport const CATALOG_STATUSES = [\"Active\", \"all\"] as const;\nexport type CatalogStatus = (typeof CATALOG_STATUSES)[number];\n\nexport interface CatalogAsinItem {\n asin: string;\n title: string;\n /** Parent ASIN for a variation child, or null for standalone/parent ASINs. */\n parentAsin: string | null;\n brand: string | null;\n /** Listing status, e.g. \"Active\". */\n status: string;\n imageUrl: string | null;\n /** True when the ASIN has variation children. Null when unknown. */\n hasVariations: boolean | null;\n}\n\nexport type SellerCatalogList = PaginationResponse<CatalogAsinItem>;\n\n// ─── /v1/seller_profiles/:sellerId/marketplaces/:marketplace/listing-changes ──\n// (ListingChangeExternalListDto, bare PaginationResponse)\n\n/** Allowed values for the `types` query param on the listing-changes endpoint. */\nexport const LISTING_CHANGE_TYPES = [\"Price\", \"Content\", \"Image\"] as const;\nexport type ListingChangeType = (typeof LISTING_CHANGE_TYPES)[number];\n\n/** Allowed values for the `sortBy` query param on the listing-changes endpoint. */\nexport const LISTING_CHANGE_SORT_BY = [\"date\", \"type\"] as const;\nexport type ListingChangeSortBy = (typeof LISTING_CHANGE_SORT_BY)[number];\n\n/** Generic sort direction, shared where a `sortOrder` query param is accepted. */\nexport const SORT_ORDER = [\"ASC\", \"DESC\"] as const;\nexport type SortOrder = (typeof SORT_ORDER)[number];\n\n/**\n * Optional ranking/conversion correlation for a listing change, present only when\n * `includeCorrelations=true`. Sub-objects (salesCvr, top10/top50SearchTerms) carry\n * before/after figures and are kept loosely typed per this file's convention.\n */\nexport interface ListingChangeCorrelation {\n /** \"PENDING\" | \"AVAILABLE\" | \"UNAVAILABLE_NO_ACTIVE_RANK_RADAR\" | \"UNAVAILABLE\". */\n status: string;\n message: string | null;\n salesCvr: Record<string, unknown> | null;\n top10SearchTerms: Record<string, unknown> | null;\n top50SearchTerms: Record<string, unknown> | null;\n}\n\nexport interface ListingChangeItem {\n asin: string;\n title: string | null;\n imageUrl: string | null;\n /** ISO timestamp of when the change was detected. */\n date: string;\n /** \"Price\" | \"Content\" | \"Image\". */\n type: string;\n /** Sub-type for content changes (e.g. title/bullets/description); null otherwise. */\n contentType: string | null;\n description: string;\n /** Prior value of the changed field. Shape varies by change type; kept opaque. */\n previousValue: Record<string, unknown> | null;\n /** New value of the changed field. Shape varies by change type; kept opaque. */\n newValue: Record<string, unknown> | null;\n /** Present only when `includeCorrelations=true`; null otherwise. */\n correlation: ListingChangeCorrelation | null;\n}\n\nexport type ListingChangeList = PaginationResponse<ListingChangeItem>;\n\n// ─── /v1/niches/rank-radars/:rankRadarId/search-terms (ExternalAddSearchTermsResponseDto, wrapped) ──\n\n/**\n * Where each submitted search term ended up. `manual` and `suggestions` are terms the\n * niche had never seen; the rest were already known to the niche in that role.\n * Mirrors the backend's SearchTermOriginBreakdown.\n */\nexport interface SearchTermOriginBreakdown {\n tracked: string[];\n paused: string[];\n outliers: string[];\n residues: string[];\n ppc: string[];\n /** Terms the niche has never seen, typed in by the user. */\n manual: string[];\n /** Terms the niche has never seen, accepted from the Rank Radar keyword suggestions. */\n suggestions: string[];\n}\n\nexport interface AddSearchTermsResult {\n originBreakdown: SearchTermOriginBreakdown;\n /** search term -> rankRadarKeywordId, the id the archive/resume search-term tools take. */\n keywordToRankRadarKeywordIdMap: Record<string, string>;\n}\n\n/** Backend cap on how many search terms one add call accepts (ADD_SEARCH_TERM_MAX_LIMIT). */\nexport const ADD_SEARCH_TERMS_MAX = 1000;\n\n// ─── /v1/niches/:nicheId/ai-copywriter (AI Copywriter, wrapped) ─────────────\n\n/**\n * Generation strategies the AI Copywriter accepts. Mirrors the backend's\n * GenerateAiCopywriterPromptOption enum.\n */\nexport const LISTING_COPY_STRATEGIES = [\"cosmo\", \"ranking-juice\", \"nlp\", \"cosmo-rufus\"] as const;\nexport type ListingCopyStrategy = (typeof LISTING_COPY_STRATEGIES)[number];\n\n/** Returned immediately when a generation is enqueued. */\nexport interface CreateListingCopyResult {\n generationId: string;\n status: \"generating\";\n}\n\nexport interface ListingCopyResult {\n title: string;\n bullets: string[];\n description: string;\n itemHighlights: string;\n /** Ranking Juice metrics for the generated copy (value + per-field contribution). */\n rankingJuice: Record<string, unknown>;\n methodology_breakdown?: unknown;\n}\n\n/** Polled status of a generation. Discriminated on `status`, like DiveStatus. */\nexport type ListingCopyGenerationStatus =\n | { generationId: string; status: \"generating\" }\n | { generationId: string; status: \"complete\"; result: ListingCopyResult }\n | { generationId: string; status: \"failed\"; error: string };\n\n// ─── Fast Keywords (/v1/fast-keywords, RS-12017 / RS-12018) ─────────────────\n\n/** Mirrors the API's page-size cap on the Fast Keywords lists. */\nexport const FAST_KEYWORDS_MAX_PAGE_SIZE = 100;\n\nexport type FastKeywordsStatus = \"in-progress\" | \"failed\" | \"success\";\n\nexport interface FastKeywordsRoot {\n root: string;\n aggregateSearchVolume: number;\n medianOrganicRank: number | null;\n keywords: string[];\n}\n\n/** GET /v1/fast-keywords/:fastKeywordsId (wrapped). The keyword rows are a separate route. */\nexport interface FastKeywordsResult {\n fastKeywordsId: number;\n status: FastKeywordsStatus;\n asin: string;\n parentAsin: string | null;\n marketplace: string;\n product: { title: string | null; imageUrl: string | null; brand: string | null; price: number | null };\n variations: Array<{ asin: string; title: string | null; imageUrl: string | null }>;\n roots: FastKeywordsRoot[];\n /** The top 10 competitors. */\n competitors: Array<{ asin: string; title: string | null; imageUrl: string | null; variationCount: number }>;\n keywordCount: number;\n lastRefreshedAt: string | null;\n /** Set when `status` is `failed`. */\n failureReason?: string;\n /** Why the run failed, or why a successful run has little to show. Safe to show to a user. */\n failureMessage?: string;\n /** `true` when a successful run found too few keywords to be useful. */\n insufficientKeywordData?: boolean;\n}\n\nexport interface FastKeywordsKeyword {\n keyword: string;\n searchVolume: number | null;\n organicRank: number | null;\n sponsoredRank: number | null;\n}\n\nexport type FastKeywordsKeywordList = PaginationResponse<FastKeywordsKeyword>;\n\nexport interface FastKeywordsListItem {\n fastKeywordsId: number;\n asin: string;\n parentAsin: string | null;\n marketplace: string;\n status: FastKeywordsStatus;\n title: string | null;\n brand: string | null;\n price: number | null;\n keywordCount: number;\n totalSearchVolume: number | null;\n top10OrganicRankCount: number | null;\n rootCount: number | null;\n lastRefreshedAt: string | null;\n /** Latest request by anyone in the organization. */\n requestedAt: string;\n}\n\nexport type FastKeywordsList = PaginationResponse<FastKeywordsListItem>;\n\n/** POST /v1/fast-keywords and /:fastKeywordsId/refresh (wrapped): a real run. */\nexport interface FastKeywordsRunResult {\n /** Poll `get_fast_keywords` with it. */\n fastKeywordsId: number;\n /** Id of this request; what `create_niche_dive` takes as `fastKeywordsRequestId`. */\n requestId: number;\n /** `success` right away when a fresh result for the same product already exists. */\n status: FastKeywordsStatus;\n tokenCost: number;\n estimatedCompletionDate: string;\n}\n\n/** The same routes with `dryRun: true`: nothing runs and nothing is spent. */\nexport interface FastKeywordsRunPreview {\n tokenCost: number;\n hasEnoughTokens: boolean;\n}\n\n/** Body of the 409 a refresh answers while a run is going or the cooldown has not passed. */\nexport interface FastKeywordsRefreshConflictBody {\n error: \"IN_PROGRESS\" | \"COOLDOWN\";\n message: string;\n /** `null` while a run is in progress. */\n nextEligibleAt: string | null;\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { FAST_KEYWORDS_MAX_PAGE_SIZE, SUPPORTED_MARKETPLACES, type FastKeywordsList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(FAST_KEYWORDS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${FAST_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`),\n search: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe(\"Case-insensitive partial match on ASIN, product title or brand.\"),\n marketplace: z.enum(SUPPORTED_MARKETPLACES).optional().describe(\"Only Fast Keywords runs from this marketplace.\"),\n};\n\nexport const listFastKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_fast_keywords\",\n title: \"List Fast Keywords Runs\",\n description:\n \"Use this first when the user asks about their Fast Keywords runs, or to find a `fastKeywordsId` for \" +\n \"`get_fast_keywords`, `get_fast_keywords_keywords` or `refresh_fast_keywords`. Fast Keywords finds the \" +\n \"keywords a product ranks for from just an ASIN, in minutes and for 1 Dive token, without a full niche \" +\n \"dive. Lists the runs of the whole organization, newest request first: `fastKeywordsId`, `asin`, \" +\n \"`parentAsin`, `marketplace`, `status` (`in-progress`, `success` or `failed`), `title`, `brand`, `price`, \" +\n \"`keywordCount`, `totalSearchVolume`, `top10OrganicRankCount`, `rootCount`, `lastRefreshedAt` and \" +\n \"`requestedAt`, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). \" +\n \"Narrow with `search` or `marketplace`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<FastKeywordsList> => {\n return await httpGet<FastKeywordsList>({ config: ctx.config, toolName: \"list_fast_keywords\" }, \"/v1/fast-keywords\", {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n search: args.search,\n marketplace: args.marketplace,\n });\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { FastKeywordsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n fastKeywordsId: z\n .number()\n .int()\n .min(1)\n .describe(\"The Fast Keywords result id, from `run_fast_keywords`, `refresh_fast_keywords` or `list_fast_keywords`.\"),\n};\n\nexport const getFastKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_fast_keywords\",\n title: \"Get a Fast Keywords Result\",\n description:\n \"Use this to poll a Fast Keywords run started with `run_fast_keywords` or `refresh_fast_keywords`, and to \" +\n \"read its result. While `status` is `in-progress`, wait a few seconds and call again; stop at `success` or \" +\n \"`failed`. On `failed`, `failureReason` and `failureMessage` say why (safe to show to the user). On `success` \" +\n \"it returns the product (`title`, `brand`, `price`, `imageUrl`), its `variations`, `keywordCount`, \" +\n \"`lastRefreshedAt`, the keyword `roots` (`root`, `aggregateSearchVolume`, `medianOrganicRank`, `keywords`) and \" +\n \"the top 10 `competitors`. `insufficientKeywordData: true` means the run succeeded but found too few \" +\n \"keywords to be useful. The keyword rows themselves are in `get_fast_keywords_keywords`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<FastKeywordsResult> => {\n return await httpGet<FastKeywordsResult>(\n { config: ctx.config, toolName: \"get_fast_keywords\" },\n `/v1/fast-keywords/${args.fastKeywordsId}`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { FAST_KEYWORDS_MAX_PAGE_SIZE, type FastKeywordsKeywordList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n fastKeywordsId: z\n .number()\n .int()\n .min(1)\n .describe(\"The Fast Keywords result id, from `run_fast_keywords` or `list_fast_keywords`.\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(FAST_KEYWORDS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Keywords per page (max ${FAST_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\nexport const getFastKeywordsKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_fast_keywords_keywords\",\n title: \"Get the Keywords of a Fast Keywords Result\",\n description:\n \"Use this to read the keywords of a finished Fast Keywords run (check `status` is `success` with \" +\n \"`get_fast_keywords` first). Returns `keyword`, `searchVolume`, `organicRank` and `sponsoredRank` per \" +\n \"keyword, highest search volume first, plus pagination metadata (currentPage, pageSize, total, lastPage, \" +\n \"hasNext, hasPrev). A page holds at most \" +\n `${FAST_KEYWORDS_MAX_PAGE_SIZE} keywords; keep requesting the next page while \\`hasNext\\` is true.`,\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<FastKeywordsKeywordList> => {\n return await httpGet<FastKeywordsKeywordList>(\n { config: ctx.config, toolName: \"get_fast_keywords_keywords\" },\n `/v1/fast-keywords/${args.fastKeywordsId}/keywords`,\n { currentPage: args.currentPage, pageSize: args.pageSize },\n );\n },\n};\n","import { z } from \"zod\";\nimport { SUPPORTED_MARKETPLACES } from \"../types/api.js\";\nimport { runFastKeywords } from \"./fast-keywords-run.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe(\n \"Amazon marketplace of the ASIN. Note these are full domain suffixes: com, ca, co.uk, com.mx, in, fr, \" +\n \"de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`).\",\n ),\n asin: z.string().min(1).describe(\"The ASIN to find keywords for (e.g. B08N5WRWNW).\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Leave unset on the first call. Set to true only after the user approved the Dive token cost from the \" +\n \"`confirmation_required` response.\",\n ),\n};\n\nexport const runFastKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"run_fast_keywords\",\n title: \"Run Fast Keywords\",\n description:\n \"Use this to find the keywords a product ranks for from just its ASIN — in minutes, without a full niche \" +\n \"dive. ⚠️ Spends 1 Dive token and cannot be undone. The first call checks eligibility and the Dive token \" +\n \"balance, spends nothing and returns `confirmation_required` with `tokenCost` and `hasEnoughTokens`; call \" +\n \"again with `confirm: true` only after the user approves. Not safe to retry: each confirmed call spends a \" +\n \"token — if it errors or times out, check `list_fast_keywords` instead of re-calling. Returns \" +\n \"`fastKeywordsId`, `requestId`, `status` and `estimatedCompletionDate`. Poll `get_fast_keywords` with the \" +\n \"`fastKeywordsId` until `status` is `success` (right away when a fresh result for the same product already \" +\n \"exists), then read the keywords with `get_fast_keywords_keywords`. To follow up with a full niche dive for \" +\n \"the same product, pass the `requestId` to `create_niche_dive` as `fastKeywordsRequestId`. Run it again \" +\n \"later with `refresh_fast_keywords`.\",\n inputSchema,\n // Additive, but it spends a Dive token irreversibly, which is what clients need to prompt on. The confirm\n // gate enforces it server-side regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await runFastKeywords(\n ctx,\n { config: ctx.config, toolName: \"run_fast_keywords\" },\n \"/v1/fast-keywords\",\n { marketplace: args.marketplace, asin: args.asin },\n args.confirm,\n \"Running Fast Keywords\",\n );\n },\n};\n","/**\n * Shared confirm gate for token-spending write tools (create_niche_dive,\n * create_rank_radar).\n *\n * Creating a Dive or Rank Radar consumes billable tokens irreversibly, so these\n * tools require an explicit `confirm: true` before they hit the API. When the flag\n * is missing, requireConfirmation returns a structured (non-error) payload telling\n * the model to confirm the token spend with the user and re-call with `confirm: true`;\n * the handler returns that payload early WITHOUT calling the API.\n *\n * The user can opt out of the gate entirely by setting DATADIVE_AUTO_CONFIRM_WRITES\n * (see config.ts) — a persistent \"don't ask me again\".\n */\n\nimport type { ApiWarning } from \"../types/api.js\";\nimport type { HandlerContext } from \"./types.js\";\n\nexport interface ConfirmationRequired {\n status: \"confirmation_required\";\n message: string;\n /** Human-readable note on what this action will cost in tokens. */\n costNote: string;\n /**\n * Non-blocking notices the API reported for this exact request, obtained from a\n * dry run (RS-11615). Absent when the tool has no dry-run preview or the API\n * flagged nothing. Each `message` is customer-facing text to be shown verbatim.\n */\n warnings?: ApiWarning[];\n}\n\n/**\n * Whether the gate applies — whether the tool must stop and ask before spending\n * anything. Split out from requireConfirmation so a tool can run a read-only preview\n * first and fold its result into the payload; see create_rank_radar's dry run.\n */\nexport function needsConfirmation(confirm: boolean | undefined, ctx: HandlerContext): boolean {\n return !(confirm === true || ctx.config.autoConfirmWrites);\n}\n\n/** The gate payload. `warnings` is omitted entirely when there are none, to keep the JSON quiet. */\nexport function confirmationRequired(costNote: string, warnings?: ApiWarning[]): ConfirmationRequired {\n return {\n status: \"confirmation_required\",\n message:\n \"This action spends billable tokens and cannot be undone. Confirm the cost with \" +\n \"the user, then call again with `confirm: true` to proceed.\",\n costNote,\n ...(warnings && warnings.length > 0 ? { warnings } : {}),\n };\n}\n\n/**\n * Returns a ConfirmationRequired payload if the action is not yet confirmed, or null\n * if the handler should proceed (confirm:true was passed, or the opt-out is set).\n */\nexport function requireConfirmation(\n confirm: boolean | undefined,\n ctx: HandlerContext,\n costNote: string,\n): ConfirmationRequired | null {\n if (!needsConfirmation(confirm, ctx)) return null;\n return confirmationRequired(costNote);\n}\n","/**\n * The confirm gate shared by `run_fast_keywords` and `refresh_fast_keywords`: both spend 1 Dive token\n * and answer to the same two request bodies (a real run, or `dryRun: true`).\n *\n * Built like `create_rank_radar`. The first call is a dry run, which does the API's own eligibility and\n * Dive token check and spends nothing, so the confirmation carries the API's figures instead of a static\n * note. `dryRun` is set only here, never taken from the model: a model that could set it could dry-run,\n * read a success-shaped answer and tell the user a run started when none did.\n */\n\nimport { httpPost, type RequestContext } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport type {\n FastKeywordsRefreshConflictBody,\n FastKeywordsRunPreview,\n FastKeywordsRunResult,\n} from \"../types/api.js\";\nimport { confirmationRequired, needsConfirmation, type ConfirmationRequired } from \"./confirm.js\";\nimport type { HandlerContext } from \"./types.js\";\n\nexport interface FastKeywordsConfirmation extends ConfirmationRequired {\n /** Dive tokens the run would consume, as the API reports it. */\n tokenCost: number;\n /** Whether the organization has enough Dive tokens left. When `false`, a confirmed call is refused. */\n hasEnoughTokens: boolean;\n}\n\nfunction isRefreshConflict(body: unknown): body is FastKeywordsRefreshConflictBody {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return b.error === \"IN_PROGRESS\" || b.error === \"COOLDOWN\";\n}\n\n/** The 409 of a refresh as a plain message the model can relay; nothing was spent. */\nfunction toConflictError(err: ApiError): ApiError {\n const body = err.body;\n if (!isRefreshConflict(body)) return err;\n\n const message =\n body.error === \"COOLDOWN\" && body.nextEligibleAt\n ? `This Fast Keywords result was refreshed recently. It can be refreshed again from ${body.nextEligibleAt}. ` +\n \"Nothing was run or charged; read the current result with `get_fast_keywords`.\"\n : body.error === \"IN_PROGRESS\"\n ? \"A run for this Fast Keywords result is already in progress. Nothing was charged: poll \" +\n \"`get_fast_keywords` until it finishes instead of calling again.\"\n : `${body.message} Nothing was run or charged.`;\n return new ApiError(\"http\", 409, message, body);\n}\n\nexport async function runFastKeywords(\n ctx: HandlerContext,\n request: RequestContext,\n path: string,\n body: Record<string, unknown>,\n confirm: boolean | undefined,\n what: string,\n): Promise<FastKeywordsRunResult | FastKeywordsConfirmation> {\n const post = async <T>(payload: Record<string, unknown>): Promise<T> => {\n try {\n return await httpPost<T>(request, path, payload);\n } catch (err) {\n throw err instanceof ApiError && err.status === 409 ? toConflictError(err) : err;\n }\n };\n\n if (needsConfirmation(confirm, ctx)) {\n const preview = await post<FastKeywordsRunPreview | FastKeywordsRunResult>({ ...body, dryRun: true });\n\n // A backend that ignores `dryRun` runs for real. It gives itself away by returning a result id where a\n // preview returns none. Report what happened rather than ask to confirm what is already paid for.\n if (\"fastKeywordsId\" in preview) return preview;\n\n const { tokenCost, hasEnoughTokens } = preview;\n return {\n ...confirmationRequired(\n `${what} consumes ${tokenCost} Dive token${tokenCost === 1 ? \"\" : \"s\"}.` +\n (hasEnoughTokens ? \"\" : \" The organization does not have enough Dive tokens left, so a confirmed call would be refused.\"),\n ),\n tokenCost,\n hasEnoughTokens,\n };\n }\n\n // Confirmed, or the gate is opted out via DATADIVE_AUTO_CONFIRM_WRITES.\n return await post<FastKeywordsRunResult>(body);\n}\n","import { z } from \"zod\";\nimport { runFastKeywords } from \"./fast-keywords-run.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n fastKeywordsId: z\n .number()\n .int()\n .min(1)\n .describe(\"The Fast Keywords result to refresh, from `run_fast_keywords` or `list_fast_keywords`.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Leave unset on the first call. Set to true only after the user approved the Dive token cost from the \" +\n \"`confirmation_required` response.\",\n ),\n};\n\nexport const refreshFastKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"refresh_fast_keywords\",\n title: \"Refresh Fast Keywords\",\n description:\n \"Use this to re-run an existing Fast Keywords result with current data. ⚠️ Spends 1 Dive token and cannot \" +\n \"be undone. The first call checks eligibility and the Dive token balance, spends nothing and returns \" +\n \"`confirmation_required` with `tokenCost` and `hasEnoughTokens`; call again with `confirm: true` only after \" +\n \"the user approves. A result can be refreshed once every 24 hours, and not while a run is going: then the \" +\n \"call fails with a message that says when it is refreshable again (or to poll `get_fast_keywords`), and \" +\n \"nothing is charged. Not safe to retry after an error or timeout — check `get_fast_keywords` instead. \" +\n \"Returns `fastKeywordsId`, `requestId`, `status` and `estimatedCompletionDate`; poll `get_fast_keywords` \" +\n \"until `status` is `success`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await runFastKeywords(\n ctx,\n { config: ctx.config, toolName: \"refresh_fast_keywords\" },\n `/v1/fast-keywords/${args.fastKeywordsId}/refresh`,\n {},\n args.confirm,\n \"Refreshing Fast Keywords\",\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { TagNode } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {};\n\nexport const listSpacesTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_spaces\",\n title: \"List Spaces\",\n description:\n \"Use this first when the user talks about Spaces, or to find the `id`s that `add_niche_spaces`, \" +\n \"`remove_niche_spaces` and the `spaces` filter of `list_niches` and `list_rank_radars` take. \" +\n \"Returns the Spaces as a tree: each node has `id`, `name`, `path` (names from the root joined with `/`), \" +\n \"`parentId` and `children`. An MCP user sees only the Spaces they have rights on, so a Space that is missing from the tree is one they cannot use. \" +\n \"Niches carry the ids of their Spaces in `spaces` / `labels` on `list_niches`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (_args, ctx): Promise<TagNode[]> => {\n return await httpGet<TagNode[]>({ config: ctx.config, toolName: \"list_spaces\" }, \"/v1/spaces\");\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { TagNode } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {};\n\nexport const listLabelsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_labels\",\n title: \"List Labels\",\n description:\n \"Use this first when the user talks about labels, or to find the `id`s that `add_niche_labels`, \" +\n \"`remove_niche_labels` and the `labels` filter of `list_niches` and `list_rank_radars` take. \" +\n \"Returns the labels as a tree: each node has `id`, `name`, `path` (names from the root joined with `/`), \" +\n \"`parentId` and `children`. Labels carry no access rights, so every label of the organization is listed. \" +\n \"Niches carry the ids of their labels in `spaces` / `labels` on `list_niches`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (_args, ctx): Promise<TagNode[]> => {\n return await httpGet<TagNode[]>({ config: ctx.config, toolName: \"list_labels\" }, \"/v1/labels\");\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { AddNicheTagsResult } from \"../types/api.js\";\nimport { NICHE_TAG_WRITE_RULES, nicheTagIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to change. Get one from `list_niches`.\"),\n spaceIds: nicheTagIdList(\"spaceIds\").describe(\n \"The ids of the Spaces to add the niche to, 1-100, unique. Get them from `list_spaces`.\",\n ),\n};\n\nexport const addNicheSpacesTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_niche_spaces\",\n title: \"Add a Niche to Spaces\",\n description:\n \"Use this to put a niche in one or more Spaces (ids from `list_spaces`). \" +\n \"A Space controls who can see the niche, so adding or removing one changes access. Returns `added` and `alreadyPresent`. Undo with `remove_niche_spaces`. \" +\n NICHE_TAG_WRITE_RULES,\n inputSchema,\n // Like every write tool here: clients prompt on destructiveHint. Changing a Space also changes who can see the niche.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<AddNicheTagsResult>(\n { config: ctx.config, toolName: \"add_niche_spaces\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/spaces`,\n { spaceIds: args.spaceIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { RemoveNicheTagsResult } from \"../types/api.js\";\nimport { NICHE_TAG_WRITE_RULES, nicheTagIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to change. Get one from `list_niches`.\"),\n spaceIds: nicheTagIdList(\"spaceIds\").describe(\n \"The ids of the Spaces to take the niche out of, 1-100, unique. Get them from `list_spaces`.\",\n ),\n};\n\nexport const removeNicheSpacesTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_niche_spaces\",\n title: \"Remove a Niche from Spaces\",\n description:\n \"Use this to take a niche out of one or more Spaces (ids from `list_spaces`). \" +\n \"A Space controls who can see the niche, so adding or removing one changes access. Returns `removed` and `notPresent`. Undo with `add_niche_spaces`. \" +\n NICHE_TAG_WRITE_RULES,\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<RemoveNicheTagsResult>(\n { config: ctx.config, toolName: \"remove_niche_spaces\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/spaces/remove`,\n { spaceIds: args.spaceIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { AddNicheTagsResult } from \"../types/api.js\";\nimport { NICHE_TAG_WRITE_RULES, nicheTagIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to change. Get one from `list_niches`.\"),\n labelIds: nicheTagIdList(\"labelIds\").describe(\n \"The ids of the labels to add the niche to, 1-100, unique. Get them from `list_labels`.\",\n ),\n};\n\nexport const addNicheLabelsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_niche_labels\",\n title: \"Add Labels to a Niche\",\n description:\n \"Use this to put a niche in one or more labels (ids from `list_labels`). \" +\n \"Returns `added` and `alreadyPresent`. Undo with `remove_niche_labels`. \" +\n NICHE_TAG_WRITE_RULES,\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<AddNicheTagsResult>(\n { config: ctx.config, toolName: \"add_niche_labels\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/labels`,\n { labelIds: args.labelIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { RemoveNicheTagsResult } from \"../types/api.js\";\nimport { NICHE_TAG_WRITE_RULES, nicheTagIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to change. Get one from `list_niches`.\"),\n labelIds: nicheTagIdList(\"labelIds\").describe(\n \"The ids of the labels to take the niche out of, 1-100, unique. Get them from `list_labels`.\",\n ),\n};\n\nexport const removeNicheLabelsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_niche_labels\",\n title: \"Remove Labels from a Niche\",\n description:\n \"Use this to take a niche out of one or more labels (ids from `list_labels`). \" +\n \"Returns `removed` and `notPresent`. Undo with `add_niche_labels`. \" +\n NICHE_TAG_WRITE_RULES,\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<RemoveNicheTagsResult>(\n { config: ctx.config, toolName: \"remove_niche_labels\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/labels/remove`,\n { labelIds: args.labelIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { GetMasterKeywordListResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_keywords\",\n title: \"Get Master Keyword List for a Niche\",\n description:\n \"Use this when the user asks about keywords, search terms, or search volume for a niche. \" +\n \"Retrieves the master keyword list for the specified Niche — the relevant search terms \" +\n \"monitored for ranking and performance metrics. For each keyword, returns search volume, \" +\n \"relevancy (numeric score or \\\"Outlier\\\"), and competitor ASIN organic ranks \" +\n \"(asinRanks: { ASIN -> rank | null }), plus `origin`: `mkl` (found by the dive), `manual` (added by \" +\n \"hand with `add_niche_keywords`), `outlier` / `residue` (moved in from those lists). Only `manual`, \" +\n \"`outlier` and `residue` keywords can be taken out with `remove_niche_keywords`. Keywords hidden by the \" +\n \"niche's keyword exclusions are not listed; see `list_keyword_exclusions`. Also returns the \" +\n \"latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<GetMasterKeywordListResult>(\n { config: ctx.config, toolName: \"get_niche_keywords\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { KeywordExclusions } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const listKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_keyword_exclusions\",\n title: \"List a Niche's Keyword Exclusions\",\n description:\n \"Use this to see which keywords are hidden from a niche's Master Keyword List, e.g. when a keyword the \" +\n \"user expects is missing from `get_niche_keywords`. Returns the same lists the DataDive web app shows, \" +\n \"oldest first: `exact` — each entry hides the one keyword that matches it exactly, with that keyword's \" +\n \"`searchVolume` (0 when the niche has no such keyword); `phrase` — each entry hides every keyword that \" +\n \"contains the phrase, with `excludedKeywordsCount` and the total `searchVolume` of the keywords it hides \" +\n \"(not counting ones already excluded exactly). Exclusions hide keywords wherever they come from: the \" +\n \"dive, the Outliers and Residue lists, and hand-added ones. Change them with `add_keyword_exclusions` \" +\n \"and `remove_keyword_exclusions`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<KeywordExclusions>(\n { config: ctx.config, toolName: \"list_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheRoots } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheRootsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_roots\",\n title: \"Get Keyword Roots for a Niche\",\n description:\n \"Use this when the user wants to find the highest-impact words across a niche's keywords, asks about \" +\n \"'keyword roots', or wants to prioritize terms for a listing. \" +\n \"Retrieves the keyword lexical roots for the Niche — individual words or word-combinations extracted \" +\n \"from the Master Keyword List (e.g. 'bluetooth headphones' yields roots 'bluetooth', 'headphones', \" +\n \"'bluetooth headphones'). Returns `roots` and `normalizedRoots` tables, each item with root, frequency \" +\n \"(how many keywords contain it), broadSearchVolume (summed search volume of those keywords), and \" +\n \"broadSearchVolumeRatio (0-1, relative to the top root), plus the per-keyword breakdowns and \" +\n \"latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheRoots>(\n { config: ctx.config, toolName: \"get_niche_roots\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/roots`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { GetCompetitorListResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getNicheCompetitorsTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_niche_competitors\",\n title: \"Get Competitors and Niche Statistics\",\n description:\n \"Use this for competitor ASINs, product titles, BSR, sales/revenue benchmarks, or niche \" +\n \"opportunity scoring. Retrieves the list of Competitors within the specified Niche along with \" +\n \"Niche statistics. For the Niche: keyword statistics, opportunity evaluation, benchmark median \" +\n \"values, and overall competitor strength assessment. For each Competitor: ASIN, product title, \" +\n \"BSR, category and full category tree, sales, revenue, ratings, reviews, price, number of \" +\n \"variations, image URL, total Ranking Juice, and ranking data. BSR supports ranking \" +\n \"Competitors by sales position within mentioned category. For the Ranking Juice breakdown by \" +\n \"title, bullets and description, use `get_ranking_juice`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<GetCompetitorListResult>(\n { config: ctx.config, toolName: \"get_niche_competitors\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/competitors`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { NicheRankingJuices } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The unique identifier of the Niche (from `list_niches`).\"),\n};\n\nexport const getRankingJuiceTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_ranking_juice\",\n title: \"Get Ranking Juices for a Niche\",\n description:\n \"Use this when the user asks 'how do I rank higher', 'what's my ranking juice', or wants \" +\n \"listing-optimization guidance. \" +\n \"Retrieves the Ranking Juices for each Competitor within the specified Niche. Ranking Juice is \" +\n \"DataDive's proprietary metric identifying the key factors driving search-ranking success \" +\n \"for each Competitor's listing. Returns currentListing and optimizedListing breakdowns \" +\n \"(rankingJuice + per-property contributions: title, bullets, description) plus per-competitor \" +\n \"listing scores and the latestResearchDate.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<NicheRankingJuices>(\n { config: ctx.config, toolName: \"get_ranking_juice\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ranking-juices`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport {\n RANK_RADAR_STATUSES,\n type ApiRankRadarItem,\n type ApiRankRadarList,\n type RankRadarItem,\n type RankRadarList,\n} from \"../types/api.js\";\nimport { tagFilterIdList } from \"./niche-tag-ids.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; a larger value is not honoured there. */\nexport const LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_RANK_RADARS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_RANK_RADARS_MAX_PAGE_SIZE}). Defaults to 20.`),\n nicheId: z\n .string()\n .min(1)\n .optional()\n .describe(\"Filter Rank Radars by Niche identifier. Use after `list_niches`.\"),\n status: z\n .enum(RANK_RADAR_STATUSES)\n .optional()\n .describe(\n \"Which Rank Radars to return. ACTIVE (the default) are tracking; PAUSED have been stopped \" +\n \"but keep their history and can be resumed; ARCHIVED have been deleted; ALL means active \" +\n \"and paused together. Archived Rank Radars are returned only by an explicit ARCHIVED.\",\n ),\n searchText: z.string().optional().describe(\"Filter Rank Radars by ASIN or product title.\"),\n spaces: tagFilterIdList(\"spaces\")\n .optional()\n .describe(\n \"Only Rank Radars whose niche is in at least one of these Spaces (ids from `list_spaces`, up to 100). \" +\n \"Combines with `labels` and the other filters with AND.\",\n ),\n labels: tagFilterIdList(\"labels\")\n .optional()\n .describe(\n \"Only Rank Radars whose niche has at least one of these labels (ids from `list_labels`, up to 100). \" +\n \"Combines with `spaces` and the other filters with AND.\",\n ),\n};\n\n/**\n * Drops the `krt_asin` row's internal identifiers and keeps the ASIN itself. The API sends\n * `asin: { id, krtId, asin }`, where `id` is an internal row id and `krtId` repeats the item's own\n * `id`; neither is accepted by any tool, so both are noise in the model's context.\n */\nfunction minimize(item: ApiRankRadarItem): RankRadarItem {\n const { asin, ...rest } = item;\n\n return { ...rest, asin: asin?.asin };\n}\n\nexport const listRankRadarsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_rank_radars\",\n title: \"List Rank Radars\",\n description:\n \"Use this to find a `rankRadarId` before calling `get_rank_radar_data`. Filter by `nicheId` \" +\n \"if the user has already identified a niche, or by `spaces` / `labels` (ids from `list_spaces` / \" +\n \"`list_labels`), which match through the Rank Radar's niche. \" +\n \"Retrieves a paginated list of Rank Radars — keyword-rank trackers monitoring organic and \" +\n \"sponsored positions for specific ASINs over time. Each item includes id, status (ACTIVE, \" +\n \"PAUSED or ARCHIVED), asin, marketplace, keywordCount, title, imageUrl, and summary metrics: \" +\n \"top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, \" +\n `total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADARS_MAX_PAGE_SIZE} ` +\n \"Rank Radars, so a large account needs several calls.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<RankRadarList> => {\n const page = await httpGet<ApiRankRadarList>(\n { config: ctx.config, toolName: \"list_rank_radars\" },\n \"/v1/niches/rank-radars\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n nicheId: args.nicheId,\n status: args.status,\n searchText: args.searchText,\n spaces: args.spaces,\n labels: args.labels,\n },\n );\n\n return { ...page, data: (page.data ?? []).map(minimize) };\n },\n};\n","/**\n * Shared inputs for the Rank Radar keyword tools (`get_rank_radar_data`, `get_rank_radar_sqp_data`,\n * `get_rank_radar_ppc_data`). All three API routes take the same date range and keyword paging\n * (RS-11683), so the fields are defined once here and spread into each tool's inputSchema,\n * mirroring seller-scoped.ts.\n */\n\nimport { z } from \"zod\";\n\n/** yyyy-mm-dd matcher used by both startDate and endDate. */\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Mirrors the API's `RANK_RADAR_KEYWORD_LIST_MAX_PAGE_SIZE`. A larger value is not rejected there:\n * it silently falls back to the default page of 20, so the cap has to be enforced here.\n */\nexport const RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;\n\n/** Mirrors the API's `MAX_DATE_RANGE_DAYS`; a longer range is refused with a 400. */\nexport const RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;\n\nexport const rankRadarKeywordQueryInputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID (from `list_rank_radars`).\"),\n startDate: z\n .string()\n .regex(ISO_DATE, \"startDate must be in yyyy-mm-dd format\")\n .describe(\"Start date of the range, yyyy-mm-dd (e.g. 2024-03-26).\"),\n endDate: z\n .string()\n .regex(ISO_DATE, \"endDate must be in yyyy-mm-dd format\")\n .describe(\n \"End date of the range, yyyy-mm-dd (e.g. 2024-04-26). Must be on or after startDate, \" +\n `and at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days after it.`,\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page of keywords, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Keywords per page (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\n/** Description sentence shared by the three tools: how to walk the pages. */\nexport const RANK_RADAR_KEYWORD_PAGING_NOTE =\n \"Results are paged by keyword: `data` holds one page, and the response carries currentPage, pageSize, \" +\n \"total, lastPage, hasNext and hasPrev. `total` is the Rank Radar's active keyword count; to read every \" +\n \"keyword, call again with `currentPage` + 1 while `hasNext` is true, using \" +\n `\\`pageSize\\` ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE} for large Rank Radars. Paused keywords are not included.`;\n\nexport function rankRadarPath(rankRadarId: string, suffix = \"\"): string {\n return `/v1/niches/rank-radars/${encodeURIComponent(rankRadarId)}${suffix ? `/${suffix}` : \"\"}`;\n}\n\n/** Query params common to the three routes; `undefined` values are dropped by the HTTP client. */\nexport function rankRadarKeywordQuery(args: {\n startDate: string;\n endDate: string;\n currentPage?: number;\n pageSize?: number;\n}) {\n return {\n startDate: args.startDate,\n endDate: args.endDate,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n };\n}\n","import { httpGet } from \"../http/client.js\";\nimport type { KrtKeyword, RankRadarKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { HandlerContext, ToolDefinition } from \"./types.js\";\n\nexport {\n RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n} from \"./rank-radar-keyword-query.js\";\n\nconst inputSchema = rankRadarKeywordQueryInputSchema;\n\ntype DateRange = { rankRadarId: string; startDate: string; endDate: string };\n\n/**\n * Reads every active keyword of a Rank Radar, one bounded API page at a time (RS-11791).\n *\n * This is what a call without `currentPage` / `pageSize` gets. Those inputs arrived after the API\n * started paging (RS-11683), and MCP clients keep the tool list they fetched when they connected —\n * ChatGPT, claude.ai and Claude Code were all seen still offering the three original inputs days\n * later, with no protocol message that makes them reload it. Such a client can never ask for page 2,\n * so without this it would silently see only the API's default first 20 keywords. Reading the pages\n * here keeps the tool's original contract — every keyword — for those clients, while each request\n * stays a normal, bounded page for the backend.\n *\n * Pages are read in order, not in parallel, so one tool call never puts more than one keyword page\n * on the backend at a time.\n */\nasync function readAllKeywords(args: DateRange, ctx: HandlerContext): Promise<RankRadarKeywordList> {\n const request = { config: ctx.config, toolName: \"get_rank_radar_data\" };\n const path = rankRadarPath(args.rankRadarId);\n const keywords: KrtKeyword[] = [];\n let page: RankRadarKeywordList;\n let currentPage = 1;\n\n do {\n page = await httpGet<RankRadarKeywordList>(\n request,\n path,\n rankRadarKeywordQuery({ ...args, currentPage, pageSize: RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE }),\n );\n keywords.push(...(page.data ?? []));\n currentPage++;\n // `lastPage` and an empty page both end the walk, so an API that stopped honouring\n // `currentPage` could not turn this into an endless loop.\n } while (page.hasNext && currentPage <= page.lastPage && (page.data?.length ?? 0) > 0);\n\n return {\n data: keywords,\n currentPage: 1,\n pageSize: keywords.length,\n total: page.total,\n lastPage: 1,\n hasNext: false,\n hasPrev: false,\n };\n}\n\nexport const getRankRadarDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_data\",\n title: \"Get Keyword Rankings for a Rank Radar\",\n description:\n \"Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), \" +\n `at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. ` +\n \"Retrieves historical keyword ranking data for the specified Rank Radar within the date range. \" +\n \"Without `currentPage` and `pageSize` it returns every active keyword in one result. To read one page \" +\n `instead — faster for large Rank Radars — pass \\`currentPage\\` and/or \\`pageSize\\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) ` +\n \"and continue with `currentPage` + 1 while `hasNext` is true. The response carries currentPage, pageSize, \" +\n \"total, lastPage, hasNext and hasPrev; `total` is the Rank Radar's active keyword count. Paused keywords \" +\n \"are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day \" +\n \"{ date, organicRank, sponsoredRank, impressionRank }), and `highlights` — the user tags on this \" +\n \"keyword. Each `highlights` entry is the same object `list_rank_radar_tags` returns (id, title, \" +\n \"color, description, startAt, endAt, type, krtId, krtKeywordId); its `id` is the `tagId` the \" +\n \"tag-management tools take. A rank of 101 means the product was not in the top 100 results that \" +\n \"day. For PPC metrics use `get_rank_radar_ppc_data`; for Search Query Performance use \" +\n \"`get_rank_radar_sqp_data`. Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n if (args.currentPage === undefined && args.pageSize === undefined) {\n return await readAllKeywords(args, ctx);\n }\n\n return await httpGet<RankRadarKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_data\" },\n rankRadarPath(args.rankRadarId),\n rankRadarKeywordQuery(args),\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { RankRadarSqpKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORD_PAGING_NOTE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = rankRadarKeywordQueryInputSchema;\n\nexport const getRankRadarSqpDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_sqp_data\",\n title: \"Get Search Query Performance for a Rank Radar\",\n description:\n \"Use this when the user asks how shoppers search, click, add to cart and buy for the keywords of a Rank \" +\n \"Radar — Amazon's Search Query Performance (SQP) data, from Brand Analytics. Requires startDate and \" +\n `endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it ` +\n \"fast. Each keyword's metrics are aggregated over the range: searchQueryVolume and searchQueryScore; \" +\n \"impressions, clicks, cart adds and purchases, each as the total across all sellers (`*TotalCount`), \" +\n \"this ASIN family's count (`*AsinCount`) and its share (`*AsinShare`); click, cart-add and purchase \" +\n \"rates; and ctr/cvr for the market (`*Total`) and for this ASIN family (`*Asin`). `numberOfDaysWithData` \" +\n \"says how many days of the range had SQP data; metrics are null when there is none, which is normal \" +\n \"for low-volume keywords, recent dates (Amazon publishes SQP with a delay) and sellers without Brand \" +\n `Analytics. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited ` +\n \"(about 60 requests/minute). Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<RankRadarSqpKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_sqp_data\" },\n rankRadarPath(args.rankRadarId, \"sqp\"),\n rankRadarKeywordQuery(args),\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { RankRadarPpcKeywordList } from \"../types/api.js\";\nimport {\n RANK_RADAR_KEYWORD_PAGING_NOTE,\n RANK_RADAR_MAX_DATE_RANGE_DAYS,\n rankRadarKeywordQuery,\n rankRadarKeywordQueryInputSchema,\n rankRadarPath,\n} from \"./rank-radar-keyword-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...rankRadarKeywordQueryInputSchema,\n includeCampaigns: z\n .boolean()\n .optional()\n .describe(\n \"When true, each keyword also carries `campaigns`: the per-campaign/ad-group breakdown (campaign and \" +\n \"ad group name, match type, targeting, spend, sales, clicks, impressions, orders, CPC, CTR, CVR, ACOS). \" +\n \"Defaults to false; leave it off unless the user asks which campaigns drive a keyword.\",\n ),\n};\n\nexport const getRankRadarPpcDataTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_rank_radar_ppc_data\",\n title: \"Get PPC Data for a Rank Radar\",\n description:\n \"Use this when the user asks about advertising (Sponsored Products) performance for the keywords of a \" +\n \"Rank Radar. Requires startDate and endDate (yyyy-mm-dd), at most \" +\n `${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are ` +\n \"aggregated over the range: sponsoredRank (median; 101 means not in the top 100 sponsored results), \" +\n \"impressionRank and impressionRankShare; how many exact, phrase, broad and auto campaigns target it; \" +\n \"organicSales, ppcSales, ppcSpend, costPerClicks, clickThroughRate, conversionRate, totalClicks, \" +\n \"totalImpressions, totalOrders and acos. Metrics are null or 0 when the seller runs no ads on a keyword. Set \" +\n `\\`includeCampaigns\\` for the per-campaign breakdown. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts ` +\n \"toward API usage and is rate limited (about 60 requests/minute). For the seller's campaigns as a \" +\n \"whole, use `list_ppc_campaigns`. Use after `list_rank_radars` to discover a `rankRadarId`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<RankRadarPpcKeywordList>(\n { config: ctx.config, toolName: \"get_rank_radar_ppc_data\" },\n rankRadarPath(args.rankRadarId, \"ppc\"),\n { ...rankRadarKeywordQuery(args), includeCampaigns: args.includeCampaigns },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { CreateRankRadarResult } from \"../types/api.js\";\nimport { confirmationRequired, needsConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst PATH = \"/v1/niches/rank-radars\";\n\nconst inputSchema = {\n asin: z.string().min(1).describe(\"The ASIN to track keyword rankings for.\"),\n numberOfKeywords: z\n .number()\n .int()\n .min(1)\n .describe(\"How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens.\"),\n nicheId: z\n .string()\n .min(1)\n .describe(\"The niche this Rank Radar belongs to. Get one from `list_niches`.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first.\",\n ),\n};\n\nexport const createRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_rank_radar\",\n title: \"Create a Rank Radar\",\n description:\n \"Use this to start tracking organic and sponsored keyword rankings for an ASIN within a niche. \" +\n \"⚠️ Spends Search Term tokens (cost scales with `numberOfKeywords`) and cannot be undone — set \" +\n \"`confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again \" +\n \"and creates a separate Rank Radar — if a call errors or times out, check `list_rank_radars` instead of \" +\n \"re-calling. The first call previews the creation and may return `warnings` alongside the cost note — \" +\n \"for example that this product family is already tracked by another Rank Radar. Show every warning \" +\n \"`message` to the user verbatim, before asking them to confirm; do not summarise it or decide on their \" +\n \"behalf. A warning never blocks creation. Returns the new `rankRadarId`; read its data \" +\n \"later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.\",\n inputSchema,\n // destructiveHint is technically \"additive\" here (this creates a new Rank Radar, it\n // doesn't destroy data), but we flag it destructive so clients prompt before the\n // irreversible token spend. The confirm gate enforces this server-side regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const request = { config: ctx.config, toolName: \"create_rank_radar\" };\n const body = {\n asin: args.asin,\n numberOfKeywords: args.numberOfKeywords,\n nicheId: args.nicheId,\n };\n\n if (needsConfirmation(args.confirm, ctx)) {\n // RS-11615. `dryRun` runs the API's full validation, quota check and duplicate check\n // and creates nothing, so the confirmation payload can carry the API's own warnings —\n // \"this family is already tracked\", say — instead of only a static cost note. The\n // wording therefore comes from the same endpoint on both passes as the one the DataDive\n // UI shows, and cannot drift from it.\n //\n // `dryRun` is deliberately absent from inputSchema and set only here. If the model could\n // set it, it could dry-run, read a success-shaped response, and tell the user their Rank\n // Radar was created when nothing was created and nothing was spent.\n const preview = await httpPost<CreateRankRadarResult>(request, PATH, { ...body, dryRun: true });\n\n // A backend older than RS-11615 ignores the unknown `dryRun` field and creates the Rank\n // Radar for real. It gives itself away by returning an id where a dry run returns null.\n // Report what actually happened rather than claiming a confirmation is still pending for\n // something already paid for.\n if (preview.rankRadarId != null) return preview;\n\n return confirmationRequired(\n `Creating this Rank Radar consumes Search Term tokens — one per keyword tracked (${args.numberOfKeywords} requested).`,\n preview.warnings,\n );\n }\n\n // Confirmed, or the gate is opted out via DATADIVE_AUTO_CONFIRM_WRITES. On that opt-out\n // path there is no confirmation step at all, so the `warnings` in this response are the\n // only ones the user will ever see — which is why they are returned, not dropped.\n return await httpPost<CreateRankRadarResult>(request, PATH, body);\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type CreateNicheDiveResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe(\n \"Amazon marketplace of the seed ASIN. Note these are full domain suffixes: com, ca, \" +\n \"co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`).\",\n ),\n asin: z\n .string()\n .min(1)\n .describe(\"The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it.\"),\n numberOfCompetitors: z\n .number()\n .int()\n .min(2)\n .describe(\"How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens.\"),\n fastKeywordsRequestId: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(\n \"Links the dive to a Fast Keywords run for the same product: the `requestId` returned by \" +\n \"`run_fast_keywords` or `refresh_fast_keywords`. Leave unset for a dive that follows no Fast Keywords run.\",\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\"Must be true to proceed — creating a dive spends dive tokens. Confirm the cost with the user first.\"),\n};\n\nexport const createNicheDiveTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_niche_dive\",\n title: \"Create a Niche Dive\",\n description:\n \"Use this to start new niche research from a seed ASIN. ⚠️ Spends dive tokens (cost scales with \" +\n \"`numberOfCompetitors`) and cannot be undone — set `confirm: true` only after the user approves the cost. \" +\n \"Not safe to retry: each call spends tokens again and starts a separate dive — if a call errors or times \" +\n \"out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. \" +\n \"The dive runs asynchronously: this returns immediately with a `diveId` and an `estimatedCompletionDate`. \" +\n \"Poll `get_dive_status` with that `diveId` until it reports `success` (which carries the new `nicheId` for \" +\n \"use with `list_niches`, `get_niche_keywords`, etc.). Requires `marketplace`, `asin`, and `numberOfCompetitors` (min 2). \" +\n \"After a Fast Keywords run for the same product, pass its `requestId` as `fastKeywordsRequestId` to link the dive to it.\",\n inputSchema,\n // destructiveHint is technically \"additive\" here (a dive creates a new niche, it\n // doesn't destroy data), but we flag it destructive so clients prompt before the\n // irreversible token spend. The confirm gate enforces this server-side regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n `Creating this dive consumes dive tokens — one batch per competitor analyzed (${args.numberOfCompetitors} requested).`,\n );\n if (pending) return pending;\n\n return await httpPost<CreateNicheDiveResult>(\n { config: ctx.config, toolName: \"create_niche_dive\" },\n \"/v1/niches/dives\",\n {\n marketplace: args.marketplace,\n asin: args.asin,\n numberOfCompetitors: args.numberOfCompetitors,\n // JSON.stringify drops it when unset, so a dive without a Fast Keywords run sends the old body.\n fastKeywordsRequestId: args.fastKeywordsRequestId,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type CreateNicheDiveResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `MIN_REQUIRED_ASINS_FOR_DIVE` and `MAX_ASINS_PER_DIVE_WITH_COMPETITORS`. */\nexport const DIVE_FROM_COMPETITORS_MIN_ASINS = 2;\nexport const DIVE_FROM_COMPETITORS_MAX_ASINS = 200;\n\n/**\n * The API upper-cases and trims each ASIN before its uniqueness check, so \"b0abc\" and \"B0ABC\" are\n * the same competitor there. Normalising here makes the duplicate check below agree with it, and\n * the cost note count the ASINs the API will actually charge for.\n */\nconst normalizedAsins = z\n .array(z.string().trim().min(1).toUpperCase())\n .min(DIVE_FROM_COMPETITORS_MIN_ASINS)\n .max(DIVE_FROM_COMPETITORS_MAX_ASINS)\n .refine((asins) => new Set(asins).size === asins.length, {\n message: \"asins must not contain the same ASIN twice\",\n });\n\nconst inputSchema = {\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe(\n \"Amazon marketplace where the ASINs are listed. Full domain suffixes: com, ca, co.uk, com.mx, in, \" +\n \"fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`).\",\n ),\n asins: normalizedAsins.describe(\n `The exact competitor ASINs to build the niche from, ${DIVE_FROM_COMPETITORS_MIN_ASINS} to ` +\n `${DIVE_FROM_COMPETITORS_MAX_ASINS}, each once. No other competitors are added.`,\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — this dive spends dive tokens for every ASIN. Confirm the cost with the user first.\",\n ),\n};\n\nexport const createNicheDiveFromCompetitorsListTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_niche_dive_from_competitors_list\",\n title: \"Create a Niche Dive from a Competitor List\",\n description:\n \"Use this when the user already knows which products belong in the niche — e.g. they list competitor \" +\n \"ASINs, or picked them from search results — and wants niche research on exactly those. Unlike \" +\n \"`create_niche_dive`, which discovers competitors around one seed ASIN, this builds the niche from the \" +\n `given ASINs only (${DIVE_FROM_COMPETITORS_MIN_ASINS}-${DIVE_FROM_COMPETITORS_MAX_ASINS}). ⚠️ Spends dive ` +\n \"tokens for every ASIN and cannot be undone — set `confirm: true` only after the user approves the cost. \" +\n \"Not safe to retry: each call spends tokens again and starts a separate dive — if a call errors or times \" +\n \"out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. Fails without spending \" +\n \"tokens when the ASIN count is over the plan's limit, when the account is at its niche limit, or when \" +\n \"Amazon product data cannot be found for enough of the ASINs. The dive runs asynchronously: this \" +\n \"returns a `diveId` and an `estimatedCompletionDate`; poll `get_dive_status` with the `diveId` until it \" +\n \"reports `success`, which carries the new `nicheId`.\",\n inputSchema,\n // Additive rather than destructive, but flagged destructive so clients prompt before the\n // irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n `Creating this dive consumes dive tokens — one batch per ASIN analyzed (${args.asins.length} ASINs given).`,\n );\n if (pending) return pending;\n\n return await httpPost<CreateNicheDiveResult>(\n { config: ctx.config, toolName: \"create_niche_dive_from_competitors_list\" },\n \"/v1/niches/dive_with_competitors\",\n { marketplace: args.marketplace, asins: args.asins },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport type { RediveNicheResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to re-dive. Get one from `list_niches`.\"),\n mode: z\n .enum([\"same_competitors\", \"discover\"])\n .describe(\n \"How to pick the competitors for the refreshed dive. `same_competitors` re-runs the niche's \" +\n \"current competitor set and takes no other argument — use it to refresh stale data. `discover` \" +\n \"searches for a fresh competitor set, which is what you want when the niche has changed.\",\n ),\n numberOfCompetitors: z\n .number()\n .int()\n .min(2)\n .optional()\n .describe(\n \"Total ASINs the refreshed dive should contain, including `lockedAsins`. Required in `discover` \" +\n \"mode and rejected in `same_competitors` mode. Dive tokens are spent per ASIN.\",\n ),\n heroAsin: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"`discover` mode only. Seed product the competitor discovery starts from. Defaults to the niche's \" +\n \"highest-selling competitor, preferring one the user's own connected seller account owns.\",\n ),\n lockedAsins: z\n .array(z.string().min(1))\n .optional()\n .describe(\n \"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery \" +\n \"finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`.\",\n ),\n excludedAsins: z\n .array(z.string().min(1))\n .optional()\n .describe(\"`discover` mode only. ASINs discovery must never select.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — a re-dive spends dive tokens. Confirm the cost with the user first.\",\n ),\n};\n\nexport const rediveNicheTool: ToolDefinition<typeof inputSchema> = {\n name: \"redive_niche\",\n title: \"Re-dive an Existing Niche\",\n description:\n \"Use this to refresh an existing niche's research with current Amazon data, instead of creating a new \" +\n \"niche with `create_niche_dive`. ⚠️ Spends dive tokens (one batch per ASIN dived) and cannot be undone — \" +\n \"set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens \" +\n \"again and starts a separate re-dive — if a call errors or times out, poll `get_dive_status` instead of \" +\n \"re-calling. Two modes: `same_competitors` re-dives the \" +\n \"niche's current competitor set (no other argument needed) and `discover` finds a fresh set, sized by \" +\n \"`numberOfCompetitors` and steerable with `heroAsin` / `lockedAsins` / `excludedAsins`. Runs \" +\n \"asynchronously: returns a `diveId` and an `estimatedCompletionDate` — poll `get_dive_status` with that \" +\n \"`diveId` until it reports `success`. The niche keeps its `nicheId`, so existing rank radars and reports \" +\n \"follow the refreshed data.\",\n inputSchema,\n // Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the\n // irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // `numberOfCompetitors` cannot be required in the schema (same_competitors mode forbids it), so\n // check it here — before the confirm gate, because a gate that cannot state the cost is worse\n // than no gate. Everything else is left to the API, which names the offending field.\n if (args.mode === \"discover\" && args.numberOfCompetitors === undefined) {\n throw new ApiError(\n \"bad_request\",\n 400,\n \"numberOfCompetitors is required in `discover` mode — it is how many ASINs the refreshed dive \" +\n \"will contain, and therefore what it costs. Ask the user how many competitors they want \" +\n '(minimum 2), or use mode \"same_competitors\" to re-dive the niche\\'s current set.',\n );\n }\n\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n args.mode === \"discover\"\n ? `Re-diving this niche consumes dive tokens — one batch per ASIN analyzed (${args.numberOfCompetitors} requested).`\n : \"Re-diving this niche consumes dive tokens — one batch per competitor currently in the niche. \" +\n \"Use `get_niche_competitors` first if the user needs the exact count.\",\n );\n if (pending) return pending;\n\n // Mode-specific fields are forwarded as given rather than filtered by mode: the API rejects a\n // field that does not belong to the requested mode with a message naming it, which is a clearer\n // correction for the model than silently dropping what it asked for. `undefined` fields are\n // dropped by JSON.stringify, so an unused optional never reaches the wire.\n return await httpPost<RediveNicheResult>(\n { config: ctx.config, toolName: \"redive_niche\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/redive`,\n {\n mode: args.mode,\n numberOfCompetitors: args.numberOfCompetitors,\n heroAsin: args.heroAsin,\n lockedAsins: args.lockedAsins,\n excludedAsins: args.excludedAsins,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { DiveStatus } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n diveId: z\n .string()\n .min(1)\n .describe(\n \"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or \" +\n \"`redive_niche`, or the `refresh.diveId` returned by `add_niche_keywords`.\",\n ),\n};\n\nexport const getDiveStatusTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_dive_status\",\n title: \"Get Niche Dive Status\",\n description:\n \"Use this to poll a niche dive started with `create_niche_dive`, `create_niche_dive_from_competitors_list` or \" +\n \"`redive_niche`, or the re-dive `add_niche_keywords` started on a stale niche, until it finishes. \" +\n \"Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), \" +\n \"`success` (with the `nicheId` plus `tokensUsed`/`tokensLeft`), or `error` (with an `error` message). \" +\n \"On `success`, use the `nicheId` with `list_niches`, `get_niche_keywords`, `get_niche_competitors`, etc. \" +\n \"(for a re-dive it is the same niche you passed in, now carrying refreshed data). \" +\n \"Re-poll periodically — dives can take minutes; the `estimatedCompletionDate` hints at when to check.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<DiveStatus>(\n { config: ctx.config, toolName: \"get_dive_status\" },\n `/v1/niches/dives/${encodeURIComponent(args.diveId)}`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport {\n NICHE_KEYWORDS_MAX_PER_CALL,\n type AddNicheKeywordsResult,\n type MaxTokensExceededBody,\n} from \"../types/api.js\";\nimport { confirmationRequired, needsConfirmation, type ConfirmationRequired } from \"./confirm.js\";\nimport { nicheKeywordList } from \"./niche-keyword-list.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche whose Master Keyword List to add to. Get one from `list_niches`.\"),\n keywords: nicheKeywordList.describe(\n `The keywords to add, 1–${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are ` +\n \"lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool \" +\n \"allows only 2 calls per minute per niche.\",\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Leave unset on the first call. Set to true only after the user approved the Dive token cost from a \" +\n \"`confirmation_required` response — it lets the add re-dive a stale niche.\",\n ),\n maxTokensUsed: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\n \"With `confirm: true`: the `tokenCost` the user approved. The call then fails with nothing saved or \" +\n \"charged rather than spend more. Ignored without `confirm`, where the tool never spends anything.\",\n ),\n};\n\n/** The gate payload for a stale niche, carrying the API's own figure for the re-dive. */\nexport interface AddNicheKeywordsConfirmation extends ConfirmationRequired {\n /** The most the re-dive can cost, in Dive tokens: the niche's competitor count. */\n tokenCost: number;\n}\n\nfunction isMaxTokensExceededBody(body: unknown): body is MaxTokensExceededBody {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return b.error === \"MAX_TOKENS_EXCEEDED\" && typeof b.tokensUsed === \"number\";\n}\n\nexport const addNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_niche_keywords\",\n title: \"Add Keywords to a Niche's Master Keyword List\",\n description:\n \"Use this to add keywords to a niche's Master Keyword List (MKL): the user's own search terms, or \" +\n \"keywords from the niche's Outliers or Residue lists. Free while the niche is fresh (dived in the last \" +\n \"24 hours, or a dive is running): the first call adds the keywords at once and they are readable with \" +\n \"`get_niche_keywords`. On a stale niche, any keyword new to the niche makes the add re-dive the whole \" +\n \"niche with its current competitors, which spends one Dive token per competitor. The first call then \" +\n \"saves and spends nothing and returns `confirmation_required` with `tokenCost`; after the user approves, \" +\n \"call again with `confirm: true` and `maxTokensUsed` set to that `tokenCost`. Keywords moved in from \" +\n \"Outliers or Residue never cost anything. Returns `originBreakdown` (`alreadyInMkl`, `takenFromOutliers`, \" +\n \"`takenFromResidue`, `addedManually`) and `tokensUsed`. When `refresh` is present a re-dive is running: \" +\n \"poll `get_dive_status` with `refresh.diveId`; the `addedManually` keywords show in the MKL once it \" +\n \"succeeds. A confirmed call is not safe to retry: if it errors or times out, poll `get_dive_status` or \" +\n \"re-read `get_niche_keywords` instead of re-calling. Limited to 2 calls per minute per niche, so batch \" +\n \"keywords into one call. Undo with `remove_niche_keywords`. A keyword hidden by the niche's keyword \" +\n \"exclusions stays hidden after an add: check `list_keyword_exclusions` and remove the exclusion with \" +\n \"`remove_keyword_exclusions` first.\",\n inputSchema,\n // Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients\n // need to prompt on. The confirm gate covers the paid path regardless.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx): Promise<AddNicheKeywordsResult | AddNicheKeywordsConfirmation> => {\n const request = { config: ctx.config, toolName: \"add_niche_keywords\" };\n const path = `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`;\n\n if (needsConfirmation(args.confirm, ctx)) {\n // A real call capped at 0 tokens, not a dry run followed by a real call: a fresh niche is free,\n // so this one request does the add, while a stale one is refused with the re-dive's cost and\n // nothing saved. A dry run would spend half the route's 2-per-minute allowance on every free add.\n // `maxTokensUsed` is set only here and on the confirmed path, never taken from an unconfirmed\n // caller, so an unconfirmed call can never spend.\n try {\n return await httpPost<AddNicheKeywordsResult>(request, path, {\n keywords: args.keywords,\n maxTokensUsed: 0,\n });\n } catch (err) {\n if (err instanceof ApiError && isMaxTokensExceededBody(err.body)) {\n const tokenCost = err.body.tokensUsed;\n return {\n ...confirmationRequired(\n `This niche was last dived more than 24 hours ago, so adding keywords that are new to it ` +\n `re-dives the whole niche with its current competitors: up to ${tokenCost} Dive tokens, one ` +\n \"per competitor (less if some can no longer be found). Nothing was added or charged yet.\",\n ),\n tokenCost,\n };\n }\n throw err;\n }\n }\n\n // Confirmed, or the gate is opted out via DATADIVE_AUTO_CONFIRM_WRITES. `maxTokensUsed` left unset\n // means no cap; JSON.stringify drops it.\n return await httpPost<AddNicheKeywordsResult>(request, path, {\n keywords: args.keywords,\n maxTokensUsed: args.maxTokensUsed,\n });\n },\n};\n","/**\n * Keyword-list input shared by the niche keyword write tools (add/remove MKL keywords).\n *\n * The API lower-cases and trims every keyword before its own uniqueness check, so \"Dog Hat\" and\n * \"dog hat \" are the same keyword there and sending both is a 400. Normalising here makes the\n * duplicate check agree with the API and turns that mistake into a schema error the model sees\n * before any call, which matters because these routes allow only 2 writes per minute per niche.\n */\n\nimport { z } from \"zod\";\nimport { NICHE_KEYWORD_MAX_LENGTH, NICHE_KEYWORDS_MAX_PER_CALL } from \"../types/api.js\";\n\n/**\n * A fresh schema per use: one instance shared by two fields is emitted as a `$ref` to the first in the\n * tool's JSON Schema, which some MCP clients do not resolve.\n */\nexport const nicheKeyword = () => z.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);\n\n/** Rejects a list holding the same keyword twice, compared after normalisation. */\nexport function uniqueKeywords<T extends z.ZodType<string[]>>(schema: T, field: string) {\n return schema.refine((keywords) => new Set(keywords).size === keywords.length, {\n message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`,\n });\n}\n\nexport const nicheKeywordList = uniqueKeywords(\n z.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),\n \"keywords\",\n);\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { NICHE_KEYWORDS_MAX_PER_CALL, type RemoveNicheKeywordsResult } from \"../types/api.js\";\nimport { nicheKeywordList } from \"./niche-keyword-list.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche whose Master Keyword List to remove from. Get one from `list_niches`.\"),\n keywords: nicheKeywordList.describe(\n `The keywords to take out, 1–${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They ` +\n \"are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool \" +\n \"allows only 2 calls per minute per niche.\",\n ),\n};\n\nexport const removeNicheKeywordsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_niche_keywords\",\n title: \"Remove Keywords from a Niche's Master Keyword List\",\n description:\n \"Use this to undo `add_niche_keywords`: take keywords back out of a niche's Master Keyword List (MKL). \" +\n \"Only keywords that were added to the MKL can be removed — `get_niche_keywords` shows each keyword's \" +\n \"`origin`. What happens depends on that origin, and the response reports it per keyword: \" +\n \"`removedManual` — added by hand (`manual`), deleted from the niche; `sentBackToOutliers` / \" +\n \"`sentBackToResidue` — moved in from those lists (`outlier` / `residue`), listed there again with their \" +\n \"data kept; `notRemovable` — found by the dive itself (`mkl`) or not in the niche, left unchanged. To hide \" +\n \"a keyword the dive found, use `add_keyword_exclusions` instead. Free, and safe to repeat: a keyword already \" +\n \"removed comes back under `notRemovable`. Adding a hand-added keyword back later re-dives the niche and \" +\n \"costs Dive tokens if the niche is stale by then. Limited to 2 calls per minute per niche.\",\n inputSchema,\n // Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<RemoveNicheKeywordsResult>(\n { config: ctx.config, toolName: \"remove_niche_keywords\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keywords/remove`,\n { keywords: args.keywords },\n );\n },\n};\n","/**\n * Input shared by add_keyword_exclusions and remove_keyword_exclusions.\n *\n * The API normalises entries like MKL keywords (lower-cased, trimmed, unique per list), so the\n * per-entry rules come from niche-keyword-list.ts. The 1–100 total spans both optional lists, which\n * a raw input shape cannot express, so `assertExclusionCount` checks it in the handler — before the\n * call, so a request the API would refuse never reaches it.\n */\n\nimport { z } from \"zod\";\nimport { ApiError } from \"../http/errors.js\";\nimport { KEYWORD_EXCLUSIONS_MAX_PER_CALL } from \"../types/api.js\";\nimport { nicheKeyword, uniqueKeywords } from \"./niche-keyword-list.js\";\n\nconst entries = (field: string) =>\n uniqueKeywords(z.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();\n\nexport const keywordExclusionsInputSchema = (verb: \"exclude\" | \"stop excluding\") => ({\n nicheId: z.string().min(1).describe(\"The niche whose keyword exclusions to change. Get one from `list_niches`.\"),\n exact: entries(\"exact\").describe(\n `Exact-match entries to ${verb}: each one hides the single keyword that matches it exactly. At most 255 ` +\n \"characters each; lower-cased and trimmed, and must be unique after that.\",\n ),\n phrase: entries(\"phrase\").describe(\n `Phrase-match entries to ${verb}: each one hides every keyword that contains the phrase (e.g. \"puppy\" ` +\n 'hides \"puppy hat\" and \"hat for puppy\"). At most 255 characters each; lower-cased and trimmed, and ' +\n \"must be unique after that.\",\n ),\n});\n\nexport function assertExclusionCount(exact: string[] | undefined, phrase: string[] | undefined): void {\n const total = (exact?.length ?? 0) + (phrase?.length ?? 0);\n if (total === 0 || total > KEYWORD_EXCLUSIONS_MAX_PER_CALL) {\n throw new ApiError(\n \"bad_request\",\n 400,\n `Send 1 to ${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` and \\`phrase\\` ` +\n `(got ${total}). Split a longer list over several calls.`,\n );\n }\n}\n","import { httpPost } from \"../http/client.js\";\nimport {\n KEYWORD_EXCLUSIONS_MAX_PER_CALL,\n KEYWORD_EXCLUSIONS_MAX_PER_NICHE,\n type AddKeywordExclusionsResult,\n} from \"../types/api.js\";\nimport { assertExclusionCount, keywordExclusionsInputSchema } from \"./keyword-exclusions-input.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = keywordExclusionsInputSchema(\"exclude\");\n\nexport const addKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_keyword_exclusions\",\n title: \"Exclude Keywords from a Niche\",\n description:\n \"Use this to hide irrelevant keywords from a niche's Master Keyword List — e.g. other brands, wrong \" +\n \"product types. `exact` entries hide the one keyword that matches exactly; `phrase` entries hide every \" +\n \"keyword containing the phrase, so prefer `exact` unless the user wants a whole family of keywords gone. \" +\n \"The excluded keywords drop out of `get_niche_keywords` at once, wherever they come from: the dive, \" +\n \"Outliers, Residue, or ones added with `add_niche_keywords`. Nothing is deleted: \" +\n \"`remove_keyword_exclusions` brings them back. Only the exclusions change — every other niche setting is \" +\n `kept. Send 1–${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` and \\`phrase\\`; a niche ` +\n `can hold at most ${KEYWORD_EXCLUSIONS_MAX_PER_NICHE} exclusions. Free, no tokens, and safe to repeat: ` +\n \"entries already excluded come back under `unchanged`. Returns `added`, `unchanged` and the full `exact` / \" +\n \"`phrase` lists after the change. Limited to 30 calls per minute per niche, so batch entries into one call.\",\n inputSchema,\n // Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n assertExclusionCount(args.exact, args.phrase);\n return await httpPost<AddKeywordExclusionsResult>(\n { config: ctx.config, toolName: \"add_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`,\n { exact: args.exact, phrase: args.phrase },\n );\n },\n};\n","import { httpPost } from \"../http/client.js\";\nimport { KEYWORD_EXCLUSIONS_MAX_PER_CALL, type RemoveKeywordExclusionsResult } from \"../types/api.js\";\nimport { assertExclusionCount, keywordExclusionsInputSchema } from \"./keyword-exclusions-input.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = keywordExclusionsInputSchema(\"stop excluding\");\n\nexport const removeKeywordExclusionsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_keyword_exclusions\",\n title: \"Remove Keyword Exclusions from a Niche\",\n description:\n \"Use this to undo `add_keyword_exclusions`, so the keywords an exclusion hid show in the niche's Master \" +\n \"Keyword List (`get_niche_keywords`) again. Pass the entries exactly as `list_keyword_exclusions` shows \" +\n \"them, `exact` and `phrase` separately: removing a phrase does not touch an exact entry for the same text, \" +\n \"and a keyword also hidden by another remaining exclusion stays hidden. Only the exclusions change — every \" +\n `other niche setting is kept. Send 1–${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \\`exact\\` ` +\n \"and `phrase`. Free, and safe to repeat: entries that are not excluded come back under `unchanged`. \" +\n \"Returns `removed`, `unchanged` and the full `exact` / `phrase` lists after the change. Limited to 30 \" +\n \"calls per minute per niche.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n assertExclusionCount(args.exact, args.phrase);\n return await httpPost<RemoveKeywordExclusionsResult>(\n { config: ctx.config, toolName: \"remove_keyword_exclusions\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions/remove`,\n { exact: args.exact, phrase: args.phrase },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche to delete. Get one from `list_niches`.\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — deleting a niche destroys its keywords, competitors and dive history \" +\n \"permanently. Confirm with the user first.\",\n ),\n};\n\nexport const deleteNicheTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_niche\",\n title: \"Delete a Niche\",\n description:\n \"Use this to permanently remove a niche the user no longer needs. ⚠️ Deletes the niche and everything \" +\n \"attached to it — keywords, competitors, all dives and diveboxes — and cannot be undone; the dive tokens \" +\n \"already spent on it are NOT refunded. Set `confirm: true` only after the user approves. A niche used by \" +\n \"any Rank Radar cannot be deleted: the call fails with a conflict, so delete or archive those rank radars \" +\n \"first (`list_rank_radars` with the `nicheId` filter shows them).\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this niche removes its keywords, competitors and all dive history permanently. \" +\n \"The dive tokens already spent on it are not refunded.\",\n );\n if (pending) return pending;\n\n // The endpoint answers 204 No Content, so there is no payload to return. Hand the\n // model an explicit result instead of `null`, which reads as a failed call.\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_niche\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}`,\n );\n return { status: \"deleted\", nicheId: args.nicheId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to pause (from `list_rank_radars`).\"),\n};\n\nexport const pauseRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"pause_rank_radar\",\n title: \"Pause a Rank Radar\",\n description:\n \"Use this to pause tracking on a Rank Radar without losing anything — the action the DataDive API calls \" +\n \"'archiving', so when a user asks to archive a Rank Radar, this is the tool they mean. Tracking stops for \" +\n \"all of its keywords and the Daily Tracked Keywords quota they held is freed for other Rank Radars, while \" +\n \"the Rank Radar and its ranking history are kept. Fully reversible with `resume_rank_radar`, so it needs \" +\n \"no `confirm` — prefer it over `delete_rank_radar` whenever the user may want the data back. Paused Rank \" +\n \"Radars show up in `list_rank_radars` under `status: PAUSED` — not `ARCHIVED`, which covers Rank Radars \" +\n \"removed with `delete_rank_radar`. Safe to repeat: pausing an already paused Rank Radar succeeds and \" +\n \"changes nothing.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // 204 No Content — return an explicit result rather than the `null` body.\n await httpPost<null>(\n { config: ctx.config, toolName: \"pause_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/archive`,\n {},\n );\n return { status: \"paused\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to resume (from `list_rank_radars`).\"),\n};\n\nexport const resumeRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"resume_rank_radar\",\n title: \"Resume a Paused Rank Radar\",\n description:\n \"Use this to restart tracking on a Rank Radar that was paused with `pause_rank_radar` (shown by \" +\n \"`list_rank_radars` under `status: PAUSED`; a deleted, `ARCHIVED` one cannot be resumed). Keywords are \" +\n \"resumed as far as the available Daily Tracked Keywords quota allows, most relevant first — so with a \" +\n \"tight quota only part of the original keyword set comes back; check `get_quota` first if that matters. \" +\n \"Reversible with `pause_rank_radar`, so it needs no `confirm`. Fails with a bad-request error when \" +\n \"there is no quota left at all. Safe to repeat: resuming an already active Rank Radar changes nothing.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n await httpPost<null>(\n { config: ctx.config, toolName: \"resume_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/resume`,\n {},\n );\n return { status: \"resumed\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID to delete (from `list_rank_radars`).\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — deleting a Rank Radar destroys its ranking history permanently. \" +\n \"Confirm with the user first.\",\n ),\n};\n\nexport const deleteRankRadarTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_rank_radar\",\n title: \"Delete a Rank Radar\",\n description:\n \"Use this to permanently remove a Rank Radar and all of its keyword ranking history. ⚠️ Cannot be undone \" +\n \"— there is no restore endpoint; set `confirm: true` only after the user approves. It does free the Daily \" +\n \"Tracked Keywords quota those keywords held. Deleted Rank Radars are what `list_rank_radars` returns for \" +\n \"`status: ARCHIVED`. If the user only wants to stop tracking for a while and keep the history — or asks \" +\n \"to 'archive' the Rank Radar, which is what the DataDive API calls pausing — use `pause_rank_radar` \" +\n \"instead: it frees the same quota and is reversible with `resume_rank_radar`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this Rank Radar removes its keyword ranking history permanently. It frees the Daily \" +\n \"Tracked Keywords quota, but the history cannot be recovered — `pause_rank_radar` frees the \" +\n \"same quota and is reversible.\",\n );\n if (pending) return pending;\n\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_rank_radar\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}`,\n );\n return { status: \"deleted\", rankRadarId: args.rankRadarId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { ADD_SEARCH_TERMS_MAX, type AddSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID to add the search terms to (from `list_rank_radars`).\"),\n searchTerms: z\n .array(z.string().min(1))\n .min(1)\n .max(ADD_SEARCH_TERMS_MAX)\n .describe(\n `The keywords to start tracking, 1–${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored ` +\n \"special characters (@, #, _, parentheses, …) are rejected.\",\n ),\n};\n\nexport const addRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_rank_radar_search_terms\",\n title: \"Add Search Terms to a Rank Radar\",\n description:\n \"Use this to start tracking extra keywords on an existing Rank Radar, instead of creating a new one with \" +\n \"`create_rank_radar`. New keywords are added to the niche and tracked from now on; any matching keyword \" +\n \"that was previously paused is resumed. Each newly tracked keyword takes one Daily Tracked Keywords slot, \" +\n \"and the call fails if the quota is exhausted — check `get_quota` first. Reversible with \" +\n \"`pause_rank_radar_search_terms`, which frees the slots again, so it needs no `confirm`. Returns \" +\n \"`originBreakdown` (where each submitted term ended up) and `keywordToRankRadarKeywordIdMap`, whose ids \" +\n \"are what the pause/resume search-term tools take.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<AddSearchTermsResult>(\n { config: ctx.config, toolName: \"add_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms`,\n { searchTerms: args.searchTerms },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID the search terms belong to.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .describe(\n \"The Rank Radar keyword ids to pause. These are the `id` values from `get_rank_radar_data`, or the \" +\n \"values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` — not the \" +\n \"keyword text.\",\n ),\n};\n\nexport const pauseRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"pause_rank_radar_search_terms\",\n title: \"Pause Search Terms of a Rank Radar\",\n description:\n \"Use this to stop tracking individual keywords on a Rank Radar while keeping the Rank Radar itself \" +\n \"active — the way to trim a keyword set the user finds too broad. The DataDive API calls this \" +\n \"'archiving' the search terms; nothing is lost. The paused keywords keep their history and their Daily \" +\n \"Tracked Keywords slots are freed for other keywords. Reversible with `resume_rank_radar_search_terms`, \" +\n \"so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. \" +\n \"Safe to repeat: already-paused terms are left unchanged.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n // 204 No Content — return an explicit result rather than the `null` body.\n await httpPost<null>(\n { config: ctx.config, toolName: \"pause_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms/archive`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n return {\n status: \"paused\",\n rankRadarId: args.rankRadarId,\n rankRadarKeywordIds: args.rankRadarKeywordIds,\n };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z.string().uuid().describe(\"The Rank Radar UUID the search terms belong to.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .describe(\n \"The Rank Radar keyword ids to resume. These are the `id` values from `get_rank_radar_data`, or the \" +\n \"values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` — not the \" +\n \"keyword text.\",\n ),\n};\n\nexport const resumeRankRadarSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"resume_rank_radar_search_terms\",\n title: \"Resume Paused Search Terms of a Rank Radar\",\n description:\n \"Use this to start tracking keywords again that were paused with `pause_rank_radar_search_terms`. Each \" +\n \"resumed keyword takes back one Daily Tracked Keywords slot, and the call fails if the quota is \" +\n \"exhausted — check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, so it needs no \" +\n \"`confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: \" +\n \"already-active terms are left unchanged.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n await httpPost<null>(\n { config: ctx.config, toolName: \"resume_rank_radar_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/search-terms/resume`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n return {\n status: \"resumed\",\n rankRadarId: args.rankRadarId,\n rankRadarKeywordIds: args.rankRadarKeywordIds,\n };\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { RankRadarTagList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `PaginationParams.MAX_PAGE_SIZE`; a larger value is not honoured there. */\nexport const LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID whose tags to list (from `list_rank_radars`).\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Items per page (max ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\nexport const listRankRadarTagsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_rank_radar_tags\",\n title: \"List Tags of a Rank Radar\",\n description:\n \"Use this to find a `tagId` before calling `update_rank_radar_tag`, `delete_rank_radar_tag`, \" +\n \"`add_rank_radar_tag_search_terms` or `remove_rank_radar_tag_search_terms`. Returns the user \" +\n \"tags (\\\"highlights\\\") on the specified Rank Radar, ordered by title (case-insensitive, \" +\n \"ascending). Each row carries `id`, `title`, `color`, `description`, `startAt`, `endAt`, \" +\n \"`createdAt`, and `keywordCount` — the number of Rank Radar keywords this tag is applied to. \" +\n \"The system \\\"paused\\\" tag (the one the quota pauser writes with a reserved colour) is not \" +\n \"listed and cannot be addressed from these tools. Supports pagination metadata \" +\n \"(currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most \" +\n `${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE} tags.`,\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx): Promise<RankRadarTagList> => {\n return await httpGet<RankRadarTagList>(\n { config: ctx.config, toolName: \"list_rank_radar_tags\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport {\n RankRadarTag,\n TAG_HEX_COLOR_PATTERN,\n TAG_KEYWORD_IDS_MAX,\n TAG_RESERVED_COLOR,\n TAG_TITLE_MAX_LENGTH,\n} from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID to create the tag on (from `list_rank_radars`).\"),\n title: z\n .string()\n .min(1)\n .max(TAG_TITLE_MAX_LENGTH)\n .describe(\n `Display title (1–${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on ` +\n \"this Rank Radar, case-insensitive. A clash returns 409 with \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }` — reuse that id instead of retrying.\",\n ),\n color: z\n .string()\n .regex(TAG_HEX_COLOR_PATTERN, \"color must be in #RRGGBB form\")\n .optional()\n .describe(\n \"Hex colour in `#RRGGBB` form. Defaults to the UI grey when omitted. The reserved colour \" +\n `\\`${TAG_RESERVED_COLOR}\\` (used by the system \"paused\" tag) is rejected with 400.`,\n ),\n description: z.string().optional().describe(\"Free-form description, e.g. 'Prime Day run-up'.\"),\n startAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`.\"),\n endAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`.\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .optional()\n .describe(\n `Optional: Rank Radar keyword ids to tag on creation (1–${TAG_KEYWORD_IDS_MAX}). These are ` +\n \"the `id` values from `get_rank_radar_data`; the ids must all belong to this Rank Radar. \" +\n \"To attach more later, use `add_rank_radar_tag_search_terms`.\",\n ),\n};\n\nexport const createRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"create_rank_radar_tag\",\n title: \"Create a Rank Radar Tag\",\n description:\n \"Use this to create a new user tag (\\\"highlight\\\") on a Rank Radar — the way the UI marks a \" +\n \"window on the ranking chart. Writing a tag costs nothing and is reversible with \" +\n \"`delete_rank_radar_tag`, so no confirmation is required. Returns the new tag row \" +\n \"(`id`, `title`, `color`, `description`, `startAt`, `endAt`, `keywordCount`, `createdAt`); \" +\n \"the `id` is the `tagId` the other tag tools take. If the title clashes with an existing tag \" +\n \"on this Rank Radar, the API answers 409 `{ error: 'TAG_TITLE_EXISTS', existingTagId }` so \" +\n \"callers can reuse the existing tag without a follow-up `list_rank_radar_tags`. The reserved \" +\n `colour \\`${TAG_RESERVED_COLOR}\\` is rejected with 400. Pass \\`rankRadarKeywordIds\\` to tag ` +\n \"a batch of keywords in the same call; otherwise create the tag and attach keywords later \" +\n \"with `add_rank_radar_tag_search_terms`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const body: Record<string, unknown> = { title: args.title };\n if (args.color !== undefined) body.color = args.color;\n if (args.description !== undefined) body.description = args.description;\n if (args.startAt !== undefined) body.startAt = args.startAt;\n if (args.endAt !== undefined) body.endAt = args.endAt;\n if (args.rankRadarKeywordIds !== undefined) body.rankRadarKeywordIds = args.rankRadarKeywordIds;\n\n return await httpPost<RankRadarTag>(\n { config: ctx.config, toolName: \"create_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,\n body,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPatch } from \"../http/client.js\";\nimport {\n RankRadarTag,\n TAG_HEX_COLOR_PATTERN,\n TAG_RESERVED_COLOR,\n TAG_TITLE_MAX_LENGTH,\n} from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`).\"),\n title: z\n .string()\n .min(1)\n .max(TAG_TITLE_MAX_LENGTH)\n .optional()\n .describe(\n `New title (1–${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this ` +\n \"Rank Radar, case-insensitive. A clash returns 409 with \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }`.\",\n ),\n color: z\n .string()\n .regex(TAG_HEX_COLOR_PATTERN, \"color must be in #RRGGBB form\")\n .optional()\n .describe(`Hex colour in \\`#RRGGBB\\` form. The reserved colour \\`${TAG_RESERVED_COLOR}\\` is rejected with 400.`),\n description: z\n .string()\n .optional()\n .describe(\"Free-form description. Pass an empty string to clear.\"),\n startAt: z\n .string()\n .optional()\n .describe(\"ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`.\"),\n endAt: z.string().optional().describe(\"ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.\"),\n};\n\nexport const updateRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"update_rank_radar_tag\",\n title: \"Update a Rank Radar Tag\",\n description:\n \"Use this to change a tag's title, colour, description, or date window. Partial update: \" +\n \"only the fields you pass are modified, the rest are left as they are. The system \" +\n \"\\\"paused\\\" tag (reserved colour, written by the quota pauser) cannot be addressed from \" +\n \"here — the API answers 404. Writing a tag costs nothing and is reversible, so no \" +\n \"confirmation is required. Returns the updated tag row. Title clashes return 409 \" +\n \"`{ error: 'TAG_TITLE_EXISTS', existingTagId }`; the reserved colour returns 400; and \" +\n \"`endAt < startAt` returns 400 (for whichever side the body omits, the comparison uses the \" +\n \"stored value).\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const body: Record<string, unknown> = {};\n if (args.title !== undefined) body.title = args.title;\n if (args.color !== undefined) body.color = args.color;\n if (args.description !== undefined) body.description = args.description;\n if (args.startAt !== undefined) body.startAt = args.startAt;\n if (args.endAt !== undefined) body.endAt = args.endAt;\n\n return await httpPatch<RankRadarTag>(\n { config: ctx.config, toolName: \"update_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,\n body,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpDelete } from \"../http/client.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to delete (the `id` from `list_rank_radar_tags`).\"),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed. Confirm with the user first — the tag's keyword assignments are \" +\n \"removed too and the tag cannot be recovered.\",\n ),\n};\n\nexport const deleteRankRadarTagTool: ToolDefinition<typeof inputSchema> = {\n name: \"delete_rank_radar_tag\",\n title: \"Delete a Rank Radar Tag\",\n description:\n \"Use this to permanently remove a tag from a Rank Radar. ⚠️ The tag itself is gone and the \" +\n \"assignments linking it to each tagged keyword are removed with it; the keywords themselves \" +\n \"keep tracking. The deletion cannot be undone from this tool, so set `confirm: true` only \" +\n \"after the user approves. The system \\\"paused\\\" tag (reserved colour, written by the quota \" +\n \"pauser) cannot be addressed from here — the API answers 404. To remove just the assignments \" +\n \"and keep the tag, use `remove_rank_radar_tag_search_terms` instead.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Deleting this tag removes it from every keyword it was applied to. The tag cannot be \" +\n \"recovered — if the user only wants to clear its keyword list, use \" +\n \"`remove_rank_radar_tag_search_terms`.\",\n );\n if (pending) return pending;\n\n await httpDelete<null>(\n { config: ctx.config, toolName: \"delete_rank_radar_tag\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,\n );\n return { status: \"deleted\", rankRadarId: args.rankRadarId, tagId: args.tagId };\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { TAG_KEYWORD_IDS_MAX, type TagSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`).\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .describe(\n `Rank Radar keyword ids to attach to this tag (1–${TAG_KEYWORD_IDS_MAX} per call, unique). ` +\n \"These are the `id` values from `get_rank_radar_data`; all ids must belong to this Rank \" +\n \"Radar. Safe to repeat: keywords already attached are left unchanged.\",\n ),\n};\n\nexport const addRankRadarTagSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"add_rank_radar_tag_search_terms\",\n title: \"Attach Search Terms to a Rank Radar Tag\",\n description:\n \"Use this to tag a batch of keywords on a Rank Radar — the same relation \" +\n \"`create_rank_radar_tag` writes when `rankRadarKeywordIds` is passed on creation, so this is \" +\n \"how you keep extending a tag's keyword set over time. Reversible with \" +\n \"`remove_rank_radar_tag_search_terms`, so no confirmation is required. Idempotent: \" +\n \"already-attached keywords are left unchanged, so re-sending the same ids after a timeout \" +\n `is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call — split larger batches into multiple ` +\n \"calls. The system \\\"paused\\\" tag (reserved colour, written by the quota pauser) is not \" +\n \"addressable: the API answers 404.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<TagSearchTermsResult>(\n { config: ctx.config, toolName: \"add_rank_radar_tag_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { TAG_KEYWORD_IDS_MAX, type TagSearchTermsResult } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n rankRadarId: z\n .string()\n .uuid()\n .describe(\"The Rank Radar UUID the tag belongs to (from `list_rank_radars`).\"),\n tagId: z\n .string()\n .uuid()\n .describe(\"The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`).\"),\n rankRadarKeywordIds: z\n .array(z.string().uuid())\n .min(1)\n .max(TAG_KEYWORD_IDS_MAX)\n .describe(\n `Rank Radar keyword ids to detach from this tag (1–${TAG_KEYWORD_IDS_MAX} per call, unique). ` +\n \"These are the `id` values from `get_rank_radar_data`. Safe to repeat: keywords that are \" +\n \"not currently attached are left unchanged.\",\n ),\n};\n\nexport const removeRankRadarTagSearchTermsTool: ToolDefinition<typeof inputSchema> = {\n name: \"remove_rank_radar_tag_search_terms\",\n title: \"Detach Search Terms from a Rank Radar Tag\",\n description:\n \"Use this to remove keywords from a tag without deleting the tag itself. The tag keeps its \" +\n \"title, colour and date window; only the keyword assignments listed are cleared. Reversible \" +\n \"with `add_rank_radar_tag_search_terms`, so no confirmation is required. Idempotent: ids \" +\n \"that are not currently attached are left unchanged, so re-sending the same ids after a \" +\n `timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call — split larger batches into ` +\n \"multiple calls. To remove the tag itself, use `delete_rank_radar_tag`. The system \" +\n \"\\\"paused\\\" tag (reserved colour, written by the quota pauser) is not addressable: the API \" +\n \"answers 404.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n return await httpPost<TagSearchTermsResult>(\n { config: ctx.config, toolName: \"remove_rank_radar_tag_search_terms\" },\n `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms/remove`,\n { rankRadarKeywordIds: args.rankRadarKeywordIds },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpPost } from \"../http/client.js\";\nimport { LISTING_COPY_STRATEGIES, type CreateListingCopyResult } from \"../types/api.js\";\nimport { requireConfirmation } from \"./confirm.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z\n .string()\n .min(1)\n .describe(\"The niche whose keyword data the copy is written from. Get one from `list_niches`.\"),\n strategy: z\n .enum(LISTING_COPY_STRATEGIES)\n .describe(\n \"Which generation strategy to use. `cosmo` targets Amazon's COSMO relevance model, `ranking-juice` \" +\n \"optimises for DataDive's ranking-juice score, `nlp` writes for keyword coverage, and `cosmo-rufus` \" +\n \"targets COSMO plus the Rufus shopping assistant. Ask the user if they have no preference.\",\n ),\n currentListing: z\n .record(z.union([z.string(), z.array(z.string())]))\n .describe(\n \"The seller's existing listing to rewrite, as an object — e.g. \" +\n '`{ \"title\": \"...\", \"description\": \"...\", \"bullets\": [\"...\", \"...\"] }`. Pass what the user has; ' +\n \"it is the starting point the generated copy builds on.\",\n ),\n confirm: z\n .boolean()\n .optional()\n .describe(\n \"Must be true to proceed — each generation spends one AI Copywriter prompt from the quota. \" +\n \"Confirm the cost with the user first.\",\n ),\n};\n\nexport const generateListingCopyTool: ToolDefinition<typeof inputSchema> = {\n name: \"generate_listing_copy\",\n title: \"Generate Listing Copy for a Niche\",\n description:\n \"Use this to draft an optimised Amazon listing — title, bullets and description — from a niche's keyword \" +\n \"research and the seller's current listing. It writes text only: nothing is published to Amazon, and the \" +\n \"user still has to paste the result into Seller Central. ⚠️ Spends one AI Copywriter prompt from the \" +\n \"quota per call and cannot be undone — set `confirm: true` only after the user approves. Not safe to \" +\n \"retry: each call spends another prompt, so if a call errors or times out, poll \" +\n \"`get_listing_copy_generation_status` instead of re-calling. Runs asynchronously: returns a \" +\n \"`generationId` — poll `get_listing_copy_generation_status` with it until the status is `complete`.\",\n inputSchema,\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },\n handler: async (args, ctx) => {\n const pending = requireConfirmation(\n args.confirm,\n ctx,\n \"Generating listing copy consumes one AI Copywriter prompt from the organization's quota. \" +\n \"Check the remaining balance with `get_quota` if the user needs it.\",\n );\n if (pending) return pending;\n\n // The API field is still named `prompt`/`listingToInclude`; the tool surface uses\n // `strategy`/`currentListing` because those read as what they are to a model.\n return await httpPost<CreateListingCopyResult>(\n { config: ctx.config, toolName: \"generate_listing_copy\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ai-copywriter`,\n { prompt: args.strategy, listingToInclude: args.currentListing },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { ListingCopyGenerationStatus } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n nicheId: z.string().min(1).describe(\"The niche the generation was started for.\"),\n generationId: z.string().min(1).describe(\"The `generationId` returned by `generate_listing_copy`.\"),\n};\n\nexport const getListingCopyGenerationStatusTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_listing_copy_generation_status\",\n title: \"Get Listing Copy Generation Status\",\n description:\n \"Use this to poll a listing-copy draft started with `generate_listing_copy` until it finishes. Returns \" +\n \"one of three shapes keyed by `status`: `generating` (still running — poll again in a few seconds), \" +\n \"`complete` (with `result`, carrying the generated `title`, `bullets`, `description`, `itemHighlights` \" +\n \"and its `rankingJuice` score), or `failed` (with an `error` message). Polling is free — it does not \" +\n \"spend another AI Copywriter prompt, so always poll rather than re-calling `generate_listing_copy`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<ListingCopyGenerationStatus>(\n { config: ctx.config, toolName: \"get_listing_copy_generation_status\" },\n `/v1/niches/${encodeURIComponent(args.nicheId)}/ai-copywriter/${encodeURIComponent(args.generationId)}`,\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport type { SellerProfileList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const listSellerProfilesTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_seller_profiles\",\n title: \"List Connected Amazon Seller Profiles\",\n description:\n \"Use this when the user asks which Amazon seller accounts are connected, or as the discovery \" +\n \"step to find the `sellerId` and `marketplace` required by `get_asin_inventory_distribution`, \" +\n \"`get_seller_catalog`, `get_seller_listing_changes`, and the alert tools. \" +\n \"Returns a paginated list of the organization's connected seller profiles — each item has \" +\n \"sellerId, sellerName, marketplace (e.g. \\\"com\\\", \\\"co.uk\\\"), hasAdApi (whether Advertising API \" +\n \"credentials are connected), and createdAt — plus pagination metadata (currentPage, pageSize, \" +\n \"total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<SellerProfileList>(\n { config: ctx.config, toolName: \"list_seller_profiles\" },\n \"/v1/seller_profiles\",\n {\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { CATALOG_STATUSES, type SellerCatalogList } from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on product title or brand.\"),\n brand: z.string().min(1).optional().describe(\"Case-insensitive partial match on brand name.\"),\n status: z\n .enum(CATALOG_STATUSES)\n .optional()\n .describe('Listing status filter. \"Active\" (default) or \"all\".'),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const getSellerCatalogTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_seller_catalog\",\n title: \"List Catalog ASINs for a Seller\",\n description:\n \"Use this when the user wants to browse or search a seller's own Amazon catalog — the products \" +\n \"they sell on a given marketplace. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` \" +\n \"to discover them). Filter by `search` (title/brand), `brand`, and `status` (Active by default, or \" +\n \"all). Returns a paginated list where each item has asin, title, parentAsin, brand, status, imageUrl, \" +\n \"and hasVariations, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<SellerCatalogList>(\n { config: ctx.config, toolName: \"get_seller_catalog\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"catalog\"),\n {\n search: args.search,\n brand: args.brand,\n status: args.status,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","/**\n * Shared input fields + path builder for the seller+marketplace-scoped tools\n * (/v1/seller_profiles/:sellerId/marketplaces/:marketplace/*). Defined once here\n * and spread into each tool's inputSchema, mirroring alert-query.ts.\n */\n\nimport { z } from \"zod\";\nimport { SUPPORTED_MARKETPLACES } from \"../types/api.js\";\n\nexport const sellerScopedInputSchema = {\n sellerId: z\n .string()\n .min(1)\n .describe(\n \"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools.\",\n ),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe('Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n};\n\n/** Builds a `/v1/seller_profiles/:sellerId/marketplaces/:marketplace/<suffix>` path with both params URL-encoded. */\nexport function sellerMarketplacePath(sellerId: string, marketplace: string, suffix: string): string {\n return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(\n marketplace,\n )}/${suffix}`;\n}\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport {\n ISO_8601,\n LISTING_CHANGE_SORT_BY,\n LISTING_CHANGE_TYPES,\n SORT_ORDER,\n type ListingChangeList,\n} from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n types: z\n .array(z.enum(LISTING_CHANGE_TYPES))\n .min(1)\n .optional()\n .describe('Filter to specific change types. Omit for all. Any of \"Price\", \"Content\", \"Image\".'),\n asin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Filter to an exact ASIN or parent ASIN.\"),\n brand: z.string().min(1).optional().describe(\"Case-insensitive partial match on brand name.\"),\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on product title or brand.\"),\n startDate: z\n .string()\n .regex(ISO_8601, \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only changes detected on or after this time.\"),\n endDate: z\n .string()\n .regex(ISO_8601, \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only changes detected on or before this time.\"),\n sortBy: z\n .enum(LISTING_CHANGE_SORT_BY)\n .optional()\n .describe('Sort field. \"date\" (default) or \"type\".'),\n sortOrder: z.enum(SORT_ORDER).optional().describe('Sort direction. \"DESC\" (default) or \"ASC\".'),\n includeCorrelations: z\n .boolean()\n .optional()\n .describe(\n \"When true, include the ranking/conversion `correlation` object per change (CVR and search-term \" +\n \"movement before vs after). Defaults to false.\",\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n\nexport const getSellerListingChangesTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_seller_listing_changes\",\n title: \"List Listing Changes for a Seller\",\n description:\n \"Use this when the user asks what changed on their Amazon listings — price, content (title/bullets/\" +\n \"description), or image edits — for a seller account. Requires a `sellerId` + `marketplace` (use \" +\n \"`list_seller_profiles` to discover them). Filter by `types`, `asin`/parent ASIN, `brand`, `search`, \" +\n \"and a `startDate`/`endDate` range; sort with `sortBy`/`sortOrder`. Set `includeCorrelations: true` to \" +\n \"attach the ranking/conversion impact per change. Returns a paginated list where each item has asin, \" +\n \"title, imageUrl, date, type, contentType, description, previousValue, newValue, and (optionally) \" +\n \"correlation, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<ListingChangeList>(\n { config: ctx.config, toolName: \"get_seller_listing_changes\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"listing-changes\"),\n {\n // The API accepts `types` as a comma-separated list.\n types: args.types?.join(\",\"),\n asin: args.asin,\n brand: args.brand,\n search: args.search,\n startDate: args.startDate,\n endDate: args.endDate,\n sortBy: args.sortBy,\n sortOrder: args.sortOrder,\n includeCorrelations: args.includeCorrelations,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { ApiError } from \"../http/errors.js\";\nimport {\n ISO_8601,\n PPC_CAMPAIGN_BASE_SORT_BY,\n PPC_CAMPAIGN_PLACEMENT_SORT_BY,\n PPC_CAMPAIGN_STATES,\n SORT_ORDER,\n type PpcCampaignList,\n} from \"../types/api.js\";\nimport { sellerMarketplacePath, sellerScopedInputSchema } from \"./seller-scoped.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\n/** Mirrors the API's `MAX_PPC_CAMPAIGNS_WINDOW_DAYS`; a longer window is refused with a 400. */\nexport const PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;\n\n/** Mirrors the API's `@Max(50)` on `pageSize`. */\nexport const PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;\n\nconst inputSchema = {\n ...sellerScopedInputSchema,\n startDate: z\n .string()\n .regex(\n ISO_8601,\n \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\",\n )\n .optional()\n .describe(\"Start of the reporting window, ISO-8601. Defaults to 30 days before endDate.\"),\n endDate: z\n .string()\n .regex(\n ISO_8601,\n \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\",\n )\n .optional()\n .describe(\n `End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`,\n ),\n asin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Only campaigns advertising this ASIN. Cannot be combined with parentAsin.\"),\n parentAsin: z\n .string()\n .min(1)\n .optional()\n .describe(\"Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin.\"),\n search: z\n .string()\n .min(3)\n .max(200)\n .optional()\n .describe(\"Partial match on campaign name or advertised ASIN. At least 3 characters.\"),\n includePlacements: z\n .boolean()\n .optional()\n .describe(\n \"Include the per-placement breakdown (topOfSearch, restOfSearch, productPage, offAmazon, each with its \" +\n \"bid adjustment). Defaults to true; set false for a smaller response when placements are not needed.\",\n ),\n sortBy: z\n .enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY])\n .optional()\n .describe(\n `Sort field. Defaults to \"name\". Any of ${PPC_CAMPAIGN_BASE_SORT_BY.join(\", \")}. The placement fields ` +\n '(e.g. \"tosSpend\", \"tosAcos\", \"ppBidAdjustment\") are only accepted when includePlacements is not false.',\n ),\n sortOrder: z.enum(SORT_ORDER).optional().describe('Sort direction. \"ASC\" (default) or \"DESC\".'),\n state: z\n .enum(PPC_CAMPAIGN_STATES)\n .optional()\n .describe(\"Only ENABLED or only PAUSED campaigns. Omit for both.\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(PPC_CAMPAIGNS_MAX_PAGE_SIZE)\n .optional()\n .describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`),\n};\n\nexport const listPpcCampaignsTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_ppc_campaigns\",\n title: \"List PPC Campaigns for a Seller\",\n description:\n \"Use this when the user asks about their Sponsored Products campaigns — which campaigns spend the most, \" +\n \"which have a high ACOS, how placements (top of search, rest of search, product pages) perform, or which \" +\n \"campaigns advertise a given product. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` \" +\n \"to discover them). Narrow to one product with `asin`, or to a variation family with `parentAsin` (not \" +\n \"both). The reporting window defaults to the last 30 days and cannot exceed \" +\n `${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days. Returns a paginated list where each campaign has campaignId, name, ` +\n \"type, state, targetingType, bidStrategy, budget { type, amount, currencyCode }, window totals \" +\n \"(impressions, clicks, ctr, cpc, cvr, spend, unitsSold, orders, sales, tosImpressionShare, acos, roas, \" +\n \"tacos), asinCount and, unless includePlacements is false, `placements`; plus pagination metadata \" +\n \"(currentPage, pageSize, total, lastPage, hasNext, hasPrev). To find the biggest spenders, sort with \" +\n '`sortBy: \"spend\", sortOrder: \"DESC\"` rather than paging through every campaign. Each call counts toward ' +\n \"API usage and is rate limited (about 60 requests/minute). For per-keyword ad data of a Rank Radar, use \" +\n \"`get_rank_radar_ppc_data`.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n if (args.asin && args.parentAsin) {\n // The API refuses the pair too; checking here saves a counted, rate-limited call.\n throw new ApiError(\n \"bad_request\",\n 400,\n \"Pass either `asin` (one product) or `parentAsin` (its whole variation family), not both.\",\n );\n }\n\n return await httpGet<PpcCampaignList>(\n { config: ctx.config, toolName: \"list_ppc_campaigns\" },\n sellerMarketplacePath(args.sellerId, args.marketplace, \"ppc/campaigns\"),\n {\n startDate: args.startDate,\n endDate: args.endDate,\n asin: args.asin,\n parentAsin: args.parentAsin,\n search: args.search,\n includePlacements: args.includePlacements,\n sortBy: args.sortBy,\n sortOrder: args.sortOrder,\n state: args.state,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { SUPPORTED_MARKETPLACES, type InventoryByFcResponse } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n sellerId: z\n .string()\n .min(1)\n .describe(\n \"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.\",\n ),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .describe('Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n asin: z\n .string()\n .regex(/^[A-Z0-9]{10}$/, \"ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).\")\n .describe(\n \"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234).\",\n ),\n};\n\nexport const getAsinInventoryDistributionTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_asin_inventory_distribution\",\n title: \"Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN\",\n description:\n \"Use this when the user asks about an ASIN's inventory: stock levels, days of stock or when it will run out, \" +\n \"low-inventory or aged-inventory risk, low-inventory-level (LILF) or long-term storage (LTSF) fees, or where \" +\n \"their units sit across Amazon's fulfillment network. Requires the Amazon sellerId (from `list_seller_profiles` \" +\n \"or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. \" +\n \"Returns the metrics the DataDive Inventory page shows — fulfillableStock, totalStock, reservedStock, \" +\n \"inboundStock, researchingStock, unfulfillableStock, daysOfStock (with daysOfStockSource AMAZON or ESTIMATE), \" +\n \"daysOfStock30, daysOfStock90, inLowInventoryState, estWeeklyLilf, estLtsfNextAssessment, ltsfLastMonthActual, \" +\n \"agedUnits, oldestUnitAgeDays, with fees in `currency` — plus totalSellableUnits and a per-FC `distribution` \" +\n \"array (fc, state, availableStock, availableStockPercentage). A metric is null, never 0, when Amazon has no data \" +\n \"(e.g. merchant-fulfilled); metrics the user's plan does not include are null and listed in `lockedFields` \" +\n \"(say they need a plan upgrade, do not report them as zero). `lastUpdatedAt` dates the stock and metrics; \" +\n \"`distributionLastUpdatedAt` dates the distribution, which is usually up to 2 days behind. Either may be null \" +\n \"when no successful refresh happened in the last 30 days.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<InventoryByFcResponse>(\n { config: ctx.config, toolName: \"get_asin_inventory_distribution\" },\n `/v1/sellers/${encodeURIComponent(args.sellerId)}/marketplaces/${encodeURIComponent(\n args.marketplace,\n )}/asins/${encodeURIComponent(args.asin)}/inventory`,\n );\n },\n};\n","/**\n * Shared input schema for the /v1/alerts/* list tools. Both alert endpoints\n * accept the same backend query DTO (ExternalAlertsQueryDto), so the input\n * contract is defined once here and imported by each tool.\n */\n\nimport { z } from \"zod\";\nimport { ALERT_STATUSES, ISO_8601, SUPPORTED_MARKETPLACES } from \"../types/api.js\";\n\nexport const alertsQueryInputSchema = {\n sellerId: z\n .string()\n .min(1)\n .max(50)\n .optional()\n .describe('Filter to a single connected Amazon seller account ID, e.g. \"A1B2C3D4E5\".'),\n marketplace: z\n .enum(SUPPORTED_MARKETPLACES)\n .optional()\n .describe('Filter to a single Amazon marketplace code, e.g. \"com\" for amazon.com or \"co.uk\" for amazon.co.uk.'),\n status: z\n .enum(ALERT_STATUSES)\n .optional()\n .describe(\n \"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, \" +\n \"`all` returns both. Dismissed alerts are never returned.\",\n ),\n updatedSince: z\n .string()\n .regex(ISO_8601, \"updatedSince must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\n \"ISO-8601 timestamp; return only alerts surfaced at or after this time (filters on `lastAlertedAt`). \" +\n \"Use it to incrementally sync since your last poll. Defaults to the last 30 days when omitted.\",\n ),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Items per page (max 50). Defaults to 20.\"),\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { IndexingIssueAlertList } from \"../types/api.js\";\nimport { alertsQueryInputSchema } from \"./alert-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nexport const listIndexingIssueAlertsTool: ToolDefinition<typeof alertsQueryInputSchema> = {\n name: \"list_indexing_issue_alerts\",\n title: \"List Indexing-Issue Alerts\",\n description:\n \"Use this to find products that may have lost search visibility. Retrieves a paginated list of \" +\n \"indexing-issue alerts across the user's connected Amazon seller accounts — an alert fires when one of \" +\n \"their ASINs is no longer indexed for its tracked keywords. By default returns active (unresolved) alerts \" +\n \"from the last 30 days; filter by `sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental \" +\n \"polling). Each item includes id, asin, title, imageUrl, isParent, sellerId, marketplace, lastAlertedAt, \" +\n \"and resolvedAt. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema: alertsQueryInputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<IndexingIssueAlertList>(\n { config: ctx.config, toolName: \"list_indexing_issue_alerts\" },\n \"/v1/alerts/indexing-issues\",\n {\n sellerId: args.sellerId,\n marketplace: args.marketplace,\n status: args.status,\n updatedSince: args.updatedSince,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { BlindSpendAlertList } from \"../types/api.js\";\nimport { alertsQueryInputSchema } from \"./alert-query.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nexport const listBlindSpendAlertsTool: ToolDefinition<typeof alertsQueryInputSchema> = {\n name: \"list_blind_spend_alerts\",\n title: \"List Blind-Spend (Wasted-Spend) Alerts\",\n description:\n \"Use this to find wasted PPC ad spend. Retrieves a paginated list of blind-spend alerts across the \" +\n \"user's connected Amazon seller accounts — an alert flags ad spend on customer search terms that produced \" +\n \"little or no sales. By default returns active (unresolved) alerts from the last 30 days; filter by \" +\n \"`sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental polling). Each item includes id, \" +\n \"asin, title, imageUrl, sellerId, marketplace, lastAlertedAt, resolvedAt, wastedSpend (total ad spend \" +\n \"across unresolved terms), totalKeywordCount, unresolvedKeywordCount, and searchTerms — the unresolved \" +\n \"wasted-spend search terms, each with term, spend, sales, clicks, cvr (0-1), and impressions. Supports \" +\n \"pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema: alertsQueryInputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<BlindSpendAlertList>(\n { config: ctx.config, toolName: \"list_blind_spend_alerts\" },\n \"/v1/alerts/blind-spend\",\n {\n sellerId: args.sellerId,\n marketplace: args.marketplace,\n status: args.status,\n updatedSince: args.updatedSince,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","import { httpGet } from \"../http/client.js\";\nimport type { Quota } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {};\n\nexport const getQuotaTool: ToolDefinition<typeof inputSchema> = {\n name: \"get_quota\",\n title: \"Get Quota Usage\",\n description:\n \"Use this when the user asks how much quota or how many tokens they have left, or before creating a \" +\n \"Niche Dive or Rank Radar to check remaining headroom. \" +\n \"Returns current quota usage and capacity for each billable feature — DIVED_ASINS (Dive tokens), \" +\n \"RANK_RADAR_KEYWORDS (tracked keywords), PRODUCT_BRIEF_ASINS, and AI_COPYWRITER_PROMPTS — each with \" +\n \"`used` and `capacity` (null when unlimited / not applicable), plus `nextRefreshDate` (ISO-8601 \" +\n \"timestamp of the next quota reset, or null). Takes no arguments.\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (_args, ctx) => {\n return await httpGet<Quota>({ config: ctx.config, toolName: \"get_quota\" }, \"/v1/quota\");\n },\n};\n","import { z } from \"zod\";\nimport { httpGet } from \"../http/client.js\";\nimport { BILLABLE_FEATURE_TYPES, ISO_8601, type UsageLogList } from \"../types/api.js\";\nimport type { ToolDefinition } from \"./types.js\";\n\nconst inputSchema = {\n type: z\n .enum(BILLABLE_FEATURE_TYPES)\n .optional()\n .describe(\"Filter to a single billable feature type. Omit to return all types.\"),\n search: z\n .string()\n .min(1)\n .optional()\n .describe(\"Case-insensitive partial match on the user's name or email.\"),\n startDate: z\n .string()\n .regex(ISO_8601, \"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only usage logs recorded on or after this time.\"),\n endDate: z\n .string()\n .regex(ISO_8601, \"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)\")\n .optional()\n .describe(\"ISO-8601 date/timestamp; return only usage logs recorded on or before this time.\"),\n currentPage: z.number().int().min(1).optional().describe(\"Page number, 1-indexed. Defaults to 1.\"),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe(\"Items per page (max 200). Defaults to 50.\"),\n};\n\nexport const listUsageTool: ToolDefinition<typeof inputSchema> = {\n name: \"list_usage\",\n title: \"List Billable Usage Logs\",\n description:\n \"Use this when the user asks who consumed tokens, how their quota was spent, or wants an audit of \" +\n \"billable activity over a date range. \" +\n \"Retrieves a paginated list of billable feature usage logs for the organization — each entry is a \" +\n \"token-consumption event (a dive, rank-radar creation, AI copywriter prompt, etc.). Filter by `type` \" +\n \"(billable feature), `search` (user name/email), and `startDate`/`endDate`. Each item includes name, \" +\n \"email, qty (tokens consumed), type, action (specific operation), nicheId/nicheName, rankRadarId, and \" +\n \"date, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).\",\n inputSchema,\n annotations: { readOnlyHint: true },\n handler: async (args, ctx) => {\n return await httpGet<UsageLogList>(\n { config: ctx.config, toolName: \"list_usage\" },\n \"/v1/usage\",\n {\n type: args.type,\n search: args.search,\n startDate: args.startDate,\n endDate: args.endDate,\n currentPage: args.currentPage,\n pageSize: args.pageSize,\n },\n );\n },\n};\n","/**\n * Tool registry. The order here is the order tools appear in `tools/list`\n * over the MCP transport, which biases LLM discovery — keep \"discovery\" tools\n * (the ones that return IDs needed by other tools) at the top.\n */\n\nimport { listNichesTool } from \"./list-niches.js\";\nimport { listFastKeywordsTool } from \"./list-fast-keywords.js\";\nimport { getFastKeywordsTool } from \"./get-fast-keywords.js\";\nimport { getFastKeywordsKeywordsTool } from \"./get-fast-keywords-keywords.js\";\nimport { runFastKeywordsTool } from \"./run-fast-keywords.js\";\nimport { refreshFastKeywordsTool } from \"./refresh-fast-keywords.js\";\nimport { listSpacesTool } from \"./list-spaces.js\";\nimport { listLabelsTool } from \"./list-labels.js\";\nimport { addNicheSpacesTool } from \"./add-niche-spaces.js\";\nimport { removeNicheSpacesTool } from \"./remove-niche-spaces.js\";\nimport { addNicheLabelsTool } from \"./add-niche-labels.js\";\nimport { removeNicheLabelsTool } from \"./remove-niche-labels.js\";\nimport { getNicheKeywordsTool } from \"./get-niche-keywords.js\";\nimport { listKeywordExclusionsTool } from \"./list-keyword-exclusions.js\";\nimport { getNicheRootsTool } from \"./get-niche-roots.js\";\nimport { getNicheCompetitorsTool } from \"./get-niche-competitors.js\";\nimport { getRankingJuiceTool } from \"./get-ranking-juice.js\";\nimport { listRankRadarsTool } from \"./list-rank-radars.js\";\nimport { getRankRadarDataTool } from \"./get-rank-radar-data.js\";\nimport { getRankRadarSqpDataTool } from \"./get-rank-radar-sqp-data.js\";\nimport { getRankRadarPpcDataTool } from \"./get-rank-radar-ppc-data.js\";\nimport { createRankRadarTool } from \"./create-rank-radar.js\";\nimport { createNicheDiveTool } from \"./create-niche-dive.js\";\nimport { createNicheDiveFromCompetitorsListTool } from \"./create-niche-dive-from-competitors-list.js\";\nimport { rediveNicheTool } from \"./redive-niche.js\";\nimport { getDiveStatusTool } from \"./get-dive-status.js\";\nimport { addNicheKeywordsTool } from \"./add-niche-keywords.js\";\nimport { removeNicheKeywordsTool } from \"./remove-niche-keywords.js\";\nimport { addKeywordExclusionsTool } from \"./add-keyword-exclusions.js\";\nimport { removeKeywordExclusionsTool } from \"./remove-keyword-exclusions.js\";\nimport { deleteNicheTool } from \"./delete-niche.js\";\nimport { pauseRankRadarTool } from \"./pause-rank-radar.js\";\nimport { resumeRankRadarTool } from \"./resume-rank-radar.js\";\nimport { deleteRankRadarTool } from \"./delete-rank-radar.js\";\nimport { addRankRadarSearchTermsTool } from \"./add-rank-radar-search-terms.js\";\nimport { pauseRankRadarSearchTermsTool } from \"./pause-rank-radar-search-terms.js\";\nimport { resumeRankRadarSearchTermsTool } from \"./resume-rank-radar-search-terms.js\";\nimport { listRankRadarTagsTool } from \"./list-rank-radar-tags.js\";\nimport { createRankRadarTagTool } from \"./create-rank-radar-tag.js\";\nimport { updateRankRadarTagTool } from \"./update-rank-radar-tag.js\";\nimport { deleteRankRadarTagTool } from \"./delete-rank-radar-tag.js\";\nimport { addRankRadarTagSearchTermsTool } from \"./add-rank-radar-tag-search-terms.js\";\nimport { removeRankRadarTagSearchTermsTool } from \"./remove-rank-radar-tag-search-terms.js\";\nimport { generateListingCopyTool } from \"./generate-listing-copy.js\";\nimport { getListingCopyGenerationStatusTool } from \"./get-listing-copy-generation-status.js\";\nimport { listSellerProfilesTool } from \"./list-seller-profiles.js\";\nimport { getSellerCatalogTool } from \"./get-seller-catalog.js\";\nimport { getSellerListingChangesTool } from \"./get-seller-listing-changes.js\";\nimport { listPpcCampaignsTool } from \"./list-ppc-campaigns.js\";\nimport { getAsinInventoryDistributionTool } from \"./get-asin-inventory-distribution.js\";\nimport { listIndexingIssueAlertsTool } from \"./list-indexing-issue-alerts.js\";\nimport { listBlindSpendAlertsTool } from \"./list-blind-spend-alerts.js\";\nimport { getQuotaTool } from \"./get-quota.js\";\nimport { listUsageTool } from \"./list-usage.js\";\nimport type { AnyTool } from \"./types.js\";\n\n// Note on the cast: each ToolDefinition<S> has a handler typed to its specific\n// schema; the registry needs a uniform shape. The MCP SDK validates incoming\n// args against `inputSchema` before calling the handler, so the cast is safe.\nexport const allTools: ReadonlyArray<AnyTool> = [\n listNichesTool,\n listFastKeywordsTool,\n getFastKeywordsTool,\n getFastKeywordsKeywordsTool,\n listSpacesTool,\n listLabelsTool,\n getNicheKeywordsTool,\n listKeywordExclusionsTool,\n getNicheRootsTool,\n getNicheCompetitorsTool,\n getRankingJuiceTool,\n listRankRadarsTool,\n getRankRadarDataTool,\n getRankRadarSqpDataTool,\n getRankRadarPpcDataTool,\n listRankRadarTagsTool,\n createRankRadarTool,\n runFastKeywordsTool,\n refreshFastKeywordsTool,\n createNicheDiveTool,\n createNicheDiveFromCompetitorsListTool,\n rediveNicheTool,\n getDiveStatusTool,\n addNicheKeywordsTool,\n removeNicheKeywordsTool,\n addKeywordExclusionsTool,\n removeKeywordExclusionsTool,\n addNicheSpacesTool,\n removeNicheSpacesTool,\n addNicheLabelsTool,\n removeNicheLabelsTool,\n addRankRadarSearchTermsTool,\n pauseRankRadarSearchTermsTool,\n resumeRankRadarSearchTermsTool,\n createRankRadarTagTool,\n updateRankRadarTagTool,\n addRankRadarTagSearchTermsTool,\n removeRankRadarTagSearchTermsTool,\n deleteRankRadarTagTool,\n pauseRankRadarTool,\n resumeRankRadarTool,\n deleteRankRadarTool,\n deleteNicheTool,\n generateListingCopyTool,\n getListingCopyGenerationStatusTool,\n listSellerProfilesTool,\n getSellerCatalogTool,\n getSellerListingChangesTool,\n listPpcCampaignsTool,\n getAsinInventoryDistributionTool,\n listIndexingIssueAlertsTool,\n listBlindSpendAlertsTool,\n getQuotaTool,\n listUsageTool,\n] as unknown as ReadonlyArray<AnyTool>;\n\nexport type { ToolDefinition, AnyTool } from \"./types.js\";\n","/**\n * \"What's new\" for connected assistants and users (RS-11631).\n *\n * MCP has no message for \"a new version with new features is out\": `tools/list_changed` only\n * reaches a live session, and the hosted server is stateless, so no session survives a deploy.\n * Two protocol features carry the news instead, both built from the list below:\n *\n * - `SERVER_INSTRUCTIONS`, sent to the client in the `initialize` result on every connection,\n * so the assistant knows the latest additions without the user asking.\n * - The `whats_new` prompt, which the user picks by hand (e.g. `/mcp__datadive__whats_new` in\n * Claude Code), for the full recent list.\n *\n * Both the stdio server (`buildServer`) and the hosted server register them from these exports,\n * so there is one copy. Add an entry here with every release that changes what a user can do;\n * keep the wording for users, not developers. Newest first.\n */\n\nexport interface WhatsNewEntry {\n /** Package version that shipped the change. */\n version: string;\n /** yyyy-mm-dd the change reached the hosted server. */\n released: string;\n items: string[];\n}\n\nexport const WHATS_NEW: ReadonlyArray<WhatsNewEntry> = [\n {\n version: \"0.20.1\",\n released: \"2026-10-23\",\n items: [\n \"Run Fast Keywords from just an ASIN and read the result (`run_fast_keywords`, `get_fast_keywords`, \" +\n \"`get_fast_keywords_keywords`, `list_fast_keywords`, `refresh_fast_keywords`). A run costs 1 Dive token \" +\n \"and asks you to confirm first. A finished run can be refreshed once every 24 hours, and \" +\n \"`create_niche_dive` can now link a full niche dive to the Fast Keywords run it follows.\",\n ]\n },\n {\n version: \"0.20.0\",\n released: \"2026-10-23\",\n items: [\n \"See your Spaces and labels (`list_spaces`, `list_labels`), put a niche into them or take it out \" +\n \"(`add_niche_spaces`, `remove_niche_spaces`, `add_niche_labels`, `remove_niche_labels`), and filter \" +\n \"`list_niches` and `list_rank_radars` by Space and label. Free. Changing a niche's Spaces and \" +\n \"labels is for the organization owner or a team admin; a Space you only have view rights on is refused.\",\n ],\n },\n {\n version: \"0.17.1\",\n released: \"2026-10-13\",\n items: [\n \"Rank Radar tags end-to-end: list, create, update, delete tags, and attach or detach up to 1000 \" +\n \"keywords per call (`list_rank_radar_tags`, `create_rank_radar_tag`, `update_rank_radar_tag`, \" +\n \"`delete_rank_radar_tag`, `add_rank_radar_tag_search_terms`, `remove_rank_radar_tag_search_terms`). \" +\n \"The highlights on each keyword in `get_rank_radar_data` are the same objects, so you can go \" +\n \"from a ranking view to editing its tags without a round trip.\",\n ]},\n {\n version: \"0.17.0\",\n released: \"2026-10-12\",\n items: [\n \"Add your own keywords to a niche's Master Keyword List, or take them back out (`add_niche_keywords`, \" +\n \"`remove_niche_keywords`). Free while the niche is fresh; on an older niche you are asked to confirm \" +\n \"the Dive tokens first. `get_niche_keywords` now shows where each keyword came from.\",\n \"See and change which keywords a niche hides (`list_keyword_exclusions`, `add_keyword_exclusions`, \" +\n \"`remove_keyword_exclusions`): exclude one exact keyword, or every keyword containing a phrase. Free.\",\n ],\n },\n {\n version: \"0.16.1\",\n released: \"2026-09-29\",\n items: [\n \"`get_rank_radar_data` returns every tracked keyword of a Rank Radar in one answer again, also in AI \" +\n \"apps that still show its older inputs. Pass `currentPage` / `pageSize` to read one page at a time.\",\n ],\n },\n {\n version: \"0.16.0\",\n released: \"2026-09-28\",\n items: [\n \"Niche research from your own competitor list (`create_niche_dive_from_competitors_list`): give 2 to 200 \" +\n \"ASINs and the niche is built from exactly those products, with no automatic competitor search.\",\n ],\n },\n {\n version: \"0.15.0\",\n released: \"2026-09-28\",\n items: [\n \"Search Query Performance for Rank Radar keywords (`get_rank_radar_sqp_data`): how shoppers search, \" +\n \"click, add to cart and buy, for the whole market and for your product.\",\n \"PPC data for Rank Radar keywords (`get_rank_radar_ppc_data`): sponsored rank, spend, sales, ACOS and \" +\n \"CPC per keyword, optionally broken down by campaign.\",\n \"Your Sponsored Products campaigns (`list_ppc_campaigns`): totals and placement performance per \" +\n \"campaign, sortable by any metric, e.g. the top spenders first.\",\n ],\n },\n {\n version: \"0.14.0\",\n released: \"2026-09-25\",\n items: [\n \"Rank Radar keywords are now read page by page (`get_rank_radar_data`, up to 100 keywords per page), \" +\n \"so large Rank Radars load reliably.\",\n ],\n },\n {\n version: \"0.13.0\",\n released: \"2026-09-09\",\n items: [\n \"Before creating a Rank Radar, you are warned when the same product family is already tracked by \" +\n \"another of your Rank Radars.\",\n ],\n },\n {\n version: \"0.12.0\",\n released: \"2026-09-07\",\n items: [\"Search and sort your niches by name, hero keyword or ASIN (`list_niches`).\"],\n },\n];\n\n/** How many releases the connection-time instructions mention; the prompt lists them all. */\nconst INSTRUCTIONS_RELEASES = 2;\n\nfunction formatEntries(entries: ReadonlyArray<WhatsNewEntry>): string {\n return entries\n .map((e) => `${e.released} (v${e.version}):\\n${e.items.map((i) => `- ${i}`).join(\"\\n\")}`)\n .join(\"\\n\\n\");\n}\n\nexport const SERVER_INSTRUCTIONS =\n \"DataDive tools for Amazon sellers: niche research (niches, keywords, roots, competitors, Ranking Juice), \" +\n \"Rank Radar keyword rank tracking with SQP and PPC data, the seller's catalog, listing changes, inventory, \" +\n \"PPC campaigns and alerts. Start with a discovery tool: `list_niches`, `list_rank_radars` or \" +\n \"`list_seller_profiles`. Tools that spend tokens ask for confirmation first.\\n\\n\" +\n `Recently added:\\n${formatEntries(WHATS_NEW.slice(0, INSTRUCTIONS_RELEASES))}\\n\\n` +\n \"When one of these additions fits what the user is doing, mention it once. The user can see the full \" +\n \"list with the `whats_new` prompt.\";\n\nexport const WHATS_NEW_PROMPT = {\n name: \"whats_new\",\n title: \"What's new in DataDive\",\n description: \"Recent additions to the DataDive tools, newest first.\",\n};\n\n/** The prompt's single user message: the list plus a request to explain it briefly. */\nexport function whatsNewPromptText(): string {\n return (\n \"Here is what is new in the DataDive tools, newest first:\\n\\n\" +\n `${formatEntries(WHATS_NEW)}\\n\\n` +\n \"Summarize this for me in a few short lines, and for each addition give one example question I could ask you.\"\n );\n}\n"],"mappings":"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","names":["z","KEY_HELP_URL","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","inputSchema","inputSchema","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","inputSchema","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","z","inputSchema","z","z","inputSchema","z","z","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","inputSchema","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","inputSchema","z","z","z","inputSchema","z","inputSchema","z","entries"]}
|