@nooticr/mcp 1.26.79 → 1.26.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/shared/amazon.js +3 -3
- package/dist/shared/brand-watch.js +9 -10
- package/dist/shared/connections.js +1 -1
- package/dist/shared/creative.js +10 -10
- package/dist/shared/crm.js +125 -0
- package/dist/shared/employees.js +8 -8
- package/dist/shared/getting-started.js +7 -7
- package/dist/shared/jobs.js +26 -28
- package/dist/shared/loading-plans.js +5 -0
- package/dist/shared/marketplace.js +5 -5
- package/dist/shared/output-schemas.js +5 -0
- package/dist/shared/own-account.js +20 -19
- package/dist/shared/replies.js +3 -3
- package/dist/shared/sheets.js +9 -7
- package/dist/shared/store-read.js +2 -2
- package/dist/shared/tools-def.js +60 -36
- package/dist/shared/tools.js +84 -83
- package/dist/shared/watchlist.js +2 -2
- package/package.json +1 -1
package/dist/shared/tools-def.js
CHANGED
|
@@ -9,43 +9,43 @@ export const TOOL_DEFINITIONS = [
|
|
|
9
9
|
{
|
|
10
10
|
name: "get_social_media",
|
|
11
11
|
title: "Get Social Media",
|
|
12
|
-
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili or LinkedIn URL: contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. Returns an inline thumbnail image.
|
|
12
|
+
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili or LinkedIn URL: contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. Returns an inline thumbnail image. Use when you need the post's facts and media and nothing more; if you want it interpreted, use analyze_post_fast instead. Consumes 1 nooticr credit.",
|
|
13
13
|
inputSchema: z.object({ url: z.string().describe("Full public post URL.") }).strict(),
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
name: "discover_social_posts",
|
|
17
17
|
title: "Discover Social Posts",
|
|
18
|
-
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Reddit, Douyin, Xiaohongshu, X, Weibo or Bilibili. Reddit and Weibo are mostly text rather than video, so a post from either may have no videoUrl and no duration. Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. Say \"next\" to paginate (offset), or \"analyze the 2nd one\" / \"analyze all\" for batch analysis.
|
|
18
|
+
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Reddit, Douyin, Xiaohongshu, X, Weibo or Bilibili. Reddit and Weibo are mostly text rather than video, so a post from either may have no videoUrl and no duration. Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. Say \"next\" to paginate (offset), or \"analyze the 2nd one\" / \"analyze all\" for batch analysis. Use to find individual posts to look at; use niche_report when you want the pattern across them rather than the posts themselves. Consumes 2 nooticr credits.",
|
|
19
19
|
inputSchema: z.object({ niche: z.string().describe("Niche/topic, e.g. 'fitness'."), keywords: z.string().optional().describe("Optional extra keywords."), limit: z.number().int().optional().describe("Max results (default 6)."), offset: z.number().int().optional().describe("Skip first N results — for 'next' pagination."), platform: z.enum(["youtube", "tiktok", "instagram", "douyin", "xiaohongshu", "twitter", "bilibili", "reddit", "weibo", "any"]).optional().describe("Platform to search (default youtube).") }).strict(),
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: "get_user_posts",
|
|
23
23
|
title: "Get User Posts",
|
|
24
|
-
description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Reddit, Douyin, Xiaohongshu, X, Weibo, Bilibili or LinkedIn. Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. Use this when Claude needs to pull more posts from the same account to spot a pattern, or to scan a whole profile.
|
|
24
|
+
description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Reddit, Douyin, Xiaohongshu, X, Weibo, Bilibili or LinkedIn. Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. Use this when Claude needs to pull more posts from the same account to spot a pattern, or to scan a whole profile. Use to scan one creator's output; use find_hook_pattern when you want their formula extracted rather than the raw list. Consumes 2 nooticr credits.",
|
|
25
25
|
inputSchema: z.object({ username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."), platform: z.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "reddit", "weibo"]).optional().describe("Which platform (default tiktok)."), limit: z.number().int().optional().describe("Max posts (default 6).") }).strict(),
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
name: "analyze_creator_profile",
|
|
29
29
|
title: "Research Creator Profile",
|
|
30
|
-
description: "A creator's recent posts with their stats, on TikTok, Instagram, YouTube, Reddit, Douyin, Xiaohongshu, X, Weibo, Bilibili or LinkedIn — the raw material of a profile teardown, for you to read. Work out their niche, content themes, hook formula, strengths and weaknesses, engagement pattern and audience from the spread of the numbers rather than the
|
|
30
|
+
description: "A creator's recent posts with their stats, on TikTok, Instagram, YouTube, Reddit, Douyin, Xiaohongshu, X, Weibo, Bilibili or LinkedIn — the raw material of a profile teardown, for you to read. Work out their niche, content themes, hook formula, strengths and weaknesses, engagement pattern and audience from the spread of the numbers rather than the top post, and name the posts you reason from. find_hook_pattern fetches the same posts and asks only for the formula. Costs 2 nooticr credits, for the one get_user_posts call it makes.",
|
|
31
31
|
inputSchema: z.object({ username: z.string().describe("Creator handle, e.g. 'zoundsapp'."), platform: z.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "reddit", "weibo"]).optional().describe("Which platform (default tiktok)."), limit: z.number().int().optional().describe("Posts to fetch (default 6; first 3 analyzed)."), focus: z.string().optional().describe("Extra instruction for the profile synthesis.") }).strict(),
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
name: "get_post_comments",
|
|
35
35
|
title: "Get Post Comments",
|
|
36
|
-
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Reddit, Douyin, X, Weibo, Bilibili or LinkedIn (plus keyword clusters from TikTok Analytics when available) — audience sentiment/audience-signal analysis.
|
|
36
|
+
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Reddit, Douyin, X, Weibo, Bilibili or LinkedIn (plus keyword clusters from TikTok Analytics when available) — audience sentiment/audience-signal analysis. Use when you want to read what people actually wrote; use summarize_post_comments when you want it synthesised into what to do next. Consumes 2 nooticr credits.",
|
|
37
37
|
inputSchema: z.object({ url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Reddit/Douyin/Weibo/Bilibili/LinkedIn). Not searchable here: xiaohongshu — it publishes no comment endpoint."), limit: z.number().int().optional().describe("Max comments (default 20).") }).strict(),
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
name: "search_creators",
|
|
41
41
|
title: "Search Creators",
|
|
42
|
-
description: "Find people by what they make. Search creators by craft, niche or keyword — designers, developers, photographers, illustrators, writers, founders — username, nickname, follower count, signature, verified status. Searches tiktok, instagram, xiaohongshu. Not searchable here: youtube, douyin, twitter, reddit, linkedin.
|
|
42
|
+
description: "Find people by what they make. Search creators by craft, niche or keyword — designers, developers, photographers, illustrators, writers, founders — username, nickname, follower count, signature, verified status. Searches tiktok, instagram, xiaohongshu. Not searchable here: youtube, douyin, twitter, reddit, linkedin. Use when you know the kind of person but not their names; use get_similar_creators when you already have one who works. Consumes 2 nooticr credits.",
|
|
43
43
|
inputSchema: z.object({ keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."), platform: z.enum(["tiktok", "instagram", "xiaohongshu"]).optional().describe("Which platform (default tiktok)."), count: z.number().int().optional().describe("Max creators (default 8).") }).strict(),
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
name: "get_similar_creators",
|
|
47
47
|
title: "Get Similar Creators",
|
|
48
|
-
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram similar users. Useful for scaling: 'if this creator works, here are more like them'.
|
|
48
|
+
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram similar users. Useful for scaling: 'if this creator works, here are more like them'. Use when one creator already fits and you want more of the same. Consumes 2 nooticr credits.",
|
|
49
49
|
inputSchema: z.object({ username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."), platform: z.enum(["tiktok", "instagram"]).optional().describe("Which platform (default tiktok).") }).strict(),
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -57,7 +57,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
57
57
|
{
|
|
58
58
|
name: "discover_sounds",
|
|
59
59
|
title: "Discover Sounds",
|
|
60
|
-
description: "Discover trending sounds/music for a keyword on TikTok or Instagram
|
|
60
|
+
description: "Discover trending sounds/music for a keyword on TikTok or Instagram. Returns title, artist, duration, play/cover URLs. Use when picking audio for a post, or to spot a sound before it peaks. Consumes 2 nooticr credits.",
|
|
61
61
|
inputSchema: z.object({ keyword: z.string().describe("Niche/keyword, e.g. 'gym'."), platform: z.enum(["tiktok", "instagram"]).optional().describe("Which platform (default tiktok)."), count: z.number().int().optional().describe("Max sounds (default 6).") }).strict(),
|
|
62
62
|
},
|
|
63
63
|
{
|
|
@@ -75,7 +75,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
75
75
|
{
|
|
76
76
|
name: "get_post_transcript",
|
|
77
77
|
title: "Get Post Transcript",
|
|
78
|
-
description: "Get the words actually spoken in a post, on any platform nooticr reads. Where the platform publishes a caption track (TikTok, Douyin, YouTube) it is read as-is; everywhere else, and for a post whose caption track is missing, the audio is transcribed on the same call, marked source:\"speech-to-text\" with languageDetected.
|
|
78
|
+
description: "Get the words actually spoken in a post, on any platform nooticr reads. Where the platform publishes a caption track (TikTok, Douyin, YouTube) it is read as-is; everywhere else, and for a post whose caption track is missing, the audio is transcribed on the same call, marked source:\"speech-to-text\" with languageDetected. Use before analyze_post when you need the script, hook wording or CTA verbatim rather than an interpretation. Returns plain text with a word count, or a final available:false with a reason; there is nothing to poll. Format 'srt' or 'vtt' also returns a ready caption file built from the track's own timing. A call that comes back with no transcript costs nothing, and neither does asking again for one already heard. The listening route needs speech-to-text configured on the server and does not transcribe posts longer than 30 minutes. Use before any analysis when the exact wording matters. Consumes 1 nooticr credit.",
|
|
79
79
|
inputSchema: z.object({ url: z.string().describe("Post URL (TikTok or YouTube)."), language: z.string().optional().describe("Preferred language code, e.g. 'en'. Picks the caption track, and is the language listened for; omit it to have the spoken language detected."), format: z.enum(["text", "srt", "vtt"]).optional().describe("Also return a caption file ('srt' or 'vtt'). Default 'text'.") }).strict(),
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -99,7 +99,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
99
99
|
{
|
|
100
100
|
name: "analyze_post_fast",
|
|
101
101
|
title: "Quick Social Post Analysis",
|
|
102
|
-
description: "A post's transcript, caption and stats, with no frames
|
|
102
|
+
description: "A post's transcript, caption and stats, with no frames. Work out the hook, the script structure, the CTA and the audience from the words and the numbers yourself, and say plainly that you have not seen the visuals. Use when the words and numbers are enough to answer; analyze_post adds the frames when a judgement needs the visuals. Fans out to get_social_media and get_post_transcript: 2 nooticr credits, 1 each.",
|
|
103
103
|
inputSchema: z.object({ url: z.string().describe("Full public post URL.") }).strict(),
|
|
104
104
|
},
|
|
105
105
|
{
|
|
@@ -117,7 +117,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
117
117
|
{
|
|
118
118
|
name: "evaluate_social_draft",
|
|
119
119
|
title: "Score a Social Media Draft",
|
|
120
|
-
description: "Score your own draft BEFORE you film or post it. Returns the draft with the rubric to hold it to — hook, clarity, payoff, specificity and fit, each scored 1-10 — and asks you for the three fixes that would move it most, one rewritten opening line and a tightened version. The only tool that runs before the content exists. Use before filming, while
|
|
120
|
+
description: "Score your own draft BEFORE you film or post it. Returns the draft with the rubric to hold it to — hook, clarity, payoff, specificity and fit, each scored 1-10 — and asks you for the three fixes that would move it most, one rewritten opening line and a tightened version. The only tool that runs before the content exists. Use before filming, while the draft can still be changed. Free: it fetches nothing, so it costs no credits.",
|
|
121
121
|
inputSchema: z.object({ draft: z.string().describe("Your script, caption or hook."), platform: z.string().optional().describe("Target platform (default tiktok).") }).strict(),
|
|
122
122
|
},
|
|
123
123
|
{
|
|
@@ -129,7 +129,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
129
129
|
{
|
|
130
130
|
name: "niche_report",
|
|
131
131
|
title: "Niche Report",
|
|
132
|
-
description: "Recent posts in a niche with their stats, so you can read what is working right now: dominant formats, recurring hook patterns, what over- and underperforms, and the gaps nobody is filling. Captions and performance, no video
|
|
132
|
+
description: "Recent posts in a niche with their stats, so you can read what is working right now: dominant formats, recurring hook patterns, what over- and underperforms, and the gaps nobody is filling. Captions and performance, no video. Only name a gap whose absence is visible in the set you were handed. Use when entering a niche or deciding what to make next, rather than judging one post. Costs 2 nooticr credits, for the one discover_social_posts call it makes.",
|
|
133
133
|
inputSchema: z.object({ niche: z.string().describe("Niche or topic, e.g. 'home fitness'."), platform: z.string().optional().describe("Platform to survey (default tiktok)."), count: z.number().int().optional().describe("Posts to survey (default 20, max 40).") }).strict(),
|
|
134
134
|
},
|
|
135
135
|
{
|
|
@@ -165,43 +165,43 @@ export const TOOL_DEFINITIONS = [
|
|
|
165
165
|
{
|
|
166
166
|
name: "show_comment_review",
|
|
167
167
|
title: "Show Comment Review",
|
|
168
|
-
description: "Display comment classifications you produced from summarize_post_comments. Free, and makes no requests — it only draws what you pass it. Renders each comment with its sentiment and category so a person can sort and act on them. Call this after you have classified the comments
|
|
168
|
+
description: "Display comment classifications you produced from summarize_post_comments. Free, and makes no requests — it only draws what you pass it. Renders each comment with its sentiment and category so a person can sort and act on them. Call this after you have classified the comments.",
|
|
169
169
|
inputSchema: z.object({ url: z.string().describe("The post the comments came from."), summary: z.string().optional(), title: z.string().optional(), comments: z.array(z.object({ id: z.string(), text: z.string(), author: z.string().optional(), likes: z.number().optional(), sentiment: z.string().optional(), category: z.string().optional(), note: z.string().optional() })), themes: z.array(z.string()).optional(), nextSteps: z.array(z.string()).optional() }).strict(),
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
name: "show_compared_posts",
|
|
173
173
|
title: "Show Comparison",
|
|
174
|
-
description: "Display a comparison you wrote after compare_social_posts fetched the first post and you fetched the rest yourself. Free, and makes no requests — it only draws what you pass it: each post with a
|
|
174
|
+
description: "Display a comparison you wrote after compare_social_posts fetched the first post and you fetched the rest yourself. Free, and makes no requests — it only draws what you pass it: each post with a badge on the one you judged the winner, what differed, shared strengths and the next experiment worth running. Call this after you have done the comparing.",
|
|
175
175
|
inputSchema: z.object({ posts: z.array(z.object({}).passthrough()).min(2).max(5), winner: z.number().int(), winnerReason: z.string().optional(), differences: z.array(z.object({ factor: z.string(), detail: z.string() })).optional(), lessons: z.array(z.string()).optional(), nextTest: z.string().optional() }).strict(),
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
name: "show_post_analysis",
|
|
179
179
|
title: "Show Analysis",
|
|
180
|
-
description: "Display an analysis you wrote after analyze_post, analyze_post_fast or understand_social_post handed you the material. Free, and makes no requests — it only draws what you pass it: hook strength, script structure, quotable lines, hashtags, target audience, viral triggers and more, whichever of these you actually produced. Call this after you have done the analysing
|
|
180
|
+
description: "Display an analysis you wrote after analyze_post, analyze_post_fast or understand_social_post handed you the material. Free, and makes no requests — it only draws what you pass it: hook strength, script structure, quotable lines, hashtags, target audience, viral triggers and more, whichever of these you actually produced. Call this after you have done the analysing.",
|
|
181
181
|
inputSchema: z.object({ url: z.string(), post: z.object({}).passthrough().optional(), analysis: z.object({}).passthrough() }).strict(),
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
name: "show_hooks",
|
|
185
185
|
title: "Show Hooks",
|
|
186
|
-
description: "Display the alternative opening hooks you wrote after generate_hook_ideas handed you a post's material (or just a topic). Free, and makes no requests — it only draws what you pass it: each hook with the device it uses and who it stops. Call this after you have written the hooks
|
|
186
|
+
description: "Display the alternative opening hooks you wrote after generate_hook_ideas handed you a post's material (or just a topic). Free, and makes no requests — it only draws what you pass it: each hook with the device it uses and who it stops. Call this after you have written the hooks.",
|
|
187
187
|
inputSchema: z.object({ url: z.string().optional(), topic: z.string().optional(), hooks: z.array(z.object({ hook: z.string(), mechanism: z.string().optional(), why: z.string().optional() })).min(1) }).strict(),
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
name: "show_variants",
|
|
191
191
|
title: "Show Variants",
|
|
192
|
-
description: "Display the post variants you wrote after generate_post_variants handed you the original post's material. Free, and makes no requests — it only draws what you pass it: each variant's hook, the angle that changes, its shot beats and its call to action. Call this after you have written the variants
|
|
192
|
+
description: "Display the post variants you wrote after generate_post_variants handed you the original post's material. Free, and makes no requests — it only draws what you pass it: each variant's hook, the angle that changes, its shot beats and its call to action. Call this after you have written the variants.",
|
|
193
193
|
inputSchema: z.object({ sourceUrl: z.string(), post: z.object({}).passthrough().optional(), variants: z.array(z.object({ title: z.string(), hook: z.string(), angle: z.string().optional(), beats: z.array(z.string()).optional(), cta: z.string().optional(), whyItCouldWork: z.string().optional() })).min(1) }).strict(),
|
|
194
194
|
},
|
|
195
195
|
{
|
|
196
196
|
name: "show_repurposed_post",
|
|
197
197
|
title: "Show Repurposed Post",
|
|
198
|
-
description: "Display the rewritten copy you produced after adapt_post_for_platform handed you the source post's material. Free, and makes no requests — it only draws what you pass it: one entry per surface you rewrote it for. Call this after you have done the rewriting
|
|
198
|
+
description: "Display the rewritten copy you produced after adapt_post_for_platform handed you the source post's material. Free, and makes no requests — it only draws what you pass it: one entry per surface you rewrote it for. Call this after you have done the rewriting.",
|
|
199
199
|
inputSchema: z.object({ sourceUrl: z.string(), versions: z.array(z.object({ surface: z.string(), content: z.string() })).min(1) }).strict(),
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
name: "nooticr_getting_started",
|
|
203
203
|
title: "Getting Started with nooticr",
|
|
204
|
-
description: "
|
|
204
|
+
description: "Help on what nooticr can do and what to try first. Free, and makes no paid call. Reads this account's state (credit balance, connected accounts, watchlist) and returns the next calls worth making, each with its price and an example. No cost to call.",
|
|
205
205
|
inputSchema: z.object({}).strict(),
|
|
206
206
|
},
|
|
207
207
|
{
|
|
@@ -255,13 +255,13 @@ export const TOOL_DEFINITIONS = [
|
|
|
255
255
|
{
|
|
256
256
|
name: "catch_up_watchlist",
|
|
257
257
|
title: "Catch Up On Watchlist",
|
|
258
|
-
description: "What the creators you watch have posted since you last checked. Fetches each one's recent posts and compares them against the snapshot taken at your last catch-up, then moves the snapshot forward \u2014 so it answers 'what is new' rather than 'what exists'. The first run for a creator records the baseline and has nothing to compare against. Use to follow a set of creators over time
|
|
258
|
+
description: "What the creators you watch have posted since you last checked. Fetches each one's recent posts and compares them against the snapshot taken at your last catch-up, then moves the snapshot forward \u2014 so it answers 'what is new' rather than 'what exists'. The first run for a creator records the baseline and has nothing to compare against. Use to follow a set of creators over time without re-reading each one. Consumes 2 nooticr credits per creator checked.",
|
|
259
259
|
inputSchema: z.object({ limit: z.number().int().optional().describe("Posts to check per creator (default 6)."), platform: z.string().optional().describe("Only check creators on this platform.") }).strict(),
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
name: "create_brand_watch",
|
|
263
263
|
title: "Create Brand Watch",
|
|
264
|
-
description: "Run a sweep on a schedule and email what is new
|
|
264
|
+
description: "Run a sweep on a schedule and email what is new. Use when the user wants to be told about new mentions or posts without asking each time. kind: \"mentions\" (default) sweeps term across platforms; kind: \"competitor\" sweeps one creator's own posts (handle + platform) and reports only what beats their own recent median; kind: \"report\" runs job {tool, args} (niche_report or discover_social_posts) on the schedule at that tool's own price; kind: \"portfolio\" sweeps several terms across the same platforms under one budget and mails one digest with each term's share of voice. Two calls by design: call it once with no confirmation to get back the cost per run and what it multiplies out to per day, then call it again with confirm: true and the confirmationToken to actually start it. The first call creates nothing and spends nothing. Bills 2 credits per network per run (5 for Xiaohongshu) for a mentions watch, or a flat 2 credits for a competitor watch, trimmed to fit budgetCredits. A run that finds nothing new (or, for a competitor watch, nothing above median) sends no mail. No cost to call.",
|
|
265
265
|
inputSchema: z.object({ kind: z.enum(["mentions", "competitor", "report", "portfolio"]).optional().describe("\"mentions\" (default), \"competitor\", \"report\" or \"portfolio\"."), job: z.object({ tool: z.enum(["niche_report", "discover_social_posts"]), args: z.object({}).passthrough() }).optional().describe("kind: report only — the job each run performs."), terms: z.array(z.string()).optional().describe("kind: portfolio only — the terms to compare (1-25)."), term: z.string().optional().describe("kind: mentions only — what to watch for (max 120 chars)."), platforms: z.array(z.string()).optional().describe("kind: mentions only — networks to sweep (default: every searchable network)."), handle: z.string().optional().describe("kind: competitor only — the creator's handle."), platform: z.string().optional().describe("kind: competitor only — the platform that creator posts on."), cadence: z.enum(["hourly", "every_6_hours", "every_12_hours", "daily", "weekly"]).optional().describe("How often to run (default daily)."), budgetCredits: z.number().int().optional().describe("Hard per-run credit ceiling (default the full sweep's cost)."), deliverTo: z.string().optional().describe("Digest email (default the account's own)."), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted cost."), confirmationToken: z.string().optional().describe("The token the first call returned.") }).strict(),
|
|
266
266
|
},
|
|
267
267
|
{
|
|
@@ -279,7 +279,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
279
279
|
{
|
|
280
280
|
name: "creative_voiceover",
|
|
281
281
|
title: "Create Voice-over",
|
|
282
|
-
description: "Voice-over with word timings (for burned-in captions); the text is spoken verbatim, up to 2500 characters. Pass a voiceRef (or voiceId) from creative_voices_search; library voices only, no cloning. quality: final (default) or draft (
|
|
282
|
+
description: "Voice-over with word timings (for burned-in captions); the text is spoken verbatim, up to 2500 characters. Pass a voiceRef (or voiceId) from creative_voices_search; library voices only, no cloning. quality: final (default) or draft (lower price, shorter render time). The first call is a free price quote in credits; nothing is charged until you confirm (vendor cost plus markup, never above the quote); nothing is charged until you confirm. Show the user the credits and ask before calling again with confirm: true and the confirmationToken. No real people's likeness or voice, no copyrighted music, logos or characters.",
|
|
283
283
|
inputSchema: z.object({ appId: z.number().int(), text: z.string().min(1).max(2500), voiceRef: z.object({ publicOwnerId: z.string(), voiceId: z.string() }).strict().optional().describe("A voiceRef from creative_voices_search."), voiceId: z.string().optional(), quality: z.enum(["final", "draft"]).optional(), stability: z.number().min(0).max(1).optional(), style: z.number().min(0).max(1).optional(), speed: z.number().min(0.7).max(1.2).optional(), briefId: z.string().optional(), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."), confirmationToken: z.string().optional().describe("The token the quote returned.") }).strict(),
|
|
284
284
|
},
|
|
285
285
|
{
|
|
@@ -297,7 +297,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
297
297
|
{
|
|
298
298
|
name: "creative_image",
|
|
299
299
|
title: "Create Images",
|
|
300
|
-
description: "Generate 1-4 still images with GPT Image (low quality
|
|
300
|
+
description: "Generate 1-4 still images with GPT Image (low quality setting) and store them as creative assets. The prompt is up to 4000 characters; do not put long text in it (put long text in a caption overlay) and do not ask for real named people. The first call is a free price quote in credits; nothing is charged until you confirm. Show the user the credits and ask before calling again with the same arguments plus confirm: true and the confirmationToken. Settles to the real usage, never above the quote. Returns assets and creativeCredits.",
|
|
301
301
|
inputSchema: z.object({ appId: z.number().int(), prompt: z.string().min(1).max(4000), size: z.enum(["1024x1536", "1024x1024", "1536x1024", "1088x1920", "1088x1360"]).optional().describe("Default 1024x1536 (portrait)."), n: z.number().int().min(1).max(4).optional(), transparent: z.boolean().optional(), briefId: z.string().optional(), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."), confirmationToken: z.string().optional().describe("The token the quote returned.") }).strict(),
|
|
302
302
|
},
|
|
303
303
|
{
|
|
@@ -357,7 +357,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
357
357
|
{
|
|
358
358
|
name: "mention_trend",
|
|
359
359
|
title: "Mention Trend",
|
|
360
|
-
description: "What a watch has SEEN OVER TIME,
|
|
360
|
+
description: "What a brand watch has SEEN OVER TIME, from the history of its scheduled runs. Use when the question is how mentions or a competitor's median changed across runs. Takes `watchId` (or `term`) and an optional `days`. Returns one point per run: when it ran, how many mentions it found, how many were new, and both counts per network. On a competitor watch each point also carries the median that run measured. Also returns the mentions more than one run has seen. FREE: the sweeps were billed when they ran. Read the series yourself and say what changed.",
|
|
361
361
|
inputSchema: z.object({ watchId: z.string().optional(), term: z.string().optional(), days: z.number().int().optional() }).strict(),
|
|
362
362
|
},
|
|
363
363
|
{
|
|
@@ -489,19 +489,19 @@ export const TOOL_DEFINITIONS = [
|
|
|
489
489
|
{
|
|
490
490
|
name: "list_comments_to_answer",
|
|
491
491
|
title: "List Comments To Answer",
|
|
492
|
-
description: "List recent comments on the user's own connected account that nobody has answered yet, newest first. Only the user's own posts, through their own connection: YouTube works once connected with capabilities: [\"manage_comments\"] (see connect_social_account); networks that cannot be answered come back under `unsupported` with the reason. Optional platform, influencerId, postId, sinceDays (1-30, default 7), limit (1-100, default 30), includeAnswered. Read-only: it sends nothing. No cost to call.",
|
|
492
|
+
description: "List recent comments on the user's own connected account that nobody has answered yet, newest first. Only the user's own posts, through their own connection: YouTube works once connected with capabilities: [\"manage_comments\"] (see connect_social_account); networks that cannot be answered come back under `unsupported` with the reason. Optional platform, influencerId, postId, sinceDays (1-30, default 7), limit (1-100, default 30), includeAnswered. Read-only: it sends nothing. No cost to call. Use when the user wants to reply to comments on their own connected account.",
|
|
493
493
|
inputSchema: z.object({ platform: z.enum(["youtube", "instagram"]).optional().describe("Only this network."), influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."), account: z.union([z.number().int(), z.string()]).optional().describe("Connection id (`connectionId` from list_social_connections) when the profile has more than one account on the platform."), postId: z.string().optional().describe("Only comments on this video/media id."), sinceDays: z.number().int().min(1).max(30).optional().describe("How far back to look (default 7)."), limit: z.number().int().min(1).max(100).optional().describe("Most comments to return (default 30)."), includeAnswered: z.boolean().optional().describe("Also list comments that already have a reply.") }).strict(),
|
|
494
494
|
},
|
|
495
495
|
{
|
|
496
496
|
name: "draft_comment_reply",
|
|
497
497
|
title: "Draft Comment Reply",
|
|
498
|
-
description: "Draft a reply to one comment from list_comments_to_answer and store it; nothing is posted. Pass `text` to use the user's own wording (no cost), or omit it and nooticr writes one (costs 1 nooticr credit); `guidance` steers the writing. Replies may not contain links, emails, phone numbers or @-mentions of anyone but the commenter. Returns a draftId and a confirmationToken: show the reply to the user first, then post it with post_comment_reply.",
|
|
498
|
+
description: "Draft a reply to one comment from list_comments_to_answer and store it; nothing is posted. Pass `text` to use the user's own wording (no cost), or omit it and nooticr writes one (costs 1 nooticr credit); `guidance` steers the writing. Replies may not contain links, emails, phone numbers or @-mentions of anyone but the commenter. Returns a draftId and a confirmationToken: show the reply to the user first, then post it with post_comment_reply. Use when the user wants to answer one comment from list_comments_to_answer.",
|
|
499
499
|
inputSchema: z.object({ platform: z.enum(["youtube", "instagram"]).describe("The network the comment is on."), commentId: z.string().describe("Comment id from list_comments_to_answer."), postId: z.string().describe("Post id the comment is on."), text: z.string().optional().describe("The reply to store, in the user's words. Omit to have one written (1 credit)."), influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."), account: z.union([z.number().int(), z.string()]).optional().describe("Connection id (`connectionId` from list_social_connections) when the profile has more than one account on the platform."), guidance: z.string().optional().describe("What the reply should say or avoid; a URL here is the only URL the reply may contain.") }).strict(),
|
|
500
500
|
},
|
|
501
501
|
{
|
|
502
502
|
name: "post_comment_reply",
|
|
503
503
|
title: "Post Comment Reply",
|
|
504
|
-
description: "Post a stored reply draft to the comment, as the user's own account. Only after the user has seen and agreed to the exact text: pass the draftId, confirm: true and the confirmationToken from draft_comment_reply. Each comment is answered at most once and daily limits apply. It writes to a third-party network and cannot be undone from here. No cost to call.",
|
|
504
|
+
description: "Post a stored reply draft to the comment, as the user's own account. Only after the user has seen and agreed to the exact text: pass the draftId, confirm: true and the confirmationToken from draft_comment_reply. Each comment is answered at most once and daily limits apply. It writes to a third-party network and cannot be undone from here. No cost to call. Use only once the user has approved a draft's exact text.",
|
|
505
505
|
inputSchema: z.object({ draftId: z.string().describe("Draft id from draft_comment_reply."), confirm: z.boolean().describe("true only once the user has agreed to the exact text."), confirmationToken: z.string().describe("Token returned with the draft.") }).strict(),
|
|
506
506
|
},
|
|
507
507
|
{
|
|
@@ -525,7 +525,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
525
525
|
{
|
|
526
526
|
name: "show_audience_replies",
|
|
527
527
|
title: "Show Audience Replies",
|
|
528
|
-
description: "Lay out the replies you drafted from answer_my_audience so a person can read them, grouped under the post each comment was left on, with what you decided to do about each one. Sends nothing and fetches nothing \u2014 it only draws what you pass it. Use after drafting
|
|
528
|
+
description: "Lay out the replies you drafted from answer_my_audience so a person can read them, grouped under the post each comment was left on, with what you decided to do about each one. Sends nothing and fetches nothing \u2014 it only draws what you pass it. Use after drafting. No cost to call.",
|
|
529
529
|
inputSchema: z.object({ username: z.string().describe("Whose audience this is, with or without @."), summary: z.string().optional(), replies: z.array(z.object({ id: z.string(), comment: z.string(), author: z.string().optional(), likes: z.number().optional(), postUrl: z.string().optional(), postTitle: z.string().optional(), postedAt: z.string().optional(), draft: z.string().optional(), kind: z.string().optional(), why: z.string().optional() })), themes: z.array(z.string()).optional(), nextSteps: z.array(z.string()).optional() }).strict(),
|
|
530
530
|
},
|
|
531
531
|
{
|
|
@@ -537,25 +537,25 @@ export const TOOL_DEFINITIONS = [
|
|
|
537
537
|
{
|
|
538
538
|
name: "compare_creators",
|
|
539
539
|
title: "Compare Creators",
|
|
540
|
-
description: "Two to five creators side by side, each scored against THEIR OWN recent median rather than against each other's raw numbers
|
|
540
|
+
description: "Two to five creators side by side, each scored against THEIR OWN recent median rather than against each other's raw numbers, because a raw view count mostly measures follower count. Returns per creator: how many posts were scored, their own median, the share of the window that beat it, how hard they beat it when they did, every post's ratio, and their highest and lowest post. The ranking is yours to make: 'how often' and 'how big' usually disagree, and a hit rate over a short window is one post either way, so the window size travels with every number. Fetches one post list per creator: 2 nooticr credits each, 4-10 in total. Use to compare hit rates between two to five creators, yourself included; track_creator covers one creator.",
|
|
541
541
|
inputSchema: z.object({ usernames: z.array(z.string()).min(2).max(5), platform: z.string().optional(), limit: z.number().int().optional(), metric: z.string().optional() }).strict(),
|
|
542
542
|
},
|
|
543
543
|
{
|
|
544
544
|
name: "watchlist_standings",
|
|
545
545
|
title: "Watchlist Standings",
|
|
546
|
-
description: "The same comparison across everyone on your watchlist, answering 'who moved' in one call
|
|
546
|
+
description: "The same comparison across everyone on your watchlist, answering 'who moved' in one call. Each is scored against their own recent median, so the numbers are comparable between accounts of different sizes; the window size travels with each so a short one is not ranked as if it were a season. Reads the watchlist itself for free and fetches one post list per creator on it: 2 nooticr credits each, so the price is the size of your watchlist — it confirms before spending. Use for the weekly 'who is accelerating'; catch_up_watchlist is what is NEW rather than how it did.",
|
|
547
547
|
inputSchema: z.object({ limit: z.number().int().optional(), metric: z.string().optional() }).strict(),
|
|
548
548
|
},
|
|
549
549
|
{
|
|
550
550
|
name: "show_standings",
|
|
551
551
|
title: "Show Standings",
|
|
552
|
-
description: "Draw the standings you read out of compare_creators or watchlist_standings. Free, and makes no requests — it only renders what you pass it: one row per creator with their window, their own median, how often they beat it and how hard, and
|
|
552
|
+
description: "Draw the standings you read out of compare_creators or watchlist_standings. Free, and makes no requests — it only renders what you pass it: one row per creator with their window, their own median, how often they beat it and how hard, and their highest-performing post. Pass `ranking` to say which axis you ordered on, and `tooThin` for the creators whose window was too short to rank — a table that hides that is the one mistake this view can make on your behalf. Call this after you have decided what the numbers mean.",
|
|
553
553
|
inputSchema: z.object({ creators: z.array(z.object({}).passthrough()).min(1).max(25), metric: z.string().optional(), ranking: z.string().optional(), verdict: z.string().optional(), tooThin: z.array(z.string()).optional() }).strict(),
|
|
554
554
|
},
|
|
555
555
|
{
|
|
556
556
|
name: "who_should_i_work_with",
|
|
557
557
|
title: "Who Should I Work With",
|
|
558
|
-
description: "A shortlist of people to work with \u2014 collaborators, or anyone to hire or commission: designers, developers, photographers, editors. Searches creators by craft or keyword and, when you name someone who already fits, merges in their lookalikes \u2014 marking which search found each one, since agreement between the two is the strongest signal in the result. Every candidate carries the links pulled out of their bio, so vetting is reading the work. Searches tiktok, instagram, xiaohongshu. Not searchable here: youtube, douyin, twitter, reddit, linkedin. It does not measure audience overlap: proving the same people comment under two accounts costs roughly nine credits per candidate, so the result says so and shows how to check a finalist
|
|
558
|
+
description: "A shortlist of people to work with \u2014 collaborators, or anyone to hire or commission: designers, developers, photographers, editors. Searches creators by craft or keyword and, when you name someone who already fits, merges in their lookalikes \u2014 marking which search found each one, since agreement between the two is the strongest signal in the result. Every candidate carries the links pulled out of their bio, so vetting is reading the work. Searches tiktok, instagram, xiaohongshu. Not searchable here: youtube, douyin, twitter, reddit, linkedin. It does not measure audience overlap: proving the same people comment under two accounts costs roughly nine credits per candidate, so the result says so and shows how to check a finalist rather than guess. Use to build a list to vet. Consumes 2 nooticr credits, or 4 with a seed creator.",
|
|
559
559
|
inputSchema: z.object({ niche: z.string().describe("Niche or keyword, e.g. 'home fitness'."), platform: z.enum(["tiktok", "instagram", "xiaohongshu"]).optional().describe("Which platform (default tiktok)."), seed: z.string().optional().describe("A creator who already fits \u2014 their lookalikes are added. Costs 2 more credits."), count: z.number().int().optional().describe("Candidates from the keyword search (default 8, max 20).") }).strict(),
|
|
560
560
|
},
|
|
561
561
|
{
|
|
@@ -573,7 +573,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
573
573
|
{
|
|
574
574
|
name: "find_people_with_problem",
|
|
575
575
|
title: "Find People With A Problem",
|
|
576
|
-
description: "Find people describing a problem in their own words — prospects, early users, anyone whose complaint your product answers. Say the problem the way a person would say it (\"I spend hours checking competitors by hand\") and this searches for posts shaped like that complaint, not for the topic it is about. That difference is the point: searching the TOPIC finds marketing about it, which is what discover_social_posts is for. Widens the query into the forms a complaint takes — the plain phrasing, the does-anyone-else question, the is-there-a-tool ask — and merges what each returns. It is a
|
|
576
|
+
description: "Find people describing a problem in their own words — prospects, early users, anyone whose complaint your product answers. Say the problem the way a person would say it (\"I spend hours checking competitors by hand\") and this searches for posts shaped like that complaint, not for the topic it is about. That difference is the point: searching the TOPIC finds marketing about it, which is what discover_social_posts is for. Widens the query into the forms a complaint takes — the plain phrasing, the does-anyone-else question, the is-there-a-tool ask — and merges what each returns. It is a broad search and NOT a filter: the posts come back for you to judge, and many will be off-target. Consumes 2 nooticr credits per search, and each platform is searched once per query shape — three by default, so 6 credits per platform. Defaults to reddit, where people describe workflow pain in sentences; add twitter for volume. Not searchable here: linkedin.",
|
|
577
577
|
inputSchema: z.object({ problem: z.string().describe("The problem in a person's own words, not a topic. \"I waste an hour a day checking what competitors posted\" finds people; \"competitor analysis\" finds marketing about competitor analysis."), platforms: z.array(z.enum(["reddit", "twitter", "youtube", "tiktok", "instagram"])).optional().describe("Where to look (default reddit). Each one is a separate paid search per query shape. Not searchable here: linkedin — no keyword post search exists for it upstream, so asking for it could only ever return nothing, which is why it is absent from this list rather than merely untested."), limit: z.number().int().optional().describe("Posts per query shape (default 6, max 15)."), readComments: z.boolean().optional().describe("Open the most promising results and read their comments too (default false). On Reddit especially, the person with the problem is at least as often replying under someone else's thread as posting their own, and a post-only search cannot see any of them. Costs 2 nooticr credits per post opened, up to 5 \u2014 and they are opened on the MERGED, deduped results, so it is five extra calls in total rather than five per query shape per platform."), queries: z.array(z.string()).optional().describe("Search these exact phrasings instead of the ones derived from `problem` (max 4). You are better at this than a string template is: write how a person would actually word the complaint on the network being searched \u2014 a Reddit title, an X post \u2014 and keep each one short, because this is a keyword search and a long sentence matches on its commonest words."), verbatim: z.boolean().optional().describe("Render every post and reply whole in the text you read (default false).") }).strict(),
|
|
578
578
|
},
|
|
579
579
|
{
|
|
@@ -672,6 +672,30 @@ export const TOOL_DEFINITIONS = [
|
|
|
672
672
|
description: "Reject one pending AI-employee action by actionId, with an optional note the employee can learn from. Only the hiring user or a workspace admin can. No cost to call.",
|
|
673
673
|
inputSchema: z.object({ actionId: z.string().describe("Action id from list_pending_actions."), note: z.string().optional().describe("Why, so the employee can learn.") }).strict(),
|
|
674
674
|
},
|
|
675
|
+
{
|
|
676
|
+
name: "crm_upsert_creator",
|
|
677
|
+
title: "Upsert Creator",
|
|
678
|
+
description: "Use when you want to track a creator or record new numbers for one already tracked. Add a creator to this workspace's creator pipeline, or update the one already there (matched by platform + handle, case-insensitive). Only the fields you pass change. New creators start as 'prospect'; use crm_update_status to change that. Put every number you cite in `evidence` as [{tool, url, metric, value, at}]. engagementRate is a fraction (0.052 = 5.2%). No cost to call.",
|
|
679
|
+
inputSchema: z.object({ platform: z.string().describe("tiktok, instagram, youtube, ..."), handle: z.string(), displayName: z.string().optional(), profileUrl: z.string().optional(), followers: z.number().int().min(0).optional(), avgViews: z.number().int().min(0).optional(), engagementRate: z.number().min(0).max(1).optional(), niche: z.string().optional(), country: z.string().optional(), contactEmail: z.string().optional().describe("Only an address the creator published."), fitScore: z.number().int().min(0).max(100).optional(), fitReasons: z.array(z.string()).optional(), risks: z.array(z.string()).optional(), evidence: z.array(z.object({}).passthrough()).optional(), nextStep: z.string().optional(), nextStepDue: z.string().optional().describe("YYYY-MM-DD"), appId: z.number().int().optional().describe("Product this creator is for.") }).strict(),
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: "crm_list_creators",
|
|
683
|
+
title: "List Creators",
|
|
684
|
+
description: "Use when someone asks who is in their creator pipeline or which next steps are due. List the workspace's creator pipeline, most recently updated first, each with its last interaction. Filter by status, appId, query (handle or name) or dueBefore (next step due on or before YYYY-MM-DD). limit is at most 100. No cost to call.",
|
|
685
|
+
inputSchema: z.object({ status: z.enum(["prospect", "shortlisted", "contacted", "replied", "negotiating", "active", "paused", "declined", "do_not_contact"]).optional(), appId: z.number().int().optional(), query: z.string().optional(), limit: z.number().int().min(1).max(100).optional(), dueBefore: z.string().optional().describe("YYYY-MM-DD") }).strict(),
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "crm_update_status",
|
|
689
|
+
title: "Update Creator Status",
|
|
690
|
+
description: "Use when a creator moves forward or back in the pipeline, for example after a reply or a decision. Move a creator to a new pipeline status and record why (writes a status_change entry). A creator marked do_not_contact stays that way unless a person changes it. AI employees may only use prospect, shortlisted or paused. No cost to call.",
|
|
691
|
+
inputSchema: z.object({ creatorId: z.string().describe("Id from crm_list_creators."), status: z.enum(["prospect", "shortlisted", "contacted", "replied", "negotiating", "active", "paused", "declined", "do_not_contact"]), note: z.string().describe("Why.") }).strict(),
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
name: "crm_log_interaction",
|
|
695
|
+
title: "Log Creator Interaction",
|
|
696
|
+
description: "Use when something happens with a creator that the pipeline should remember. Record something that happened with a creator: a note, vetting result, outreach draft (put the message in body), reply or deliverable. AI employees cannot log outreach_sent: a person sends outreach and logs it. No cost to call.",
|
|
697
|
+
inputSchema: z.object({ creatorId: z.string(), kind: z.enum(["note", "vetting", "outreach_draft", "outreach_sent", "reply", "deliverable"]), summary: z.string().describe("At most 4000 characters."), body: z.string().optional().describe("At most 8000 characters, e.g. the drafted message.") }).strict(),
|
|
698
|
+
},
|
|
675
699
|
{
|
|
676
700
|
name: "marketplace_changes",
|
|
677
701
|
title: "Marketplace Changes",
|
|
@@ -681,7 +705,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
681
705
|
{
|
|
682
706
|
name: "get_price_history",
|
|
683
707
|
title: "Get Price History",
|
|
684
|
-
description: "One product's price over time — or its rating, review count or availability with `metric` — from the observations stored each time the product was scanned. `marketplace` and `id` name the product the same way get_marketplace_product does; `from` and `to` take dates (default: the last 30 days). Returns the points oldest first with min, max, first, last and the percentage change from first to last. A point is stored only when something changed, so the last value held until `lastSeenAt`. Reads stored data only: it never fetches, and a product nobody has scanned yet says so (`recorded: false`) — run get_marketplace_product once to start its record. Free.",
|
|
708
|
+
description: "One product's price over time — or its rating, review count or availability with `metric` — from the observations stored each time the product was scanned. Use when someone asks how a product's price, rating, review count or stock moved over time. `marketplace` and `id` name the product the same way get_marketplace_product does; `from` and `to` take dates (default: the last 30 days). Returns the points oldest first with min, max, first, last and the percentage change from first to last. A point is stored only when something changed, so the last value held until `lastSeenAt`. Reads stored data only: it never fetches, and a product nobody has scanned yet says so (`recorded: false`) — run get_marketplace_product once to start its record. Free.",
|
|
685
709
|
inputSchema: z.object({ marketplace: z.string().describe("Which site the product is on."), id: z.string().describe("The site's own product id, or a product URL from it."), market: z.string().optional().describe("The storefront the product is on, if the site has several."), from: z.string().optional().describe("Start of the window, as a date (2026-09-01) or an ISO timestamp. Default: 30 days before `to`."), to: z.string().optional().describe("End of the window, inclusive. Default: now."), metric: z.enum(["price", "rating", "reviewCount", "availability"]).optional().describe("What to chart (default price).") }).strict(),
|
|
686
710
|
},
|
|
687
711
|
{
|
|
@@ -693,13 +717,13 @@ export const TOOL_DEFINITIONS = [
|
|
|
693
717
|
{
|
|
694
718
|
name: "show_amazon_category_insights",
|
|
695
719
|
title: "Show Amazon Category Insights",
|
|
696
|
-
description: "Display the category read you produced from scan_amazon_category: purchase drivers, barriers, what each brand does well, the gaps, and the positioning angles \u2014 drawn beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. Pass the scanId: the listings are then re-read from the scan itself (free) rather than taken from you, and any ASIN you cite that the scan does not contain is flagged. Without a scanId, listings you pass are drawn marked as not checked against a scan. Free. Your read is attributed to you rather than presented as a nooticr rating of anyone's product. Call this after you have read the reviews
|
|
720
|
+
description: "Display the category read you produced from scan_amazon_category: purchase drivers, barriers, what each brand does well, the gaps, and the positioning angles \u2014 drawn beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. Pass the scanId: the listings are then re-read from the scan itself (free) rather than taken from you, and any ASIN you cite that the scan does not contain is flagged. Without a scanId, listings you pass are drawn marked as not checked against a scan. Free. Your read is attributed to you rather than presented as a nooticr rating of anyone's product. Call this after you have read the reviews.",
|
|
697
721
|
inputSchema: z.object({ category: z.string().describe("The category this is a read of."), summary: z.string().optional(), drivers: z.array(z.any()).optional().describe("What makes someone buy, each with the review evidence behind it."), barriers: z.array(z.any()).optional().describe("What stops them buying, or makes them return it."), strengths: z.array(z.any()).optional().describe("What each incumbent does well."), gaps: z.array(z.any()).optional().describe("What buyers keep asking for that nobody serves."), positioning: z.array(z.any()).optional().describe("Angles a new entrant could take."), products: z.array(z.any()).optional().describe("The listings from the scan, passed straight through."), rollup: z.record(z.unknown()).optional().describe("The rollup from the scan, passed straight through."), scanId: z.string().optional() }).passthrough(),
|
|
698
722
|
},
|
|
699
723
|
{
|
|
700
724
|
name: "show_marketplace_category_insights",
|
|
701
725
|
title: "Show Marketplace Category Insights",
|
|
702
|
-
description: "Display the category read you produced from scan_marketplace_category: purchase drivers, barriers, what each brand does well, the gaps, and the positioning angles \u2014 drawn beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. Pass the scanId: the listings are then re-read from the scan itself (free) rather than taken from you, and any ASIN you cite that the scan does not contain is flagged. Without a scanId, listings you pass are drawn marked as not checked against a scan. Free. Your read is attributed to you rather than presented as a nooticr rating of anyone's product. Call this after you have read the reviews
|
|
726
|
+
description: "Display the category read you produced from scan_marketplace_category: purchase drivers, barriers, what each brand does well, the gaps, and the positioning angles \u2014 drawn beside the listings and their reviews, so a person can click a product and check any claim against the text it came from. Pass the scanId: the listings are then re-read from the scan itself (free) rather than taken from you, and any ASIN you cite that the scan does not contain is flagged. Without a scanId, listings you pass are drawn marked as not checked against a scan. Free. Your read is attributed to you rather than presented as a nooticr rating of anyone's product. Call this after you have read the reviews.",
|
|
703
727
|
inputSchema: z.object({ marketplace: z.string().describe("The site the scan ran on, e.g. lazada."), category: z.string().describe("The category this is a read of."), summary: z.string().optional(), drivers: z.array(z.any()).optional().describe("What makes someone buy, each with the review evidence behind it."), barriers: z.array(z.any()).optional().describe("What stops them buying, or makes them return it."), strengths: z.array(z.any()).optional().describe("What each incumbent does well."), gaps: z.array(z.any()).optional().describe("What buyers keep asking for that nobody serves."), positioning: z.array(z.any()).optional().describe("Angles a new entrant could take."), products: z.array(z.any()).optional().describe("The listings from the scan, passed straight through."), rollup: z.record(z.unknown()).optional().describe("The rollup from the scan, passed straight through."), scanId: z.string().optional() }).passthrough(),
|
|
704
728
|
},
|
|
705
729
|
{
|