@nooticr/mcp 1.26.27 → 1.26.29
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 -5
- package/dist/shared/brand-watch.js +19 -7
- package/dist/shared/evidence.js +67 -0
- package/dist/shared/jobs.js +366 -17
- package/dist/shared/loading-plans.js +29 -1
- package/dist/shared/output-schemas.js +110 -2
- package/dist/shared/prompts.js +1 -1
- package/dist/shared/standings.js +146 -0
- package/dist/shared/tools-def.js +26 -2
- package/dist/shared/tools.js +137 -22
- package/dist/shared/ui-template.js +71 -3
- package/dist/shared/watchlist.js +13 -5
- package/package.json +4 -3
package/dist/shared/tools.js
CHANGED
|
@@ -14,7 +14,7 @@ import { OUTPUT_SCHEMAS, anyObject } from "./output-schemas.js";
|
|
|
14
14
|
import { deriveHashtags, derivedNote } from "./hashtags.js";
|
|
15
15
|
import { createTaskStore, registerSlowTool } from "./tasks.js";
|
|
16
16
|
import { COMMENT_CATEGORIES, COMMENT_SENTIMENTS, platformFromUrl, reviewGuidance, toEvidence, } from "./comment-review.js";
|
|
17
|
-
import { costSentence, EVIDENCE_PLANS, fetchBillingNote, frameIndex, framesToBlocks, planCalls, scoreDraftGuidance, } from "./evidence.js";
|
|
17
|
+
import { costSentence, EVIDENCE_PLANS, fetchBillingNote, frameIndex, framesToBlocks, planCalls, scoreDraftGuidance, KNOWN_PLATFORMS, platformFailureGuidance, } from "./evidence.js";
|
|
18
18
|
import { confirmSpend, declinedResult, searchMentionsCost, SEARCH_PLATFORMS, DISCOVERABLE_PLATFORMS, } from "./spend.js";
|
|
19
19
|
import { BackendWatchStore, MemoryWatchStore, registerWatchlist, } from "./watchlist.js";
|
|
20
20
|
import { registerJobTools } from "./jobs.js";
|
|
@@ -25,7 +25,7 @@ import { loadingPlansJson } from "./loading-plans.js";
|
|
|
25
25
|
import { registerHandoff } from "./handoff.js";
|
|
26
26
|
import { registerCollabTools } from "./collab.js";
|
|
27
27
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
28
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
28
|
+
export const MCP_SERVER_VERSION = "1.26.29";
|
|
29
29
|
/** MCP Apps extension identifier */
|
|
30
30
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
31
31
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -342,6 +342,21 @@ function toolError(prefix, err) {
|
|
|
342
342
|
isError: true,
|
|
343
343
|
};
|
|
344
344
|
}
|
|
345
|
+
// A network that ERRORED is not a network that came back empty, and the two
|
|
346
|
+
// want opposite responses: an empty result means try a different query, an
|
|
347
|
+
// error means try a different network or tell the user. Nothing said which,
|
|
348
|
+
// so a host did the one thing that cannot work — re-ran the same call with
|
|
349
|
+
// the words changed, three times, each a paid upstream call. The backend
|
|
350
|
+
// names the platform in these messages ("... failed: reddit: ..."), so the
|
|
351
|
+
// split is recoverable here without every one of the 26 call sites having to
|
|
352
|
+
// pass its arguments down.
|
|
353
|
+
const platform = KNOWN_PLATFORMS.find((p) => new RegExp(`(^|[^a-z])${p}([^a-z]|$)`, "i").test(msg));
|
|
354
|
+
if (platform) {
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: `${prefix}. ${platformFailureGuidance({ platform, message: msg })}` }],
|
|
357
|
+
isError: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
345
360
|
return { content: [{ type: "text", text: `${prefix}: ${msg}` }], isError: true };
|
|
346
361
|
}
|
|
347
362
|
/**
|
|
@@ -377,16 +392,34 @@ async function runEvidence(tool, args, client) {
|
|
|
377
392
|
extra = { unavailable: plan.also.via };
|
|
378
393
|
}
|
|
379
394
|
}
|
|
380
|
-
// The guidance
|
|
381
|
-
// that
|
|
382
|
-
//
|
|
395
|
+
// The guidance rides in BOTH channels, and the structured one is the one
|
|
396
|
+
// that arrives.
|
|
397
|
+
//
|
|
398
|
+
// This used to say "the guidance leads: a model reads the first text block in
|
|
399
|
+
// a result, and that is where the account of what to produce has to be". That
|
|
400
|
+
// is what the MCP spec allows, and it is not what hosts do. Claude Code
|
|
401
|
+
// discards every `content` text block whenever a result carries
|
|
402
|
+
// `structuredContent`, keeps the non-text blocks, and shows the model the
|
|
403
|
+
// serialised JSON instead — unconditionally, with no setting, and without
|
|
404
|
+
// consulting `outputSchema`. Every tool here declares one, so every guidance
|
|
405
|
+
// string this file writes was landing nowhere.
|
|
406
|
+
//
|
|
407
|
+
// Measured before changing it, over 59 real runs of the quest suite: 0
|
|
408
|
+
// guidance phrases across 175 tool results. `analyze_post` handed back frames,
|
|
409
|
+
// a transcript and 2,000 characters ending in "call show_analysis when you are
|
|
410
|
+
// done"; the model wrote the analysis and delivered it in chat, and
|
|
411
|
+
// show_analysis was called in 1 run of 12.
|
|
412
|
+
//
|
|
413
|
+
// The text block stays for hosts that honour it (and for the images, which
|
|
414
|
+
// must sit in `content`). `guidance` on the payload is what reaches the rest.
|
|
415
|
+
// See docs/testing/tool-chaining-quests.md.
|
|
383
416
|
const billing = nothingToFetch
|
|
384
417
|
? "Nothing was fetched for this call, so nothing was charged."
|
|
385
418
|
: fetchBillingNote(tool);
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
];
|
|
419
|
+
const guidance = `${plan.guidance(args)}\n\n${billing}`;
|
|
420
|
+
const content = [{ type: "text", text: guidance }];
|
|
389
421
|
const out = {
|
|
422
|
+
guidance,
|
|
390
423
|
// Kept, and constant. It is not an echo of an argument — the job tools in
|
|
391
424
|
// jobs.ts set the same key with no argument to echo — it marks a payload as
|
|
392
425
|
// material the caller still has to read. Dropping it would break anything
|
|
@@ -525,7 +558,12 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
525
558
|
// a search_mentions result render in the monitoring view.
|
|
526
559
|
"answer_my_audience",
|
|
527
560
|
"show_audience_replies",
|
|
528
|
-
"
|
|
561
|
+
"track_creator",
|
|
562
|
+
// The comparison track_creator computes per creator and never puts side
|
|
563
|
+
// by side, plus the free view that draws the table once a read is written.
|
|
564
|
+
"compare_creators",
|
|
565
|
+
"watchlist_standings",
|
|
566
|
+
"show_standings",
|
|
529
567
|
"who_should_i_work_with",
|
|
530
568
|
"why_did_this_underperform",
|
|
531
569
|
"what_should_i_make_next",
|
|
@@ -533,6 +571,10 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
533
571
|
// hits rather than comments, so it gets its own view rather than the
|
|
534
572
|
// monitoring one.
|
|
535
573
|
"search_spoken_mentions",
|
|
574
|
+
// Prospect discovery: the same keyword search as discover_social_posts, but
|
|
575
|
+
// widened into the shapes a complaint takes rather than the topic it is
|
|
576
|
+
// about. Returns `posts`, so the gallery view draws it.
|
|
577
|
+
"find_people_with_problem",
|
|
536
578
|
// Own-account intelligence (own-account.ts). list_own_apps stays
|
|
537
579
|
// view-less like watch_creator — it lists metadata, nothing to draw.
|
|
538
580
|
// get_scheduled_posts and get_post_performance return a `posts` array
|
|
@@ -902,7 +944,11 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
902
944
|
"Work out their niche, recurring themes, hook formula, what over- and under-performs and who " +
|
|
903
945
|
"their audience is, reading the spread of the numbers rather than only the best post, and " +
|
|
904
946
|
`name the posts you reason from. ${costSentence("analyze_creator_profile")} ` +
|
|
905
|
-
"Use for the teardown itself; find_hook_pattern fetches the same posts and asks only for the
|
|
947
|
+
"Use for the teardown itself; find_hook_pattern fetches the same posts and asks only for the " +
|
|
948
|
+
"formula. NOT for keeping an eye on a rival over time: \"track X\", \"how are they doing " +
|
|
949
|
+
"lately\", \"what have they shipped since I last looked\" are track_creator, which scores " +
|
|
950
|
+
"each post against that creator's own median and remembers where you left off. This tool " +
|
|
951
|
+
"returns the posts and no verdict, and remembers nothing between calls.",
|
|
906
952
|
_meta: {
|
|
907
953
|
ui: { resourceUri: uiResource("analyze_creator_profile") },
|
|
908
954
|
"ui/resourceUri": uiResource("analyze_creator_profile"),
|
|
@@ -1155,9 +1201,14 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1155
1201
|
const res = await client.callTool("get_post_comments", { ...args });
|
|
1156
1202
|
const structured = (res.structured ?? {});
|
|
1157
1203
|
const comments = toEvidence(args.url, structured.comments);
|
|
1204
|
+
const guidance = reviewGuidance(args.url, comments.length);
|
|
1158
1205
|
return {
|
|
1159
|
-
content: [{ type: "text", text:
|
|
1206
|
+
content: [{ type: "text", text: guidance }],
|
|
1160
1207
|
structuredContent: {
|
|
1208
|
+
// See runEvidence: a text block alone reaches no host that renders
|
|
1209
|
+
// structuredContent, and this is the tool show_comment_review's whole
|
|
1210
|
+
// chain hangs off.
|
|
1211
|
+
guidance,
|
|
1161
1212
|
mode: "evidence",
|
|
1162
1213
|
url: args.url,
|
|
1163
1214
|
platform: structured.platform ?? null,
|
|
@@ -1418,6 +1469,62 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1418
1469
|
},
|
|
1419
1470
|
};
|
|
1420
1471
|
});
|
|
1472
|
+
server.registerTool("show_standings", {
|
|
1473
|
+
title: "Show Standings",
|
|
1474
|
+
description: "Draw the standings you read out of compare_creators or watchlist_standings. Free, and makes " +
|
|
1475
|
+
"no requests — it only renders what you pass it: one row per creator with their window, their " +
|
|
1476
|
+
"own median, how often they beat it and how hard, and the post that did best. Pass `ranking` " +
|
|
1477
|
+
"to say which axis you ordered on, and `tooThin` for the creators whose window was too short " +
|
|
1478
|
+
"to rank — a table that hides that is the one mistake this view can make on your behalf. " +
|
|
1479
|
+
"Call this after you have decided what the numbers mean, not instead of deciding.",
|
|
1480
|
+
_meta: {
|
|
1481
|
+
ui: { resourceUri: uiResource("show_standings") },
|
|
1482
|
+
"ui/resourceUri": uiResource("show_standings"),
|
|
1483
|
+
"openai/outputTemplate": appsSdkResource("show_standings"),
|
|
1484
|
+
},
|
|
1485
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1486
|
+
outputSchema: OUTPUT_SCHEMAS.show_standings,
|
|
1487
|
+
inputSchema: z
|
|
1488
|
+
.object({
|
|
1489
|
+
creators: z
|
|
1490
|
+
.array(anyObject())
|
|
1491
|
+
.min(1)
|
|
1492
|
+
.max(25)
|
|
1493
|
+
.describe("The creator rows, in the order you decided on — the same shape compare_creators returned " +
|
|
1494
|
+
"(handle, platform, window, baseline, hitRate, medianWinRatio, best, ...)."),
|
|
1495
|
+
metric: z.string().optional().describe("Which stat the standings are on (views, likes, ...)."),
|
|
1496
|
+
ranking: z
|
|
1497
|
+
.string()
|
|
1498
|
+
.optional()
|
|
1499
|
+
.describe('What you ordered on and why — "how often they land one" and "how big it is when they do" ' +
|
|
1500
|
+
"usually disagree, so the reader needs to know which they are looking at."),
|
|
1501
|
+
verdict: z.string().optional().describe("Your read, in a sentence or two."),
|
|
1502
|
+
tooThin: z
|
|
1503
|
+
.array(z.string())
|
|
1504
|
+
.optional()
|
|
1505
|
+
.describe("Handles whose window was too short to rank. Drawn as unranked rather than bottom, " +
|
|
1506
|
+
"because missing from a comparison is not the same as losing it."),
|
|
1507
|
+
})
|
|
1508
|
+
.strict(),
|
|
1509
|
+
}, async (args) => {
|
|
1510
|
+
return {
|
|
1511
|
+
content: [
|
|
1512
|
+
{
|
|
1513
|
+
type: "text",
|
|
1514
|
+
text: `Showing standings for ${args.creators.length} creator${args.creators.length === 1 ? "" : "s"}.` +
|
|
1515
|
+
(args.verdict ? ` ${args.verdict}` : ""),
|
|
1516
|
+
},
|
|
1517
|
+
],
|
|
1518
|
+
structuredContent: {
|
|
1519
|
+
creators: args.creators,
|
|
1520
|
+
metric: args.metric ?? "views",
|
|
1521
|
+
ranking: args.ranking ?? null,
|
|
1522
|
+
verdict: args.verdict ?? null,
|
|
1523
|
+
tooThin: args.tooThin ?? [],
|
|
1524
|
+
standings: true,
|
|
1525
|
+
},
|
|
1526
|
+
};
|
|
1527
|
+
});
|
|
1421
1528
|
server.registerTool("show_comparison", {
|
|
1422
1529
|
title: "Show Comparison",
|
|
1423
1530
|
description: "Display a comparison you wrote after compare_posts fetched the first post and you fetched " +
|
|
@@ -1860,16 +1967,24 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1860
1967
|
// argument: the draft is the caller's own text, so the backend was only ever
|
|
1861
1968
|
// being paid to hold it up against a standard, and the standard is what
|
|
1862
1969
|
// comes back instead. Nothing here can fail, which is why nothing is caught.
|
|
1863
|
-
async (args) =>
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1970
|
+
async (args) => {
|
|
1971
|
+
const guidance = scoreDraftGuidance(args.draft, String(args.platform ?? ""));
|
|
1972
|
+
return {
|
|
1973
|
+
content: [{ type: "text", text: guidance }],
|
|
1974
|
+
structuredContent: {
|
|
1975
|
+
// The rubric IS this tool's answer — it fetches nothing and scores
|
|
1976
|
+
// nothing itself. In a text block it reached no host that renders
|
|
1977
|
+
// structuredContent, which made the tool return its own input and a
|
|
1978
|
+
// credit note. See runEvidence.
|
|
1979
|
+
guidance,
|
|
1980
|
+
draft: args.draft,
|
|
1981
|
+
platform: args.platform ?? "tiktok",
|
|
1982
|
+
// Same shape the free tools use, so a caller totting up a session's spend
|
|
1983
|
+
// does not have to special-case this one.
|
|
1984
|
+
mcpCredits: { cost: 0 },
|
|
1985
|
+
},
|
|
1986
|
+
};
|
|
1987
|
+
});
|
|
1873
1988
|
server.registerTool("repurpose_post", {
|
|
1874
1989
|
title: "Repurpose Post",
|
|
1875
1990
|
description: "The source post, its transcript and its stats, for you to rewrite for other surfaces — X " +
|
|
@@ -2186,7 +2301,7 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2186
2301
|
}
|
|
2187
2302
|
});
|
|
2188
2303
|
registerPrompts(server);
|
|
2189
|
-
// One store for both.
|
|
2304
|
+
// One store for both. track_creator keeps its "since I last looked" marker
|
|
2190
2305
|
// on the same watchlist entries, in its own field — two stores would mean a
|
|
2191
2306
|
// creator you watch and a creator you track were different people.
|
|
2192
2307
|
//
|
|
@@ -2782,7 +2782,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
2782
2782
|
/**
|
|
2783
2783
|
* The one line a competitor sweep or an underperformance check actually
|
|
2784
2784
|
* exists to produce — ratio to the creator's own median, and the verdict
|
|
2785
|
-
* that ratio earns.
|
|
2785
|
+
* that ratio earns. track_creator and why_did_this_underperform attach
|
|
2786
2786
|
* "standing" to every post they score (see performance.ts), and until this
|
|
2787
2787
|
* existed postCard rendered the same view/like/comment pills any gallery
|
|
2788
2788
|
* gets, so the computed comparison a person paid for never reached the
|
|
@@ -3661,6 +3661,64 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
3661
3661
|
if(d.posts&&Array.isArray(d.posts)){
|
|
3662
3662
|
if(!d.posts.length){app.innerHTML='<div class="empty-state fade-in"><div class="icon">🔍</div><div class="text">No posts found</div></div>';return;}
|
|
3663
3663
|
app.innerHTML=galleryWrap(d.posts.map(function(p){return postCard(p,false,true);}).join(""),d.posts.length)+pickBarHtml();initPicks();return;}
|
|
3664
|
+
// Standings: creators on one normalised axis. A table, because the question
|
|
3665
|
+
// is comparative — and every row carries the window it was scored over. A
|
|
3666
|
+
// hit rate whose denominator is invisible cannot be told apart from one
|
|
3667
|
+
// that means something, which is why these numbers were computed per
|
|
3668
|
+
// creator and never put side by side before (issue #30).
|
|
3669
|
+
if(Array.isArray(d.creators)&&(d.standings||d.tool==="compare_creators"||d.tool==="watchlist_standings")){
|
|
3670
|
+
var thin=Array.isArray(d.tooThin)?d.tooThin:[];
|
|
3671
|
+
var floor=typeof d.thinWindow==="number"?d.thinWindow:8;
|
|
3672
|
+
var above=typeof d.aboveRatio==="number"?d.aboveRatio:1.25;
|
|
3673
|
+
var met=esc(d.metric||"views");
|
|
3674
|
+
var rows=d.creators.map(function(c){
|
|
3675
|
+
var un=c.unavailable;
|
|
3676
|
+
// A window shorter than the floor, or one the model flagged, is drawn
|
|
3677
|
+
// unranked. Bottom of a table is a claim; unranked is the truth.
|
|
3678
|
+
var short=!un&&(thin.indexOf(String(c.handle))>=0||(typeof c.window==="number"&&c.window<floor));
|
|
3679
|
+
var med=c.baseline&&c.baseline.median!=null?fmtNum(c.baseline.median):"-";
|
|
3680
|
+
var hit=c.hitRate!=null?Math.round(c.hitRate*100)+"%":"-";
|
|
3681
|
+
var win=c.medianWinRatio!=null?(Math.round(c.medianWinRatio*100)/100)+"x":"-";
|
|
3682
|
+
var best=c.best&&(c.best.title||c.best.caption)?String(c.best.title||c.best.caption):"";
|
|
3683
|
+
return '<tr style="border-top:1px solid var(--border-soft)'+(un?";opacity:.6":"")+'">'
|
|
3684
|
+
+'<td style="padding:9px 10px;font-weight:600;white-space:nowrap">@'+esc(c.handle)
|
|
3685
|
+
+(c.platform?'<span style="font-weight:400;color:var(--muted)"> · '+esc(c.platform)+"</span>":"")
|
|
3686
|
+
+(short?'<span style="margin-left:6px;font-size:10px;font-weight:700;color:var(--warn)">too thin to rank</span>':"")
|
|
3687
|
+
+"</td>"
|
|
3688
|
+
+(un
|
|
3689
|
+
?'<td colspan="4" style="padding:9px 10px;color:var(--muted);font-size:12.5px">'+esc(String(un))+"</td>"
|
|
3690
|
+
:'<td style="padding:9px 10px;text-align:right;font-variant-numeric:tabular-nums">'+fmtNum(c.window||0)+"</td>"
|
|
3691
|
+
+'<td style="padding:9px 10px;text-align:right;font-variant-numeric:tabular-nums">'+med+"</td>"
|
|
3692
|
+
+'<td style="padding:9px 10px;text-align:right;font-variant-numeric:tabular-nums">'+hit+"</td>"
|
|
3693
|
+
+'<td style="padding:9px 10px;text-align:right;font-variant-numeric:tabular-nums">'+win+"</td>")
|
|
3694
|
+
+"</tr>"
|
|
3695
|
+
+(best&&!un
|
|
3696
|
+
?'<tr><td colspan="5" style="padding:0 10px 9px 10px;color:var(--muted);font-size:12px">best: '+esc(best.slice(0,120))+"</td></tr>"
|
|
3697
|
+
:"");
|
|
3698
|
+
}).join("");
|
|
3699
|
+
app.innerHTML='<div class="card card-wide fade-in"><div class="card-body">'
|
|
3700
|
+
+'<div style="font-size:16px;font-weight:700">Standings</div>'
|
|
3701
|
+
+'<div class="faint" style="font-size:11.5px;color:var(--muted);margin:2px 0 10px">'
|
|
3702
|
+
+d.creators.length+" creator"+(d.creators.length===1?"":"s")+" · each against their OWN median "+met
|
|
3703
|
+
+(d.watching!=null?" · watchlist of "+fmtNum(d.watching):"")+"</div>"
|
|
3704
|
+
+(d.ranking?'<div style="font-size:12.5px;margin:0 0 8px">Ordered on: '+esc(String(d.ranking))+"</div>":"")
|
|
3705
|
+
+'<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:13px">'
|
|
3706
|
+
+'<thead><tr style="text-align:left;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.05em">'
|
|
3707
|
+
+'<th style="padding:0 10px 6px">Creator</th>'
|
|
3708
|
+
+'<th style="padding:0 10px 6px;text-align:right">Window</th>'
|
|
3709
|
+
+'<th style="padding:0 10px 6px;text-align:right">Their median</th>'
|
|
3710
|
+
+'<th style="padding:0 10px 6px;text-align:right">Beat it</th>'
|
|
3711
|
+
+'<th style="padding:0 10px 6px;text-align:right">By</th>'
|
|
3712
|
+
+"</tr></thead><tbody>"+rows+"</tbody></table></div>"
|
|
3713
|
+
+(d.verdict?'<div style="font-size:13.5px;line-height:1.6;margin-top:12px">'+esc(String(d.verdict))+"</div>":"")
|
|
3714
|
+
+'<div class="faint" style="font-size:11.5px;color:var(--muted);margin-top:10px;line-height:1.6">'
|
|
3715
|
+
+'"Beat it" is the share of the window at or above '+above+"x that creator's own median. "
|
|
3716
|
+
+"A raw view count mostly measures follower count, so nothing here compares raw numbers. "
|
|
3717
|
+
+"A window under "+floor+" posts is one post either way, and no ratio here says whether a "
|
|
3718
|
+
+"baseline is itself moving — that needs a second point in time."
|
|
3719
|
+
+"</div></div></div>";
|
|
3720
|
+
return;}
|
|
3721
|
+
|
|
3664
3722
|
// Creators
|
|
3665
3723
|
if(d.creators&&Array.isArray(d.creators)){
|
|
3666
3724
|
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;}
|
|
@@ -3924,8 +3982,18 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
3924
3982
|
|
|
3925
3983
|
// Single post
|
|
3926
3984
|
if(d.post||d.platform){app.innerHTML=postCard(d.post||d,true);return;}
|
|
3927
|
-
// Fallback
|
|
3928
|
-
|
|
3985
|
+
// Fallback. The guidance field is prose written for the model, not data —
|
|
3986
|
+
// dumped into the JSON block it reads as a wall of escaped text, and it is
|
|
3987
|
+
// the longest string in most payloads. Drawn above the block and dropped
|
|
3988
|
+
// from it. This is also what finally gives score_draft a view worth
|
|
3989
|
+
// looking at: its rubric IS its answer, and the card used to show the
|
|
3990
|
+
// echoed draft and a credit cost with the rubric nowhere on screen.
|
|
3991
|
+
var rest=d,lead="";
|
|
3992
|
+
if(d&&typeof d==="object"&&typeof d.guidance==="string"){
|
|
3993
|
+
lead='<div class="fade-in" style="white-space:pre-wrap;line-height:1.5;margin-bottom:12px">'+esc(d.guidance)+"</div>";
|
|
3994
|
+
rest={};for(var gk in d){if(gk!=="guidance")rest[gk]=d[gk];}
|
|
3995
|
+
}
|
|
3996
|
+
app.innerHTML=lead+'<div class="json-block fade-in">'+esc(JSON.stringify(rest,null,2))+"</div>";
|
|
3929
3997
|
setTimeout(reportSize,50);
|
|
3930
3998
|
}
|
|
3931
3999
|
|
package/dist/shared/watchlist.js
CHANGED
|
@@ -33,11 +33,18 @@
|
|
|
33
33
|
* registers, and only one registration under a name can win — but renaming
|
|
34
34
|
* either side would break whichever hosts had already learned the old one,
|
|
35
35
|
* for a tool whose whole promise is "what you called before still works". So
|
|
36
|
-
* nothing about the surface moved. `watch_creator`,
|
|
37
|
-
* `
|
|
36
|
+
* nothing about the surface moved for THAT change. `watch_creator`,
|
|
37
|
+
* `unwatch_creator` and `catch_up_watchlist` keep their names, arguments and
|
|
38
38
|
* behaviour; only the bytes moved, and the backend's own twins stay
|
|
39
39
|
* unregistered here because they would be a second, disconnected list.
|
|
40
40
|
*
|
|
41
|
+
* `track_creator` is the one exception, and for an unrelated reason: it was
|
|
42
|
+
* `track_competitor`, and that name was measured to be unreachable for the
|
|
43
|
+
* way most people ask for it — see its registration in jobs.ts. The backend
|
|
44
|
+
* still registers `track_competitor` for its own dashboard copilot, which is
|
|
45
|
+
* the ordinary state of two repos with two release cycles rather than a
|
|
46
|
+
* collision: the names no longer overlap, so nothing has to win.
|
|
47
|
+
*
|
|
41
48
|
* The stores above are still live and still matter. They are the fallback for
|
|
42
49
|
* an account with no workspace or a backend too old to have the tools, and
|
|
43
50
|
* they are what the one-time migration reads from. Deleting one because "the
|
|
@@ -155,9 +162,10 @@ export class FileWatchStore {
|
|
|
155
162
|
* and on ChatGPT had two, and a server-side scheduler could read neither.
|
|
156
163
|
*
|
|
157
164
|
* The swap is deliberately at `WatchStore` rather than at the tool layer.
|
|
158
|
-
* `watch_creator`, `unwatch_creator
|
|
159
|
-
*
|
|
160
|
-
*
|
|
165
|
+
* `watch_creator`, `unwatch_creator` and `catch_up_watchlist` keep their
|
|
166
|
+
* names, their arguments and their behaviour; only where the bytes live
|
|
167
|
+
* changes. (`track_creator` was later renamed from `track_competitor` for a
|
|
168
|
+
* reason that has nothing to do with storage — see above.) That is the whole reason the collision
|
|
161
169
|
* with the backend's identically-named tools was not worth solving by
|
|
162
170
|
* renaming anything: a host that already learned `watch_creator` keeps
|
|
163
171
|
* working, and it now writes somewhere the other host can see.
|
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.29",
|
|
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": {
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
"test:e2e:visual": "playwright test tests/e2e/agentic-visual.e2e.ts tests/e2e/agentic-visual-full-app.e2e.ts",
|
|
39
39
|
"test:e2e-smoke": "bash scripts/run-mechanical-e2e-smoke.sh",
|
|
40
40
|
"test:e2e-smoke:fixture": "NOOTICR_E2E_BACKEND=fixture bash scripts/run-mechanical-e2e-smoke.sh",
|
|
41
|
-
"test:
|
|
41
|
+
"test:quests": "bash scripts/run-quests.sh",
|
|
42
42
|
"contract:host": "sh scripts/host-contract.sh",
|
|
43
|
-
"
|
|
43
|
+
"chain:map": "bash scripts/run-chain-map.sh",
|
|
44
|
+
"verify": "npm run build && npx tsc --noEmit && (cd cloudflare && npx tsc --noEmit) && npx vitest run && npm run contract:host && npm run chain:map && npm run conformance:mcpjam && npx playwright test",
|
|
44
45
|
"contract:manifest": "node scripts/contract-check.mjs --from=../nooticr-server/platform-capabilities.json",
|
|
45
46
|
"test:mutation": "node scripts/mutation-guard.mjs",
|
|
46
47
|
"test:invariants": "node scripts/invariant-guard.mjs",
|