@nooticr/mcp 1.26.41 → 1.26.43
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 +17 -8
- package/dist/shared/amazon.js +85 -14
- package/dist/shared/backend-only.js +24 -0
- package/dist/shared/connections.js +4 -2
- package/dist/shared/evidence-digest.js +83 -20
- package/dist/shared/getting-started.js +201 -0
- package/dist/shared/jobs.js +20 -10
- package/dist/shared/loading-plans.js +21 -0
- package/dist/shared/nooticr.js +29 -1
- package/dist/shared/oauth.js +3 -2
- package/dist/shared/output-schemas.js +143 -4
- package/dist/shared/own-account.js +62 -0
- package/dist/shared/prompts.js +5 -1
- package/dist/shared/spend.js +4 -0
- package/dist/shared/tools-def.js +61 -7
- package/dist/shared/tools.js +290 -8
- package/dist/shared/ui-template.js +222 -5
- package/dist/shared/watchlist.js +39 -0
- package/package.json +1 -1
|
@@ -39,9 +39,18 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
39
39
|
get_post_comments:"Get Post Comments",
|
|
40
40
|
search_creators:"Search Creators",
|
|
41
41
|
get_similar_creators:"Similar Creators",
|
|
42
|
+
suggest_creator_identity:"Same Person Elsewhere",
|
|
42
43
|
discover_sounds:"Discover Sounds",
|
|
43
44
|
understand_social_post:"Understand Social Post",
|
|
44
45
|
check_nooticr_credits:"Check Credits",
|
|
46
|
+
list_tool_runs:"Tool Runs",
|
|
47
|
+
get_tool_run:"Tool Run",
|
|
48
|
+
list_watchlist:"Watchlist",
|
|
49
|
+
detect_spoken_mentions:"Spoken Mentions",
|
|
50
|
+
nooticr_getting_started:"Getting Started",
|
|
51
|
+
get_google_analytics:"Google Analytics",
|
|
52
|
+
get_search_console_data:"Search Console",
|
|
53
|
+
get_posthog_analytics:"PostHog",
|
|
45
54
|
compose_sequence:"Compose Sequence",
|
|
46
55
|
overlay_bake:"Overlay Bake",
|
|
47
56
|
spawn_variants:"Spawn Variants",
|
|
@@ -68,9 +77,18 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
68
77
|
get_post_comments:"Top comments on a post — sentiment, themes, viral threads.",
|
|
69
78
|
search_creators:"Find creators in a niche by engagement, followers, and content.",
|
|
70
79
|
get_similar_creators:"Find creators similar to a given handle.",
|
|
80
|
+
suggest_creator_identity:"Accounts on other networks that may be the same person, with the evidence.",
|
|
71
81
|
discover_sounds:"Trending sounds and music on TikTok/Instagram.",
|
|
72
82
|
understand_social_post:"The same frames and transcript, for a description of what happens on screen.",
|
|
73
83
|
check_nooticr_credits:"View your Nooticr credit balance and usage.",
|
|
84
|
+
list_tool_runs:"Where your credits went: every call, what it cost, and whether it worked.",
|
|
85
|
+
get_tool_run:"One call from your history, with its full error.",
|
|
86
|
+
list_watchlist:"The creators you are watching, and when you last caught up on each.",
|
|
87
|
+
detect_spoken_mentions:"Brands named out loud in a video, beside what its caption says.",
|
|
88
|
+
nooticr_getting_started:"Where your account stands and what to try first.",
|
|
89
|
+
get_google_analytics:"Your GA4 sync, as of its last sync.",
|
|
90
|
+
get_search_console_data:"Search clicks, impressions and top queries, as of the last sync.",
|
|
91
|
+
get_posthog_analytics:"Your PostHog pageview trend, as of the last sync.",
|
|
74
92
|
compose_sequence:"AI-powered content composition for social posts.",
|
|
75
93
|
overlay_bake:"Bake text/image overlays onto video or image.",
|
|
76
94
|
spawn_variants:"Generate multiple content variants from a single seed.",
|
|
@@ -2997,6 +3015,141 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
2997
3015
|
+"</div>";
|
|
2998
3016
|
}
|
|
2999
3017
|
|
|
3018
|
+
// ─── Identity suggestions (suggest_creator_identity) ───
|
|
3019
|
+
//
|
|
3020
|
+
// Its own view rather than creatorCard: a candidate carries a points score
|
|
3021
|
+
// (50 for a shared bio link, 25 for a cross-link) that the vetting strip
|
|
3022
|
+
// would draw as "/100", and no follower total, because adding two accounts'
|
|
3023
|
+
// followers together on this evidence is exactly what the tool forbids.
|
|
3024
|
+
// Every card says "suggestion" on its face for the same reason.
|
|
3025
|
+
function renderIdentity(d){
|
|
3026
|
+
var seed=d.seed||{},list=d.suggestions||[];
|
|
3027
|
+
var searched=(d.searched||[]).join(", ");
|
|
3028
|
+
var head='<div style="margin-bottom:12px"><div style="font-size:15px;font-weight:700">Possibly the same person as @'
|
|
3029
|
+
+esc(seed.handle||"")+" on "+esc(seed.platform||"")+"</div>"
|
|
3030
|
+
+'<div style="font-size:12px;color:var(--muted);margin-top:3px">'
|
|
3031
|
+
+list.length+" suggestion"+(list.length===1?"":"s")+(searched?" · looked on "+esc(searched):"")
|
|
3032
|
+
+" · nothing merged, a human confirms</div>"
|
|
3033
|
+
+(d.note?'<div style="font-size:12px;color:var(--muted);margin-top:6px">'+esc(d.note)+"</div>":"")
|
|
3034
|
+
+"</div>";
|
|
3035
|
+
var cards=list.map(function(c){
|
|
3036
|
+
var high=c.confidence==="high",color=pColor(c.platform||"");
|
|
3037
|
+
var ev=(c.evidence||[]).map(function(e){
|
|
3038
|
+
var strong=e.strength==="strong";
|
|
3039
|
+
return '<li style="margin:3px 0"><span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:'
|
|
3040
|
+
+(strong?"var(--green)":"var(--muted)")+'">'+esc(e.strength||"")+"</span> "
|
|
3041
|
+
+'<span style="font-weight:600">'+esc(String(e.signal||"").split("_").join(" "))+"</span>"
|
|
3042
|
+
+(e.detail?' <span style="color:var(--muted)">· '+esc(e.detail)+"</span>":"")+"</li>";
|
|
3043
|
+
}).join("");
|
|
3044
|
+
var link=c.profileUrl&&String(c.profileUrl).indexOf("http")===0
|
|
3045
|
+
?'<a href="'+esc(c.profileUrl)+'" style="font-size:12px;color:var(--fg)">Open profile</a>':"";
|
|
3046
|
+
return '<div class="creator-card" data-identity-candidate>'
|
|
3047
|
+
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">'
|
|
3048
|
+
+'<div class="avatar-placeholder" style="flex-shrink:0;background:'+color+'20">'+pSvg(c.platform||"",22)+"</div>"
|
|
3049
|
+
+'<div style="min-width:0"><div style="font-size:14px;font-weight:700">'+esc(c.nickname||c.username||"")+"</div>"
|
|
3050
|
+
+'<div class="handle" style="overflow-wrap:anywhere">@'+esc(c.username||"")+" · "+esc(c.platform||"")+"</div></div></div>"
|
|
3051
|
+
+'<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;color:'
|
|
3052
|
+
+(high?"var(--green)":"var(--muted)")+'">'+(high?"likely":"possible")+" · suggestion</div>"
|
|
3053
|
+
+(c.followers!=null?'<div style="font-size:12px;color:var(--muted);margin-bottom:6px">'+fmtNum(c.followers)+" followers on this account alone</div>":"")
|
|
3054
|
+
+(ev?'<ul style="list-style:none;padding:0;margin:0;font-size:12px;line-height:1.4">'+ev+"</ul>":"")
|
|
3055
|
+
+(link?'<div style="margin-top:8px">'+link+"</div>":"")
|
|
3056
|
+
+"</div>";
|
|
3057
|
+
}).join("");
|
|
3058
|
+
var none=list.length?"":'<div class="empty-state"><div class="icon">👤</div><div class="text">No account cleared the bar. A creator with no links in their bio is hard to match this way; that is not proof they have no other accounts.</div></div>';
|
|
3059
|
+
var gaps=(d.unavailable||[]).map(function(u){return esc(u.platform||"")+" could not be searched";}).join(" · ");
|
|
3060
|
+
return '<div class="fade-in">'+head+none+(cards?'<div class="gallery">'+cards+"</div>":"")
|
|
3061
|
+
+(gaps?'<div style="font-size:12px;color:var(--muted);margin-top:10px">'+gaps+"</div>":"")+"</div>";
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// ─── Getting started (nooticr_getting_started) ───
|
|
3065
|
+
//
|
|
3066
|
+
// Checked before the credits card, which it would otherwise fall into: it
|
|
3067
|
+
// carries a balance too. A null is drawn as "could not read", never as 0 —
|
|
3068
|
+
// "no balance" and "no connections" are claims this view has not checked.
|
|
3069
|
+
function renderGettingStarted(d){
|
|
3070
|
+
function stat(label,val,unknown){
|
|
3071
|
+
return '<div style="flex:1;min-width:120px;padding:10px 12px;border:1px solid var(--border);border-radius:10px">'
|
|
3072
|
+
+'<div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">'+esc(label)+"</div>"
|
|
3073
|
+
+'<div style="font-size:16px;font-weight:700;margin-top:3px">'+(val==null?'<span style="color:var(--muted);font-weight:500;font-size:13px">'+esc(unknown)+"</span>":esc(String(val)))+"</div></div>";
|
|
3074
|
+
}
|
|
3075
|
+
var head='<div style="font-size:15px;font-weight:700;margin-bottom:10px">'
|
|
3076
|
+
+(d.signedIn?"Where your nooticr account stands":"Not signed in to nooticr yet")+"</div>";
|
|
3077
|
+
var stats=d.signedIn
|
|
3078
|
+
?'<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px">'
|
|
3079
|
+
+stat("Credits",d.balance,"could not read")
|
|
3080
|
+
+stat("Connected accounts",d.connectedCount,"could not read")
|
|
3081
|
+
+stat("Watching",d.watching,"could not read")+"</div>"
|
|
3082
|
+
:"";
|
|
3083
|
+
var steps=(d.nextSteps||[]).map(function(n){
|
|
3084
|
+
var price=Number(n.credits)>0?esc(String(n.credits))+" cr":"free";
|
|
3085
|
+
var ex=n.example&&typeof n.example==="object"?JSON.stringify(n.example):"";
|
|
3086
|
+
return '<div data-next-step style="padding:10px 0;border-top:1px solid var(--border)">'
|
|
3087
|
+
+'<div style="display:flex;align-items:baseline;gap:8px"><code style="font-weight:700">'+esc(n.tool||"")+"</code>"
|
|
3088
|
+
+'<span style="margin-left:auto;font-size:11px;font-weight:700;color:'+(price==="free"?"var(--green)":"var(--muted)")+'">'+price+"</span></div>"
|
|
3089
|
+
+'<div style="font-size:13px;margin-top:3px">'+esc(n.why||"")+"</div>"
|
|
3090
|
+
+(ex?'<div style="font-size:12px;color:var(--muted);margin-top:3px;font-family:ui-monospace,monospace">'+esc(ex)+"</div>":"")
|
|
3091
|
+
+"</div>";
|
|
3092
|
+
}).join("");
|
|
3093
|
+
return '<div class="fade-in">'+head+stats
|
|
3094
|
+
+'<div class="sec-label">Try next</div>'+steps+"</div>";
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// ─── Connector reads (get_google_analytics, get_search_console_data,
|
|
3098
|
+
// get_posthog_analytics) ───
|
|
3099
|
+
//
|
|
3100
|
+
// Every one is a stored sync, so "as of" leads the card: a number drawn
|
|
3101
|
+
// without its date is read as today's (#104).
|
|
3102
|
+
function renderConnector(d){
|
|
3103
|
+
var names={google_analytics:"Google Analytics",search_console:"Search Console",posthog:"PostHog"};
|
|
3104
|
+
var name=names[d.connector]||"Analytics";
|
|
3105
|
+
var when=String(d.synced_at||d.lastSync||d.lastRefresh||"");
|
|
3106
|
+
var asOf=when?"as of "+esc(when.slice(0,16).split("T").join(" "))+" UTC · not live":"sync time not reported";
|
|
3107
|
+
var head='<div style="margin-bottom:12px"><div style="font-size:15px;font-weight:700">'+esc(name)
|
|
3108
|
+
+(d.appName?" · "+esc(d.appName):"")+"</div>"
|
|
3109
|
+
+'<div data-as-of style="font-size:12px;color:var(--muted);margin-top:2px">'+asOf+"</div></div>";
|
|
3110
|
+
if(d.message){
|
|
3111
|
+
return '<div class="fade-in">'+head+'<div class="empty-state"><div class="icon">📊</div><div class="text">'+esc(d.message)+"</div></div></div>";
|
|
3112
|
+
}
|
|
3113
|
+
function tile(label,val){
|
|
3114
|
+
return '<div style="flex:1;min-width:120px;padding:10px 12px;border:1px solid var(--border);border-radius:10px">'
|
|
3115
|
+
+'<div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">'+esc(label)+"</div>"
|
|
3116
|
+
+'<div style="font-size:18px;font-weight:700;margin-top:3px">'+esc(String(val))+"</div></div>";
|
|
3117
|
+
}
|
|
3118
|
+
var body="";
|
|
3119
|
+
if(d.connector==="search_console"){
|
|
3120
|
+
var clicks=Number(d.clicks)||0,imp=Number(d.impressions)||0;
|
|
3121
|
+
body='<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">'
|
|
3122
|
+
+tile("Clicks",fmtNum(clicks))+tile("Impressions",fmtNum(imp))
|
|
3123
|
+
+tile("Click-through",imp?(Math.round(clicks/imp*1000)/10)+"%":"—")+"</div>"
|
|
3124
|
+
+(d.error?'<div style="font-size:12px;color:var(--red);margin-bottom:8px">Search Console refused the last sync: '+esc(d.error)+"</div>":"")
|
|
3125
|
+
+((d.topQueries||[]).length
|
|
3126
|
+
?'<div class="sec-label">Top queries</div>'+(d.topQueries||[]).map(function(q,i){
|
|
3127
|
+
return '<div data-query style="padding:6px 0;border-top:1px solid var(--border);font-size:13px"><span style="color:var(--muted);margin-right:8px">'+(i+1)+"</span>"+esc(q)+"</div>";
|
|
3128
|
+
}).join("")
|
|
3129
|
+
:"");
|
|
3130
|
+
}else if(d.connector==="google_analytics"){
|
|
3131
|
+
var r=d.dateRange||{};
|
|
3132
|
+
body='<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px">'
|
|
3133
|
+
+tile("Days synced",d.rowsSynced==null?"—":d.rowsSynced)
|
|
3134
|
+
+tile("Range",(r.from||"?")+" → "+(r.to||"?"))+"</div>"
|
|
3135
|
+
+(d.propertyId?'<div style="font-size:12px;color:var(--muted)">Property '+esc(d.propertyId)+"</div>":"")
|
|
3136
|
+
+'<div style="font-size:12px;color:var(--muted);margin-top:6px">The metrics themselves feed post performance and the growth brief.</div>';
|
|
3137
|
+
}else{
|
|
3138
|
+
var series=Array.isArray(d.result)?d.result:[];
|
|
3139
|
+
body=series.length?series.map(function(s){
|
|
3140
|
+
var pts=Array.isArray(s.data)?s.data.map(Number):[];
|
|
3141
|
+
var total=pts.reduce(function(a,b){return a+(b||0);},0),max=Math.max.apply(null,pts.concat([1]));
|
|
3142
|
+
var bars=pts.slice(-30).map(function(v){
|
|
3143
|
+
return '<span style="display:inline-block;width:6px;margin-right:2px;background:var(--fg);opacity:.55;height:'+Math.max(2,Math.round((v||0)/max*40))+'px"></span>';
|
|
3144
|
+
}).join("");
|
|
3145
|
+
return '<div data-series style="margin-bottom:12px"><div style="font-size:13px;font-weight:600">'+esc(s.label||s.action&&s.action.name||"Pageviews")
|
|
3146
|
+
+' <span style="color:var(--muted);font-weight:500">· '+fmtNum(total)+" total</span></div>"
|
|
3147
|
+
+'<div style="display:flex;align-items:flex-end;height:44px;margin-top:6px">'+bars+"</div></div>";
|
|
3148
|
+
}).join(""):'<div style="font-size:13px;color:var(--muted)">No trend in the last sync.</div>';
|
|
3149
|
+
}
|
|
3150
|
+
return '<div class="fade-in">'+head+body+"</div>";
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3000
3153
|
// ─── Vetting strip ───
|
|
3001
3154
|
//
|
|
3002
3155
|
// Only show_collab_shortlist sends these fields, so every other creator card
|
|
@@ -3414,8 +3567,8 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
3414
3567
|
+'<div class="sec-label">Read-only — nooticr can sweep these, no account to link</div>'
|
|
3415
3568
|
+'<div class="chiprow">'+readOnlyTags+"</div>"
|
|
3416
3569
|
+'<div class="section-text" style="margin-top:12px;color:var(--muted)">So "monitor my '
|
|
3417
|
-
+'brand on Weibo" works and "reply to that Weibo comment" cannot —
|
|
3418
|
-
+"
|
|
3570
|
+
+'brand on Weibo" works and "reply to that Weibo comment" cannot — nooticr does not '
|
|
3571
|
+
+"post replies on any network, whatever a connection is allowed to do.</div>"
|
|
3419
3572
|
+"</div></div>"
|
|
3420
3573
|
:"")
|
|
3421
3574
|
+"</div>";
|
|
@@ -4061,10 +4214,23 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4061
4214
|
var drivers=(ins.drivers||[]).map(function(f){return amazonFinding(f,"driver");}).join("");
|
|
4062
4215
|
var barriers=(ins.barriers||[]).map(function(f){return amazonFinding(f,"barrier");}).join("");
|
|
4063
4216
|
var gaps=(ins.gaps||[]).map(function(f){return amazonFinding(f,"gap");}).join("");
|
|
4217
|
+
// #107: whether the listings on this card are the scan's own, re-read by
|
|
4218
|
+
// the server, or whatever the model re-sent. Only the first earns the
|
|
4219
|
+
// "check any claim" line at the bottom.
|
|
4220
|
+
var ver=ins.verification||null,verified=!!(ver&&ver.status==="verified");
|
|
4221
|
+
var unknown=(ver&&ver.unknownAsins)||[];
|
|
4064
4222
|
var strengths=(ins.strengths||[]).map(function(s){
|
|
4223
|
+
var off=s.asin&&unknown.indexOf(s.asin)!==-1;
|
|
4065
4224
|
return '<div class="amz-strength"><span class="amz-strength-brand">'+esc(s.brand||"")+"</span>"
|
|
4066
|
-
+'<span class="amz-strength-detail">'+esc(s.detail||"")+"</span
|
|
4225
|
+
+'<span class="amz-strength-detail">'+esc(s.detail||"")+"</span>"
|
|
4226
|
+
+(off?'<span data-not-in-scan style="margin-left:6px;font-size:10px;font-weight:700;text-transform:uppercase;color:var(--red,#b42318)">not in the scan</span>':"")
|
|
4227
|
+
+"</div>";
|
|
4067
4228
|
}).join("");
|
|
4229
|
+
var verBox=ver
|
|
4230
|
+
?'<div data-verification="'+(verified?"verified":"unverified")+'" style="font-size:12px;padding:8px 10px;margin-bottom:10px;border-radius:8px;border:1px solid var(--border);'
|
|
4231
|
+
+(verified?"":"background:rgba(180,35,24,.06);")+'">'
|
|
4232
|
+
+'<b>'+(verified?"Listings re-read from the scan":"Listings not checked against a scan")+"</b> · "+esc(ver.note||"")+"</div>"
|
|
4233
|
+
:"";
|
|
4068
4234
|
var angles=(ins.positioning||[]).map(function(a){
|
|
4069
4235
|
return '<div class="amz-angle"><div class="amz-angle-claim">'+esc(a.angle||"")+"</div>"
|
|
4070
4236
|
+(a.who?'<div class="amz-angle-row"><b>For</b> '+esc(a.who)+"</div>":"")
|
|
@@ -4073,6 +4239,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4073
4239
|
+"</div>";
|
|
4074
4240
|
}).join("");
|
|
4075
4241
|
return '<div class="fade-in">'
|
|
4242
|
+
+verBox
|
|
4076
4243
|
+(ins.summary?'<div class="lede-box">'+esc(ins.summary)+"</div>":"")
|
|
4077
4244
|
+block("Purchase drivers","What makes someone buy. Click an id to read the review it rests on.",drivers)
|
|
4078
4245
|
+block("Purchase barriers","What stops them, or brings it back.",barriers)
|
|
@@ -4080,7 +4247,8 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4080
4247
|
+block("Gaps in the category","Asked for repeatedly, served by nobody in this set.",gaps)
|
|
4081
4248
|
+block("Positioning angles","",angles)
|
|
4082
4249
|
+'<div class="ai-note">This read is your model’s, from the reviews on this screen — not a nooticr '
|
|
4083
|
-
+"rating of anyone’s product.
|
|
4250
|
+
+"rating of anyone’s product."
|
|
4251
|
+
+(verified||!ver?" Every claim above can be checked against the text it came from.":" The listings were not checked against a scan, so check them before relying on them.")+"</div>"
|
|
4084
4252
|
+"</div>";
|
|
4085
4253
|
}
|
|
4086
4254
|
|
|
@@ -4409,6 +4577,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4409
4577
|
return;}
|
|
4410
4578
|
|
|
4411
4579
|
// Creators
|
|
4580
|
+
if(Array.isArray(d.suggestions)&&d.seed){app.innerHTML=renderIdentity(d);setTimeout(reportSize,50);return;}
|
|
4412
4581
|
if(d.creators&&Array.isArray(d.creators)){
|
|
4413
4582
|
if(!d.creators.length){app.innerHTML='<div class="empty-state fade-in"><div class="icon">👤</div><div class="text">No creators found</div></div>';return;}
|
|
4414
4583
|
app.innerHTML=galleryWrap(d.creators.map(function(c){return creatorCard(c);}).join(""),d.creators.length);return;}
|
|
@@ -4423,7 +4592,12 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4423
4592
|
+'<div style="display:flex;align-items:center;gap:7px;font-size:16px;font-weight:700;margin-bottom:6px">'+mpIcon("note",16)+"<span>Transcript</span></div>"
|
|
4424
4593
|
+'<div class="muted" style="font-size:13px;color:var(--muted)">'+esc(d.reason||"No transcript available.")+"</div></div></div>";
|
|
4425
4594
|
return;}
|
|
4426
|
-
|
|
4595
|
+
// No regex escape here: this file is one template literal (and the
|
|
4596
|
+
// page is backslash-free by test), so the old whitespace class reached
|
|
4597
|
+
// the browser as the letter s ("I switched to Acme grinders" counted 3
|
|
4598
|
+
// words). Whitespace is spelled out by char code instead.
|
|
4599
|
+
var WS=new RegExp("["+String.fromCharCode(32,9,10,13)+"]+");
|
|
4600
|
+
var wc=d.wordCount||(d.transcript?String(d.transcript).trim().split(WS).filter(Boolean).length:0);
|
|
4427
4601
|
var meta=[wc+" words"];
|
|
4428
4602
|
if(d.language)meta.push(esc(d.language));
|
|
4429
4603
|
if(d.autoGenerated)meta.push("auto-generated");
|
|
@@ -4613,7 +4787,50 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4613
4787
|
+(s.videoCount?'<div style="font-size:11px;color:var(--muted);margin-top:2px">'+fmtNum(s.videoCount)+" videos</div>":"")
|
|
4614
4788
|
+"</div></div>"+player+"</div>";
|
|
4615
4789
|
}).join(""),d.sounds.length);return;}
|
|
4790
|
+
// Run history (list_tool_runs / get_tool_run): where the credits went.
|
|
4791
|
+
// One row per call, the credits it actually took on the right, so the
|
|
4792
|
+
// expensive calls are the ones the eye lands on.
|
|
4793
|
+
if(Array.isArray(d.runs)||(d.run&&d.run.tool)){
|
|
4794
|
+
var runs=Array.isArray(d.runs)?d.runs:[d.run];
|
|
4795
|
+
if(!runs.length){app.innerHTML='<div class="empty-state fade-in"><div class="icon">🧾</div><div class="text">No runs in this window</div></div>';return;}
|
|
4796
|
+
var runRows=runs.map(function(r){
|
|
4797
|
+
var when=r.startedAt?String(r.startedAt).slice(0,16).split("T").join(" ")+" UTC":"";
|
|
4798
|
+
var secs=Number(r.durationMs)>0?(Number(r.durationMs)/1000).toFixed(1)+"s":"";
|
|
4799
|
+
var meta=[when,secs,r.userId?"user "+String(r.userId).slice(0,8):""].filter(Boolean).join(" • ");
|
|
4800
|
+
var cr=Number(r.credits)||0;
|
|
4801
|
+
return '<div style="display:flex;align-items:flex-start;gap:10px;padding:9px 0;border-bottom:1px solid var(--border)">'
|
|
4802
|
+
+'<div style="flex:1;min-width:0">'
|
|
4803
|
+
+'<div style="font-size:13.5px;font-weight:600">'+(r.ok===false?"⚠️ ":"")+esc(r.tool||"")+"</div>"
|
|
4804
|
+
+'<div style="font-size:11.5px;color:var(--muted)">'+esc(meta)+"</div>"
|
|
4805
|
+
+(r.error?'<div style="font-size:12px;color:var(--muted);margin-top:3px;word-break:break-word">'+esc(String(r.error))+"</div>":"")
|
|
4806
|
+
+"</div>"
|
|
4807
|
+
+'<div style="font-size:14px;font-weight:700;white-space:nowrap">'+(cr?cr+" cr":"free")+"</div></div>";
|
|
4808
|
+
}).join("");
|
|
4809
|
+
var total=d.creditsOnThisPage!=null?'<div class="section-label" style="margin-bottom:6px">'+esc(String(d.creditsOnThisPage))+" credits across "+runs.length+" call"+(runs.length===1?"":"s")+(d.scope==="workspace"?" in the workspace":"")+"</div>":"";
|
|
4810
|
+
app.innerHTML='<div class="card card-wide fade-in"><div class="card-body">'
|
|
4811
|
+
+'<div style="font-size:16px;font-weight:700;margin-bottom:8px">🧾 '+(d.run?"Tool run":"Where your credits went")+"</div>"
|
|
4812
|
+
+total+runRows
|
|
4813
|
+
+(d.nextBefore!=null?'<div style="font-size:11.5px;color:var(--muted);margin-top:8px">More runs are older than these.</div>':"")
|
|
4814
|
+
+"</div></div>";return;}
|
|
4815
|
+
// Watchlist (list_watchlist): who is being watched, and since when.
|
|
4816
|
+
if(Array.isArray(d.entries)&&d.watching!=null&&!d.posts&&!d.results){
|
|
4817
|
+
if(!d.entries.length){app.innerHTML='<div class="empty-state fade-in"><div class="icon">👀</div><div class="text">You are not watching anyone yet</div></div>';return;}
|
|
4818
|
+
var wlRows=d.entries.map(function(e){
|
|
4819
|
+
if(!e)return"";
|
|
4820
|
+
var since=e.lastCaughtUpAt?"caught up "+String(e.lastCaughtUpAt).slice(0,10):"never caught up";
|
|
4821
|
+
var added=e.addedAt?"added "+String(e.addedAt).slice(0,10):"";
|
|
4822
|
+
return '<div style="padding:9px 0;border-bottom:1px solid var(--border)">'
|
|
4823
|
+
+'<div style="font-size:13.5px;font-weight:600">@'+esc(e.handle||"")+' <span style="font-weight:400;color:var(--muted)">'+esc(e.platform||"")+"</span></div>"
|
|
4824
|
+
+'<div style="font-size:11.5px;color:var(--muted)">'+esc([added,since].filter(Boolean).join(" • "))+"</div>"
|
|
4825
|
+
+(e.note?'<div style="font-size:12px;margin-top:3px">'+esc(String(e.note))+"</div>":"")
|
|
4826
|
+
+"</div>";
|
|
4827
|
+
}).join("");
|
|
4828
|
+
app.innerHTML='<div class="card card-wide fade-in"><div class="card-body">'
|
|
4829
|
+
+'<div style="font-size:16px;font-weight:700;margin-bottom:8px">👀 Watching '+esc(String(d.watching))+"</div>"
|
|
4830
|
+
+wlRows+"</div></div>";return;}
|
|
4616
4831
|
// Credits
|
|
4832
|
+
if(d.gettingStarted===true){app.innerHTML=renderGettingStarted(d);setTimeout(reportSize,50);return;}
|
|
4833
|
+
if(d.connector==="google_analytics"||d.connector==="search_console"||d.connector==="posthog"){app.innerHTML=renderConnector(d);setTimeout(reportSize,50);return;}
|
|
4617
4834
|
if(d.balance!=null||d.tier){
|
|
4618
4835
|
var bal=Number(d.balance)||0,tier=d.tier||"",ff=d.firstFreeTools||[];
|
|
4619
4836
|
var freeHtml="";
|
package/dist/shared/watchlist.js
CHANGED
|
@@ -513,6 +513,45 @@ export function registerWatchlist(server, makeClient, store) {
|
|
|
513
513
|
const entries = await store.list(owner);
|
|
514
514
|
return text({ removed, id, watching: entries.length, entries });
|
|
515
515
|
});
|
|
516
|
+
// Reading the list back (#101). The resource above says the same thing, but
|
|
517
|
+
// most hosts never surface a resource to the model and ChatGPT does not read
|
|
518
|
+
// one at all, so "who am I watching?" had no free answer: the only tools
|
|
519
|
+
// that returned the list were the paid catch-up and standings runs. Built on
|
|
520
|
+
// `store`, not the backend's own twin, for the reason the header gives.
|
|
521
|
+
server.registerTool("list_watchlist", {
|
|
522
|
+
title: "List Watchlist",
|
|
523
|
+
_meta: {
|
|
524
|
+
ui: { resourceUri: "ui://nooticr/list_watchlist" },
|
|
525
|
+
"ui/resourceUri": "ui://nooticr/list_watchlist",
|
|
526
|
+
"openai/outputTemplate": "ui://nooticr/list_watchlist.html",
|
|
527
|
+
},
|
|
528
|
+
description: "The creators on your watchlist: handle, platform, your note, when you added them and when " +
|
|
529
|
+
"you last caught up on them. Reads the stored list only — nothing is fetched, so there is no " +
|
|
530
|
+
"cost. Use it to answer 'who am I watching?', or to find the exact handle and platform " +
|
|
531
|
+
"unwatch_creator needs, before paying for catch_up_watchlist. No cost to call.",
|
|
532
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
533
|
+
inputSchema: z.object({}).strict(),
|
|
534
|
+
outputSchema: z
|
|
535
|
+
.object({
|
|
536
|
+
watching: orNull(z.number()),
|
|
537
|
+
entries: z.array(z.union([z.object({}).passthrough(), z.null()])).nullish(),
|
|
538
|
+
})
|
|
539
|
+
.passthrough(),
|
|
540
|
+
}, async (_args, extra) => {
|
|
541
|
+
const client = await makeClient({ ...extra, arguments: {} });
|
|
542
|
+
const entries = (await store.list(await ownerOf(client))).map((e) => ({
|
|
543
|
+
id: e.id,
|
|
544
|
+
platform: e.platform,
|
|
545
|
+
handle: e.handle,
|
|
546
|
+
note: e.note ?? null,
|
|
547
|
+
addedAt: e.addedAt,
|
|
548
|
+
// When "since I last looked" starts from; null until the first catch-up.
|
|
549
|
+
// The baseline's post ids stay out: they are the store's bookkeeping,
|
|
550
|
+
// and a long list of them is context spent on nothing.
|
|
551
|
+
lastCaughtUpAt: e.baseline?.capturedAt ?? null,
|
|
552
|
+
}));
|
|
553
|
+
return text({ watching: entries.length, entries });
|
|
554
|
+
});
|
|
516
555
|
server.registerTool("catch_up_watchlist", {
|
|
517
556
|
title: "Catch Up On Watchlist",
|
|
518
557
|
_meta: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nooticr/mcp",
|
|
3
3
|
"mcpName": "com.nooticr/mcp",
|
|
4
|
-
"version": "1.26.
|
|
4
|
+
"version": "1.26.43",
|
|
5
5
|
"description": "MCP server for nooticr - fetch, discover and understand TikTok/Instagram/YouTube/X/Reddit/LinkedIn/Weibo posts with AI (media metadata, niche discovery, brand monitoring, hook & viral analysis)",
|
|
6
6
|
"homepage": "https://mcp.nooticr.com",
|
|
7
7
|
"repository": {
|