@byldd/seo-tools-plugin 1.0.0 → 1.0.2
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/dist/manifest.js +25 -35
- package/dist/manifest.js.map +4 -4
- package/dist/worker.js +92 -165
- package/dist/worker.js.map +4 -4
- package/package.json +4 -4
package/dist/manifest.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
// src/types/index.ts
|
|
2
|
+
var RerunStage = /* @__PURE__ */ ((RerunStage2) => {
|
|
3
|
+
RerunStage2["GEO"] = "geo";
|
|
4
|
+
RerunStage2["SCHEMA"] = "schema";
|
|
5
|
+
RerunStage2["OUTLINE"] = "outline";
|
|
6
|
+
RerunStage2["ENRICHMENT"] = "enrichment";
|
|
7
|
+
RerunStage2["INTENT"] = "intent";
|
|
8
|
+
RerunStage2["STRATEGY"] = "strategy";
|
|
9
|
+
return RerunStage2;
|
|
10
|
+
})(RerunStage || {});
|
|
11
|
+
|
|
1
12
|
// src/manifest.ts
|
|
2
13
|
var manifest = {
|
|
3
14
|
id: "byldd.seo-tools",
|
|
@@ -55,9 +66,9 @@ var manifest = {
|
|
|
55
66
|
},
|
|
56
67
|
tools: [
|
|
57
68
|
{
|
|
58
|
-
name: "
|
|
59
|
-
displayName: "
|
|
60
|
-
description: "
|
|
69
|
+
name: "startKeywordResearch",
|
|
70
|
+
displayName: "Start a New Keyword Research",
|
|
71
|
+
description: "ONLY entry point for new keyword research. Handles discovery AND enrichment in one pipeline. Returns a keywordSessionRef immediately \u2014 the job runs in the background. Research typically takes ~20-30s. Wait ~20 seconds, then poll getKeywordSessionStatus with that keywordSessionRef every 10 seconds until status is 'completed' or 'failed'. Max ~16 poll attempts (~3 minutes including the initial wait) before reporting timeout.",
|
|
61
72
|
parametersSchema: {
|
|
62
73
|
type: "object",
|
|
63
74
|
properties: {
|
|
@@ -69,7 +80,7 @@ var manifest = {
|
|
|
69
80
|
{
|
|
70
81
|
name: "getKeywordSessionStatus",
|
|
71
82
|
displayName: "Get Keyword Session Status",
|
|
72
|
-
description: "Poll the status of a KeywordSession started
|
|
83
|
+
description: "Poll the status of a KeywordSession started by startKeywordResearch. Wait ~20 seconds before the first poll (research takes ~20-30s), then call every 10 seconds. Stop when status is 'completed' or 'failed'. Max ~16 attempts (~3 min) \u2014 if still running after that, report timeout to the user and stop. When status is 'completed', the response contains the keywordSessionRef \u2014 the single id for this research run; pass it to clusterKeywords. Save it.",
|
|
73
84
|
parametersSchema: {
|
|
74
85
|
type: "object",
|
|
75
86
|
properties: {
|
|
@@ -79,33 +90,9 @@ var manifest = {
|
|
|
79
90
|
}
|
|
80
91
|
},
|
|
81
92
|
{
|
|
82
|
-
name: "
|
|
83
|
-
displayName: "Enrich Keywords",
|
|
84
|
-
description: "Use only to re-enrich an EXISTING session that was previously discovered. For new keyword research, use runKeywordResearch which handles everything end-to-end.",
|
|
85
|
-
parametersSchema: {
|
|
86
|
-
type: "object",
|
|
87
|
-
properties: {
|
|
88
|
-
keywordSessionRef: { type: "string" }
|
|
89
|
-
},
|
|
90
|
-
required: ["keywordSessionRef"]
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: "getKeywordSuggestions",
|
|
95
|
-
displayName: "Get Keyword Suggestions",
|
|
96
|
-
description: "List raw expansion keywords for a session WITHOUT triggering the costly enrich step.",
|
|
97
|
-
parametersSchema: {
|
|
98
|
-
type: "object",
|
|
99
|
-
properties: {
|
|
100
|
-
keywordSessionRef: { type: "string" }
|
|
101
|
-
},
|
|
102
|
-
required: ["keywordSessionRef"]
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: "listKeywordSessions",
|
|
93
|
+
name: "listKeywordResearchSessions",
|
|
107
94
|
displayName: "List Keyword Sessions",
|
|
108
|
-
description: "List all past keyword research sessions.",
|
|
95
|
+
description: "List all past keyword research sessions with a brief overview \u2014 seedKeyword, status, and keyword count only.",
|
|
109
96
|
parametersSchema: {
|
|
110
97
|
type: "object",
|
|
111
98
|
properties: {}
|
|
@@ -114,7 +101,7 @@ var manifest = {
|
|
|
114
101
|
{
|
|
115
102
|
name: "clusterKeywords",
|
|
116
103
|
displayName: "Cluster Keywords",
|
|
117
|
-
description: "Call this AFTER getKeywordSessionStatus returns 'completed'
|
|
104
|
+
description: "Call this AFTER getKeywordSessionStatus returns 'completed'. Pass the keywordSessionRef from that completed response (or from listKeywordResearchSessions). Returns a clusterId. Clustering can take ~20s to 1 min or more. Wait ~20 seconds, then poll getClusterStatus every 15 seconds until status is 'completed'. Max ~18 attempts (~5 min) before reporting timeout.",
|
|
118
105
|
parametersSchema: {
|
|
119
106
|
type: "object",
|
|
120
107
|
properties: {
|
|
@@ -126,7 +113,7 @@ var manifest = {
|
|
|
126
113
|
{
|
|
127
114
|
name: "getClusterStatus",
|
|
128
115
|
displayName: "Get Cluster Status",
|
|
129
|
-
description: "
|
|
116
|
+
description: "Poll the status of a clustering job. After clusterKeywords, wait ~20 seconds, then call every 15 seconds until status is 'completed' or 'failed'. Max ~18 attempts (~5 min) before reporting timeout. When completed, the response contains the full cluster plan \u2014 render it as a Markdown table immediately.",
|
|
130
117
|
parametersSchema: {
|
|
131
118
|
type: "object",
|
|
132
119
|
properties: {
|
|
@@ -138,7 +125,7 @@ var manifest = {
|
|
|
138
125
|
{
|
|
139
126
|
name: "listClusterSessions",
|
|
140
127
|
displayName: "List Cluster Sessions",
|
|
141
|
-
description: "List all past clustering sessions.",
|
|
128
|
+
description: "List all past clustering sessions with a brief overview \u2014 seedKeyword, status, and keyword count only.",
|
|
142
129
|
parametersSchema: {
|
|
143
130
|
type: "object",
|
|
144
131
|
properties: {}
|
|
@@ -147,7 +134,7 @@ var manifest = {
|
|
|
147
134
|
{
|
|
148
135
|
name: "generateContentBrief",
|
|
149
136
|
displayName: "Generate Content Brief",
|
|
150
|
-
description: "Call this once user approves a cluster. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'.
|
|
137
|
+
description: "Call this once user approves a cluster or passed only a keyword. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'. When source is 'cluster', clusterSessionRef is REQUIRED (the clusterId from a completed clusterKeywords run); for source 'standalone' omit clusterSessionRef and pass only keyword.",
|
|
151
138
|
parametersSchema: {
|
|
152
139
|
type: "object",
|
|
153
140
|
properties: {
|
|
@@ -190,7 +177,10 @@ var manifest = {
|
|
|
190
177
|
type: "object",
|
|
191
178
|
properties: {
|
|
192
179
|
briefId: { type: "string" },
|
|
193
|
-
stage: {
|
|
180
|
+
stage: {
|
|
181
|
+
type: "string",
|
|
182
|
+
enum: Object.values(RerunStage)
|
|
183
|
+
}
|
|
194
184
|
},
|
|
195
185
|
required: ["briefId", "stage"]
|
|
196
186
|
}
|
package/dist/manifest.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/manifest.ts"],
|
|
4
|
-
"sourcesContent": ["import type { PaperclipPluginManifestV1 } from \"@paperclipai/plugin-sdk\";\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: \"byldd.seo-tools\",\n apiVersion: 1,\n version: \"0.1.0\",\n displayName: \"Seo Tools\",\n description: \"A Paperclip plugin\",\n author: \"Plugin Author\",\n categories: [\"connector\"],\n capabilities: [\n \"agent.tools.register\",\n \"http.outbound\",\n \"ui.detailTab.register\",\n \"events.subscribe\",\n \"plugin.state.read\",\n \"plugin.state.write\",\n \"ui.dashboardWidget.register\",\n ],\n instanceConfigSchema: {\n type: \"object\",\n properties: {\n backendUrl: {\n type: \"string\",\n title: \"Backend URL\",\n description: \"Base URL of the SEO Byldd Server\",\n },\n authToken: {\n type: \"string\",\n title: \"Auth Token\",\n description: \"JWT auth token for the SEO Byldd Server\",\n },\n },\n required: [\"backendUrl\", \"authToken\"],\n },\n entrypoints: {\n worker: \"./dist/worker.js\",\n ui: \"./dist/ui\",\n },\n ui: {\n slots: [\n {\n type: \"dashboardWidget\",\n id: \"health-widget\",\n displayName: \"Seo Tools Health\",\n exportName: \"DashboardWidget\",\n },\n {\n type: \"detailTab\",\n id: \"keyword-research-tab\",\n displayName: \"Keyword Research\",\n exportName: \"KeywordResearchTab\",\n entityTypes: [\"issue\"],\n },\n ],\n },\n tools: [\n {\n name: \"runKeywordResearch\",\n displayName: \"Run Keyword Research\",\n description:\n \"Call this when user asks for keyword research or Keyword Planner. Starts an async job \u2014 returns a keywordSessionRef immediately. Research takes ~20-30s. Wait ~20 seconds, then poll getKeywordSessionStatus every 10 seconds until status is 'completed' or 'failed' (max ~16 attempts, ~3 min).\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keyword: { type: \"string\" },\n },\n required: [\"keyword\"],\n },\n },\n {\n name: \"getKeywordSessionStatus\",\n displayName: \"Get Keyword Session Status\",\n description:\n \"Poll the status of a KeywordSession started with runKeywordResearch. Wait ~20 seconds before the first poll, then call every 10 seconds until status is 'completed' or 'failed' (max ~16 attempts, ~3 min).\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"enrichKeywords\",\n displayName: \"Enrich Keywords\",\n description:\n \"Use only to re-enrich an EXISTING session that was previously discovered. For new keyword research, use runKeywordResearch which handles everything end-to-end.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"getKeywordSuggestions\",\n displayName: \"Get Keyword Suggestions\",\n description:\n \"List raw expansion keywords for a session WITHOUT triggering the costly enrich step.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"listKeywordSessions\",\n displayName: \"List Keyword Sessions\",\n description: \"List all past keyword research sessions.\",\n parametersSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"clusterKeywords\",\n displayName: \"Cluster Keywords\",\n description:\n \"Call this AFTER getKeywordSessionStatus returns 'completed' (and the user approves). Group enriched keywords into topical clusters. Returns a clusterId. Clustering can take ~20s to 1 min or more. Wait ~20 seconds, then poll getClusterStatus every 15 seconds until status is 'completed' (max ~18 attempts, ~5 min).\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"getClusterStatus\",\n displayName: \"Get Cluster Status\",\n description:\n \"Fetch status and results of a clustering session. After clusterKeywords, wait ~20 seconds, then poll every 15 seconds until status is completed (max ~18 attempts, ~5 min).\",\n parametersSchema: {\n type: \"object\",\n properties: {\n clusterId: { type: \"string\" },\n },\n required: [\"clusterId\"],\n },\n },\n {\n name: \"listClusterSessions\",\n displayName: \"List Cluster Sessions\",\n description: \"List all past clustering sessions.\",\n parametersSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"generateContentBrief\",\n displayName: \"Generate Content Brief\",\n description:\n \"Call this once user approves a cluster. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'. Start the async content-brief pipeline.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n source: { type: \"string\", enum: [\"cluster\", \"standalone\"] },\n keyword: { type: \"string\" },\n clusterSessionRef: { type: \"string\" },\n isSelfReferentialListicle: { type: \"boolean\" },\n },\n required: [\"source\", \"keyword\"],\n },\n },\n {\n name: \"getBriefStatus\",\n displayName: \"Get Brief Status\",\n description:\n \"Fetch the current state of a brief. Poll this until status is COMPLETED.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n },\n required: [\"briefId\"],\n },\n },\n {\n name: \"listContentBriefs\",\n displayName: \"List Content Briefs\",\n description: \"List content briefs, optionally filtered by cluster.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n clusterSessionRef: { type: \"string\" },\n },\n },\n },\n {\n name: \"rerunBriefStage\",\n displayName: \"Rerun Brief Stage\",\n description: \"Regenerate a single stage of the brief pipeline.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n stage: { type: \"string\" },\n },\n required: [\"briefId\", \"stage\"],\n },\n },\n {\n name: \"attachProofPoints\",\n displayName: \"Attach Proof Points\",\n description: \"Inject business-specific proof points into a brief.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n pricingRanges: { type: \"string\" },\n timelines: { type: \"string\" },\n clientOutcomes: { type: \"array\", items: { type: \"string\" } },\n methodology: { type: \"string\" },\n },\n required: [\"briefId\"],\n },\n },\n ],\n};\n\nexport default manifest;\n"],
|
|
5
|
-
"mappings": ";
|
|
6
|
-
"names": []
|
|
3
|
+
"sources": ["../src/types/index.ts", "../src/manifest.ts"],
|
|
4
|
+
"sourcesContent": ["// Tool param types \u2014 mirror parametersSchema definitions in worker.ts\nexport interface RunKeywordResearchParams {\n keyword: string;\n}\n\nexport interface GetKeywordSessionStatusParams {\n keywordSessionRef: string;\n}\n\nexport interface ClusterKeywordsParams {\n keywordSessionRef: string;\n}\n\nexport interface GetClusterStatusParams {\n clusterId: string;\n}\n\nexport enum BriefSource {\n CLUSTER = \"cluster\",\n STANDALONE = \"standalone\",\n}\n\nexport interface GenerateContentBriefParams {\n source: BriefSource;\n keyword: string;\n clusterSessionRef?: string;\n isSelfReferentialListicle?: boolean;\n}\n\nexport interface GetBriefStatusParams {\n briefId: string;\n}\n\nexport interface ListContentBriefsParams {\n clusterSessionRef?: string;\n}\n\nexport interface RerunBriefStageParams {\n briefId: string;\n stage: RerunStage;\n}\n\nexport interface AttachProofPointsParams {\n briefId: string;\n pricingRanges?: string;\n timelines?: string;\n clientOutcomes?: string[];\n methodology?: string;\n}\n\n// Keyword session status\nexport enum KeywordSessionStatus {\n PENDING = \"pending\",\n DISCOVERING = \"discovering\",\n ENRICHING = \"enriching\",\n COMPLETED = \"completed\",\n FAILED = \"failed\",\n}\n\n// Raw KeywordSession item as returned by GET /api/keyword-research/.\n// Mongo ObjectIds and Dates are serialized to strings over JSON.\nexport interface KeywordSessionApiItem {\n _id: string;\n seedKeyword: string;\n status: KeywordSessionStatus;\n keywordCount?: number;\n createdAt: string;\n}\n\n// Output of listKeywordResearchSessions \u2014 one summary row per session.\nexport interface KeywordResearchSessionSummary {\n keywordSessionRef: string;\n seedKeyword: string;\n status: KeywordSessionStatus;\n keywordCount: number;\n createdAt: string;\n}\n\n// Brief pipeline rerun stages\nexport enum RerunStage {\n GEO = \"geo\",\n SCHEMA = \"schema\",\n OUTLINE = \"outline\",\n ENRICHMENT = \"enrichment\",\n INTENT = \"intent\",\n STRATEGY = \"strategy\",\n}\n\n// Clustering session status\nexport enum ClusterStatus {\n PENDING = \"pending\",\n FETCHING_SERPS = \"fetching_serps\",\n PROCESSING = \"processing\",\n ARCHITECTING = \"architecting\",\n COMPLETED = \"completed\",\n FAILED = \"failed\",\n}\n\n// Raw ClusterSession item as returned by GET /api/clustering/.\n// Mongo ObjectIds and Dates are serialized to strings over JSON.\nexport interface ClusterSessionApiItem {\n _id: string;\n seedKeyword?: string;\n status: ClusterStatus;\n keywordCount?: number;\n createdAt: string;\n}\n\n// Output of listClusterSessions \u2014 one summary row per session.\nexport interface ClusterSessionSummary {\n clusterId: string;\n seedKeyword?: string;\n status: ClusterStatus;\n keywordCount: number;\n createdAt: string;\n}\n\n// Raw ContentBrief item as returned by GET /api/content-brief/ (list).\n// Mongo ObjectIds and Dates are serialized to strings over JSON.\nexport interface ContentBriefApiItem {\n _id: string;\n keyword: string;\n status: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Output of listContentBriefs \u2014 one summary row per brief.\nexport interface ContentBriefSummary {\n briefId: string;\n keyword: string;\n status: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Cluster plan shapes\nexport interface ClusterPost {\n keyword: string;\n volume: number;\n intent: string;\n pageType: string;\n title: string;\n}\n\nexport interface ClusterGroup {\n name: string;\n posts: ClusterPost[];\n}\n\nexport interface ClusterPillar {\n keyword: string;\n volume: number;\n intent: string;\n pageType: string;\n title: string;\n}\n\nexport interface ClusterLandingPage {\n keyword: string;\n pageType: string;\n urlSlug: string;\n}\n\nexport interface ClusterInternalLinking {\n pillarLinksTo: string[];\n spokesLinkTo: string[];\n}\n\nexport interface ClusterArchitecture {\n pillar: ClusterPillar | null;\n clusters: ClusterGroup[];\n landingPage: ClusterLandingPage | null;\n internalLinking: ClusterInternalLinking | null;\n}\n\n// Brief writer-view shapes \u2014 the trimmed projection getBriefStatus returns to\n// the agent. Only the writer-facing fields survive; raw SERP/enrichment/schema\n// JSON, EEAT, and ops/meta are dropped here so RENDERING.md never has to.\nexport interface BriefOutlineSectionView {\n heading: string;\n level: number;\n wordCount: number;\n importance: string | null;\n format: string;\n guidance: string;\n subSections?: BriefOutlineSectionView[];\n}\n\nexport interface BriefInternalLinkView {\n anchor: string;\n targetUrl: string;\n reason: string;\n}\n\nexport interface BriefClusterSpokeView {\n keyword: string;\n title: string;\n url: string;\n}\n\nexport interface BriefWriterView {\n status: string;\n // Q1 \u2014 page identity\n pageType: string;\n intent: string;\n targetKeyword: string;\n targetAudience?: string;\n priorityScore?: number;\n priorityTier?: string;\n clusterPosition?: string;\n recommendedTitle?: string;\n metaDescription?: string;\n urlSlug?: string;\n // Q2 \u2014 coverage & depth\n supportingKeywords: string[];\n mustAnswer: string[];\n niceToAnswer: string[];\n targetWordCount?: number;\n competitorAvgWordCount?: number;\n outline: BriefOutlineSectionView[];\n // coverage checklist material (derived from coverageMatrix)\n requiredTopics: string[];\n contentGaps: string[];\n differentiatorOpportunities: string[];\n faq: string[];\n // Q3 \u2014 citability\n tldrBlock?: {\n text: string;\n wordCount: number;\n placement: string;\n fsTarget: boolean;\n };\n objectiveLanguageInstruction?: string;\n requiredSignals: string[];\n schemaRequired: string[];\n // Q4 \u2014 differentiation\n contentAngle?: string;\n competitorGap?: string;\n mustOwnTopics: string[];\n uniqueAssets: string[];\n proofPointsToInclude: string[];\n competitorBlindSpots: string[];\n competitorPagesToReference: string[];\n // Q5 \u2014 site connection\n internalLinks: BriefInternalLinkView[];\n internalLinksFrom: string[];\n isSelfReferentialListicle: boolean;\n clusterContext?: {\n pillarTitle: string;\n pillarUrl: string;\n spokes: BriefClusterSpokeView[];\n };\n // kept for the agent's polling / Manager report \u2014 not for the writer doc\n flags: { code: string; note?: string }[];\n}\n", "import type { PaperclipPluginManifestV1 } from \"@paperclipai/plugin-sdk\";\nimport { RerunStage } from \"./types/index.js\";\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: \"byldd.seo-tools\",\n apiVersion: 1,\n version: \"0.1.0\",\n displayName: \"Seo Tools\",\n description: \"A Paperclip plugin\",\n author: \"Plugin Author\",\n categories: [\"connector\"],\n capabilities: [\n \"agent.tools.register\",\n \"http.outbound\",\n \"ui.detailTab.register\",\n \"events.subscribe\",\n \"plugin.state.read\",\n \"plugin.state.write\",\n \"ui.dashboardWidget.register\",\n ],\n instanceConfigSchema: {\n type: \"object\",\n properties: {\n backendUrl: {\n type: \"string\",\n title: \"Backend URL\",\n description: \"Base URL of the SEO Byldd Server\",\n },\n authToken: {\n type: \"string\",\n title: \"Auth Token\",\n description: \"JWT auth token for the SEO Byldd Server\",\n },\n },\n required: [\"backendUrl\", \"authToken\"],\n },\n entrypoints: {\n worker: \"./dist/worker.js\",\n ui: \"./dist/ui\",\n },\n ui: {\n slots: [\n {\n type: \"dashboardWidget\",\n id: \"health-widget\",\n displayName: \"Seo Tools Health\",\n exportName: \"DashboardWidget\",\n },\n {\n type: \"detailTab\",\n id: \"keyword-research-tab\",\n displayName: \"Keyword Research\",\n exportName: \"KeywordResearchTab\",\n entityTypes: [\"issue\"],\n },\n ],\n },\n tools: [\n {\n name: \"startKeywordResearch\",\n displayName: \"Start a New Keyword Research\",\n description:\n \"ONLY entry point for new keyword research. Handles discovery AND enrichment in one pipeline. Returns a keywordSessionRef immediately \u2014 the job runs in the background. Research typically takes ~20-30s. Wait ~20 seconds, then poll getKeywordSessionStatus with that keywordSessionRef every 10 seconds until status is 'completed' or 'failed'. Max ~16 poll attempts (~3 minutes including the initial wait) before reporting timeout.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keyword: { type: \"string\" },\n },\n required: [\"keyword\"],\n },\n },\n {\n name: \"getKeywordSessionStatus\",\n displayName: \"Get Keyword Session Status\",\n description:\n \"Poll the status of a KeywordSession started by startKeywordResearch. Wait ~20 seconds before the first poll (research takes ~20-30s), then call every 10 seconds. Stop when status is 'completed' or 'failed'. Max ~16 attempts (~3 min) \u2014 if still running after that, report timeout to the user and stop. When status is 'completed', the response contains the keywordSessionRef \u2014 the single id for this research run; pass it to clusterKeywords. Save it.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"listKeywordResearchSessions\",\n displayName: \"List Keyword Sessions\",\n description:\n \"List all past keyword research sessions with a brief overview \u2014 seedKeyword, status, and keyword count only.\",\n parametersSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"clusterKeywords\",\n displayName: \"Cluster Keywords\",\n description:\n \"Call this AFTER getKeywordSessionStatus returns 'completed'. Pass the keywordSessionRef from that completed response (or from listKeywordResearchSessions). Returns a clusterId. Clustering can take ~20s to 1 min or more. Wait ~20 seconds, then poll getClusterStatus every 15 seconds until status is 'completed'. Max ~18 attempts (~5 min) before reporting timeout.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n keywordSessionRef: { type: \"string\" },\n },\n required: [\"keywordSessionRef\"],\n },\n },\n {\n name: \"getClusterStatus\",\n displayName: \"Get Cluster Status\",\n description:\n \"Poll the status of a clustering job. After clusterKeywords, wait ~20 seconds, then call every 15 seconds until status is 'completed' or 'failed'. Max ~18 attempts (~5 min) before reporting timeout. When completed, the response contains the full cluster plan \u2014 render it as a Markdown table immediately.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n clusterId: { type: \"string\" },\n },\n required: [\"clusterId\"],\n },\n },\n {\n name: \"listClusterSessions\",\n displayName: \"List Cluster Sessions\",\n description:\n \"List all past clustering sessions with a brief overview \u2014 seedKeyword, status, and keyword count only.\",\n parametersSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"generateContentBrief\",\n displayName: \"Generate Content Brief\",\n description:\n \"Call this once user approves a cluster or passed only a keyword. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'. When source is 'cluster', clusterSessionRef is REQUIRED (the clusterId from a completed clusterKeywords run); for source 'standalone' omit clusterSessionRef and pass only keyword.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n source: { type: \"string\", enum: [\"cluster\", \"standalone\"] },\n keyword: { type: \"string\" },\n clusterSessionRef: { type: \"string\" },\n isSelfReferentialListicle: { type: \"boolean\" },\n },\n required: [\"source\", \"keyword\"],\n },\n },\n {\n name: \"getBriefStatus\",\n displayName: \"Get Brief Status\",\n description:\n \"Fetch the current state of a brief. Poll this until status is COMPLETED.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n },\n required: [\"briefId\"],\n },\n },\n {\n name: \"listContentBriefs\",\n displayName: \"List Content Briefs\",\n description: \"List content briefs, optionally filtered by cluster.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n clusterSessionRef: { type: \"string\" },\n },\n },\n },\n {\n name: \"rerunBriefStage\",\n displayName: \"Rerun Brief Stage\",\n description: \"Regenerate a single stage of the brief pipeline.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n stage: {\n type: \"string\",\n enum: Object.values(RerunStage),\n },\n },\n required: [\"briefId\", \"stage\"],\n },\n },\n {\n name: \"attachProofPoints\",\n displayName: \"Attach Proof Points\",\n description: \"Inject business-specific proof points into a brief.\",\n parametersSchema: {\n type: \"object\",\n properties: {\n briefId: { type: \"string\" },\n pricingRanges: { type: \"string\" },\n timelines: { type: \"string\" },\n clientOutcomes: { type: \"array\", items: { type: \"string\" } },\n methodology: { type: \"string\" },\n },\n required: [\"briefId\"],\n },\n },\n ],\n};\n\nexport default manifest;\n"],
|
|
5
|
+
"mappings": ";AA+EO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,SAAM;AACN,EAAAA,YAAA,YAAS;AACT,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,YAAS;AACT,EAAAA,YAAA,cAAW;AAND,SAAAA;AAAA,GAAA;;;AC5EZ,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,WAAW;AAAA,EACxB,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,YAAY;AAAA,MACV,YAAY;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA,WAAW;AAAA,QACT,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,cAAc,WAAW;AAAA,EACtC;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,aAAa,CAAC,OAAO;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,SAAS,EAAE,MAAM,SAAS;AAAA,QAC5B;AAAA,QACA,UAAU,CAAC,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,mBAAmB,EAAE,MAAM,SAAS;AAAA,QACtC;AAAA,QACA,UAAU,CAAC,mBAAmB;AAAA,MAChC;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY,CAAC;AAAA,MACf;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,mBAAmB,EAAE,MAAM,SAAS;AAAA,QACtC;AAAA,QACA,UAAU,CAAC,mBAAmB;AAAA,MAChC;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,WAAW,EAAE,MAAM,SAAS;AAAA,QAC9B;AAAA,QACA,UAAU,CAAC,WAAW;AAAA,MACxB;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY,CAAC;AAAA,MACf;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,QAAQ,EAAE,MAAM,UAAU,MAAM,CAAC,WAAW,YAAY,EAAE;AAAA,UAC1D,SAAS,EAAE,MAAM,SAAS;AAAA,UAC1B,mBAAmB,EAAE,MAAM,SAAS;AAAA,UACpC,2BAA2B,EAAE,MAAM,UAAU;AAAA,QAC/C;AAAA,QACA,UAAU,CAAC,UAAU,SAAS;AAAA,MAChC;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,SAAS,EAAE,MAAM,SAAS;AAAA,QAC5B;AAAA,QACA,UAAU,CAAC,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aAAa;AAAA,MACb,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,mBAAmB,EAAE,MAAM,SAAS;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aAAa;AAAA,MACb,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,SAAS,EAAE,MAAM,SAAS;AAAA,UAC1B,OAAO;AAAA,YACL,MAAM;AAAA,YACN,MAAM,OAAO,OAAO,UAAU;AAAA,UAChC;AAAA,QACF;AAAA,QACA,UAAU,CAAC,WAAW,OAAO;AAAA,MAC/B;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aAAa;AAAA,MACb,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,YAAY;AAAA,UACV,SAAS,EAAE,MAAM,SAAS;AAAA,UAC1B,eAAe,EAAE,MAAM,SAAS;AAAA,UAChC,WAAW,EAAE,MAAM,SAAS;AAAA,UAC5B,gBAAgB,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,UAC3D,aAAa,EAAE,MAAM,SAAS;AAAA,QAChC;AAAA,QACA,UAAU,CAAC,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
|
|
6
|
+
"names": ["RerunStage"]
|
|
7
7
|
}
|
package/dist/worker.js
CHANGED
|
@@ -9773,88 +9773,16 @@ function startWorkerRpcHost(options) {
|
|
|
9773
9773
|
};
|
|
9774
9774
|
}
|
|
9775
9775
|
|
|
9776
|
-
// src/
|
|
9777
|
-
var
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
};
|
|
9786
|
-
function toBriefWriterView(d) {
|
|
9787
|
-
const brief = d.brief ?? {};
|
|
9788
|
-
const coverage = d.coverageMatrix ?? {};
|
|
9789
|
-
const intentSat = d.searchIntentSatisfaction ?? {};
|
|
9790
|
-
const diff = d.differentiationStrategy ?? {};
|
|
9791
|
-
const schemaBundle = d.schemaBundle ?? {};
|
|
9792
|
-
const mapOutline = (s) => ({
|
|
9793
|
-
heading: s.heading,
|
|
9794
|
-
level: s.level,
|
|
9795
|
-
wordCount: s.wordCount,
|
|
9796
|
-
importance: s.importance ?? null,
|
|
9797
|
-
format: s.formatNotes,
|
|
9798
|
-
guidance: s.keywordGuidance,
|
|
9799
|
-
...Array.isArray(s.subSections) && s.subSections.length ? { subSections: s.subSections.map(mapOutline) } : {}
|
|
9800
|
-
});
|
|
9801
|
-
return {
|
|
9802
|
-
status: d.status,
|
|
9803
|
-
pageType: d.template,
|
|
9804
|
-
intent: d.intent,
|
|
9805
|
-
targetKeyword: d.keyword,
|
|
9806
|
-
targetAudience: d.targetAudience,
|
|
9807
|
-
priorityScore: d.priorityScore,
|
|
9808
|
-
priorityTier: d.priorityTier,
|
|
9809
|
-
clusterPosition: d.clusterPosition,
|
|
9810
|
-
recommendedTitle: brief.title,
|
|
9811
|
-
metaDescription: brief.metaDescription,
|
|
9812
|
-
urlSlug: brief.urlSlug,
|
|
9813
|
-
supportingKeywords: d.supportingKeywords ?? [],
|
|
9814
|
-
mustAnswer: intentSat.mustAnswer ?? [],
|
|
9815
|
-
niceToAnswer: intentSat.niceToAnswer ?? [],
|
|
9816
|
-
targetWordCount: brief.targetWordCount,
|
|
9817
|
-
competitorAvgWordCount: brief.competitorAvgWordCount,
|
|
9818
|
-
outline: (brief.outline ?? []).map(mapOutline),
|
|
9819
|
-
requiredTopics: (coverage.requiredTopics ?? []).map((t) => t.topic),
|
|
9820
|
-
contentGaps: coverage.contentGaps ?? [],
|
|
9821
|
-
differentiatorOpportunities: coverage.differentiatorOpportunities ?? [],
|
|
9822
|
-
faq: (brief.faq ?? []).map((f) => f.q),
|
|
9823
|
-
tldrBlock: d.tldrBlock ? {
|
|
9824
|
-
text: d.tldrBlock.text,
|
|
9825
|
-
wordCount: d.tldrBlock.wordCount,
|
|
9826
|
-
placement: d.tldrBlock.placement,
|
|
9827
|
-
fsTarget: d.tldrBlock.fsTarget
|
|
9828
|
-
} : void 0,
|
|
9829
|
-
objectiveLanguageInstruction: d.objectiveLanguageInstruction,
|
|
9830
|
-
requiredSignals: d.geo?.requiredSignals ?? [],
|
|
9831
|
-
schemaRequired: Object.keys(SCHEMA_KEY_LABELS).filter((k) => schemaBundle[k]).map((k) => SCHEMA_KEY_LABELS[k]),
|
|
9832
|
-
contentAngle: d.contentAngle,
|
|
9833
|
-
competitorGap: d.competitorGap,
|
|
9834
|
-
mustOwnTopics: diff.mustOwnTopics ?? [],
|
|
9835
|
-
uniqueAssets: diff.uniqueAssets ?? [],
|
|
9836
|
-
proofPointsToInclude: diff.proofPointsToInclude ?? [],
|
|
9837
|
-
competitorBlindSpots: diff.competitorBlindSpots ?? [],
|
|
9838
|
-
competitorPagesToReference: d.competitorPagesToReference ?? [],
|
|
9839
|
-
internalLinks: (brief.internalLinks ?? []).map((l) => ({
|
|
9840
|
-
anchor: l.anchor,
|
|
9841
|
-
targetUrl: l.targetUrl,
|
|
9842
|
-
reason: l.reason
|
|
9843
|
-
})),
|
|
9844
|
-
internalLinksFrom: brief.internalLinksFrom ?? [],
|
|
9845
|
-
isSelfReferentialListicle: Boolean(d.isSelfReferentialListicle),
|
|
9846
|
-
clusterContext: d.clusterContext ? {
|
|
9847
|
-
pillarTitle: d.clusterContext.pillarTitle,
|
|
9848
|
-
pillarUrl: d.clusterContext.pillarUrl,
|
|
9849
|
-
spokes: (d.clusterContext.spokes ?? []).map((s) => ({
|
|
9850
|
-
keyword: s.keyword,
|
|
9851
|
-
title: s.title,
|
|
9852
|
-
url: s.url
|
|
9853
|
-
}))
|
|
9854
|
-
} : void 0,
|
|
9855
|
-
flags: (d.flags ?? []).map((f) => ({ code: f.code, note: f.note }))
|
|
9856
|
-
};
|
|
9857
|
-
}
|
|
9776
|
+
// src/types/index.ts
|
|
9777
|
+
var RerunStage = /* @__PURE__ */ ((RerunStage2) => {
|
|
9778
|
+
RerunStage2["GEO"] = "geo";
|
|
9779
|
+
RerunStage2["SCHEMA"] = "schema";
|
|
9780
|
+
RerunStage2["OUTLINE"] = "outline";
|
|
9781
|
+
RerunStage2["ENRICHMENT"] = "enrichment";
|
|
9782
|
+
RerunStage2["INTENT"] = "intent";
|
|
9783
|
+
RerunStage2["STRATEGY"] = "strategy";
|
|
9784
|
+
return RerunStage2;
|
|
9785
|
+
})(RerunStage || {});
|
|
9858
9786
|
|
|
9859
9787
|
// src/worker.ts
|
|
9860
9788
|
var plugin = definePlugin({
|
|
@@ -9862,6 +9790,8 @@ var plugin = definePlugin({
|
|
|
9862
9790
|
const config = await ctx.config.get();
|
|
9863
9791
|
const BASE = config.backendUrl;
|
|
9864
9792
|
const AUTH_TOKEN = config.authToken;
|
|
9793
|
+
const API_PREFIX2 = "/api";
|
|
9794
|
+
const apiRoot = `${BASE}${API_PREFIX2}`;
|
|
9865
9795
|
ctx.logger.info("Loaded config on startup", {
|
|
9866
9796
|
BACKEND_URL: BASE,
|
|
9867
9797
|
hasApiToken: !!AUTH_TOKEN
|
|
@@ -9869,7 +9799,7 @@ var plugin = definePlugin({
|
|
|
9869
9799
|
const api = async (path2, method = "GET", body) => {
|
|
9870
9800
|
ctx.logger.info(`pluggin called - ${method} ${path2}`);
|
|
9871
9801
|
try {
|
|
9872
|
-
const res = await ctx.http.fetch(`${
|
|
9802
|
+
const res = await ctx.http.fetch(`${apiRoot}${path2}`, {
|
|
9873
9803
|
method,
|
|
9874
9804
|
headers: {
|
|
9875
9805
|
"Content-Type": "application/json",
|
|
@@ -9894,9 +9824,9 @@ var plugin = definePlugin({
|
|
|
9894
9824
|
}
|
|
9895
9825
|
};
|
|
9896
9826
|
ctx.tools.register(
|
|
9897
|
-
"
|
|
9827
|
+
"startKeywordResearch",
|
|
9898
9828
|
{
|
|
9899
|
-
displayName: "
|
|
9829
|
+
displayName: "Start a New Keyword Research",
|
|
9900
9830
|
description: "ONLY entry point for new keyword research. Handles discovery AND enrichment in one pipeline. Returns a keywordSessionRef immediately \u2014 the job runs in the background. Research typically takes ~20-30s. Wait ~20 seconds, then poll getKeywordSessionStatus with that keywordSessionRef every 10 seconds until status is 'completed' or 'failed'. Max ~16 poll attempts (~3 minutes including the initial wait) before reporting timeout.",
|
|
9901
9831
|
parametersSchema: {
|
|
9902
9832
|
type: "object",
|
|
@@ -9907,7 +9837,7 @@ var plugin = definePlugin({
|
|
|
9907
9837
|
async (params) => {
|
|
9908
9838
|
const { keyword } = params;
|
|
9909
9839
|
try {
|
|
9910
|
-
const json = await api("/
|
|
9840
|
+
const json = await api("/keyword-research/start", "POST", {
|
|
9911
9841
|
keyword
|
|
9912
9842
|
});
|
|
9913
9843
|
const d = json?.data ?? {};
|
|
@@ -9927,7 +9857,7 @@ var plugin = definePlugin({
|
|
|
9927
9857
|
"getKeywordSessionStatus",
|
|
9928
9858
|
{
|
|
9929
9859
|
displayName: "Get Keyword Session Status",
|
|
9930
|
-
description: "Poll the status of a KeywordSession started by
|
|
9860
|
+
description: "Poll the status of a KeywordSession started by startKeywordResearch. Wait ~20 seconds before the first poll (research takes ~20-30s), then call every 10 seconds. Stop when status is 'completed' or 'failed'. Max ~16 attempts (~3 min) \u2014 if still running after that, report timeout to the user and stop. When status is 'completed', the response contains the keywordSessionRef \u2014 the single id for this research run; pass it to clusterKeywords. Save it.",
|
|
9931
9861
|
parametersSchema: {
|
|
9932
9862
|
type: "object",
|
|
9933
9863
|
properties: { keywordSessionRef: { type: "string" } },
|
|
@@ -9938,40 +9868,43 @@ var plugin = definePlugin({
|
|
|
9938
9868
|
const { keywordSessionRef } = params;
|
|
9939
9869
|
try {
|
|
9940
9870
|
const json = await api(
|
|
9941
|
-
`/
|
|
9871
|
+
`/keyword-research/${keywordSessionRef}/status`,
|
|
9942
9872
|
"GET"
|
|
9943
9873
|
);
|
|
9944
|
-
const
|
|
9945
|
-
if (
|
|
9874
|
+
const sessionStatus = json?.data ?? {};
|
|
9875
|
+
if (sessionStatus.status === "completed" /* COMPLETED */) {
|
|
9946
9876
|
return {
|
|
9947
|
-
content: `Keyword research for "${
|
|
9877
|
+
content: `Keyword research for "${sessionStatus.seedKeyword}" complete: ${sessionStatus.highCount} HIGH, ${sessionStatus.medCount} MEDIUM, ${sessionStatus.lowCount} LOW, ${sessionStatus.deadCount} DEAD out of ${sessionStatus.totalEnriched} enriched keywords. keywordSessionRef is "${sessionStatus.keywordSessionRef}" \u2014 save it for clusterKeywords. Full keyword list is in the 'keywords' array \u2014 render it as a Markdown table now.`,
|
|
9948
9878
|
data: {
|
|
9949
|
-
status:
|
|
9950
|
-
keywordSessionRef:
|
|
9951
|
-
seedKeyword:
|
|
9952
|
-
totalSuggestions:
|
|
9953
|
-
totalEnriched:
|
|
9954
|
-
highCount:
|
|
9955
|
-
medCount:
|
|
9956
|
-
lowCount:
|
|
9957
|
-
deadCount:
|
|
9958
|
-
keywords:
|
|
9879
|
+
status: sessionStatus.status,
|
|
9880
|
+
keywordSessionRef: sessionStatus.keywordSessionRef,
|
|
9881
|
+
seedKeyword: sessionStatus.seedKeyword,
|
|
9882
|
+
totalSuggestions: sessionStatus.totalSuggestions,
|
|
9883
|
+
totalEnriched: sessionStatus.totalEnriched,
|
|
9884
|
+
highCount: sessionStatus.highCount,
|
|
9885
|
+
medCount: sessionStatus.medCount,
|
|
9886
|
+
lowCount: sessionStatus.lowCount,
|
|
9887
|
+
deadCount: sessionStatus.deadCount,
|
|
9888
|
+
keywords: sessionStatus.keywords ?? []
|
|
9959
9889
|
}
|
|
9960
9890
|
};
|
|
9961
9891
|
}
|
|
9962
|
-
if (
|
|
9892
|
+
if (sessionStatus.status === "failed" /* FAILED */) {
|
|
9963
9893
|
return {
|
|
9964
|
-
content: `Keyword research for "${
|
|
9894
|
+
content: `Keyword research for "${sessionStatus.seedKeyword}" failed: ${sessionStatus.errorMessage}`,
|
|
9965
9895
|
data: {
|
|
9966
|
-
status:
|
|
9967
|
-
keywordSessionRef:
|
|
9968
|
-
errorMessage:
|
|
9896
|
+
status: sessionStatus.status,
|
|
9897
|
+
keywordSessionRef: sessionStatus.keywordSessionRef,
|
|
9898
|
+
errorMessage: sessionStatus.errorMessage
|
|
9969
9899
|
}
|
|
9970
9900
|
};
|
|
9971
9901
|
}
|
|
9972
9902
|
return {
|
|
9973
|
-
content: `Job is still running (phase: ${
|
|
9974
|
-
data: {
|
|
9903
|
+
content: `Job is still running (phase: ${sessionStatus.status}). Poll again in 10 seconds.`,
|
|
9904
|
+
data: {
|
|
9905
|
+
status: sessionStatus.status,
|
|
9906
|
+
keywordSessionRef: sessionStatus.keywordSessionRef
|
|
9907
|
+
}
|
|
9975
9908
|
};
|
|
9976
9909
|
} catch (e) {
|
|
9977
9910
|
return { error: String(e) };
|
|
@@ -9979,7 +9912,7 @@ var plugin = definePlugin({
|
|
|
9979
9912
|
}
|
|
9980
9913
|
);
|
|
9981
9914
|
ctx.tools.register(
|
|
9982
|
-
"
|
|
9915
|
+
"listKeywordResearchSessions",
|
|
9983
9916
|
{
|
|
9984
9917
|
displayName: "List Keyword Sessions",
|
|
9985
9918
|
description: "List all past keyword research sessions with a brief overview \u2014 seedKeyword, status, and keyword count only.",
|
|
@@ -9987,17 +9920,16 @@ var plugin = definePlugin({
|
|
|
9987
9920
|
},
|
|
9988
9921
|
async () => {
|
|
9989
9922
|
try {
|
|
9990
|
-
const json = await api("/
|
|
9923
|
+
const json = await api("/keyword-research/", "GET");
|
|
9991
9924
|
const sessions = json?.data ?? [];
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
};
|
|
9925
|
+
const data = sessions.map((s) => ({
|
|
9926
|
+
keywordSessionRef: String(s._id ?? ""),
|
|
9927
|
+
seedKeyword: s.seedKeyword,
|
|
9928
|
+
status: s.status,
|
|
9929
|
+
keywordCount: s.keywordCount ?? 0,
|
|
9930
|
+
createdAt: s.createdAt
|
|
9931
|
+
}));
|
|
9932
|
+
return { data };
|
|
10001
9933
|
} catch (e) {
|
|
10002
9934
|
return { error: String(e) };
|
|
10003
9935
|
}
|
|
@@ -10007,7 +9939,7 @@ var plugin = definePlugin({
|
|
|
10007
9939
|
"clusterKeywords",
|
|
10008
9940
|
{
|
|
10009
9941
|
displayName: "Cluster Keywords",
|
|
10010
|
-
description: "Call this AFTER getKeywordSessionStatus returns 'completed'. Pass the keywordSessionRef from that completed response (or from
|
|
9942
|
+
description: "Call this AFTER getKeywordSessionStatus returns 'completed'. Pass the keywordSessionRef from that completed response (or from listKeywordResearchSessions). Returns a clusterId. Clustering can take ~20s to 1 min or more. Wait ~20 seconds, then poll getClusterStatus every 15 seconds until status is 'completed'. Max ~18 attempts (~5 min) before reporting timeout.",
|
|
10011
9943
|
parametersSchema: {
|
|
10012
9944
|
type: "object",
|
|
10013
9945
|
properties: { keywordSessionRef: { type: "string" } },
|
|
@@ -10017,7 +9949,7 @@ var plugin = definePlugin({
|
|
|
10017
9949
|
async (params) => {
|
|
10018
9950
|
const { keywordSessionRef } = params;
|
|
10019
9951
|
try {
|
|
10020
|
-
const json = await api("/
|
|
9952
|
+
const json = await api("/clustering", "POST", {
|
|
10021
9953
|
keywordSessionRef
|
|
10022
9954
|
});
|
|
10023
9955
|
const d = json?.data ?? {};
|
|
@@ -10044,9 +9976,10 @@ var plugin = definePlugin({
|
|
|
10044
9976
|
async (params) => {
|
|
10045
9977
|
const { clusterId } = params;
|
|
10046
9978
|
try {
|
|
10047
|
-
const json = await api(`/
|
|
9979
|
+
const json = await api(`/clustering/${clusterId}`, "GET");
|
|
10048
9980
|
const d = json?.data ?? {};
|
|
10049
|
-
|
|
9981
|
+
const clusterStatus = String(d.status ?? "").toLowerCase();
|
|
9982
|
+
if (clusterStatus === "completed" /* COMPLETED */) {
|
|
10050
9983
|
const plan = d.clusterPlan ?? {};
|
|
10051
9984
|
const architectures = (plan.architectures ?? []).map((arch) => ({
|
|
10052
9985
|
pillar: arch.pillar ? {
|
|
@@ -10086,7 +10019,7 @@ var plugin = definePlugin({
|
|
|
10086
10019
|
}
|
|
10087
10020
|
};
|
|
10088
10021
|
}
|
|
10089
|
-
if (
|
|
10022
|
+
if (clusterStatus === "failed" /* FAILED */) {
|
|
10090
10023
|
return {
|
|
10091
10024
|
content: `Clustering failed: ${d.error ?? "Unknown error"}`,
|
|
10092
10025
|
data: { status: d.status, error: d.error }
|
|
@@ -10110,17 +10043,16 @@ var plugin = definePlugin({
|
|
|
10110
10043
|
},
|
|
10111
10044
|
async () => {
|
|
10112
10045
|
try {
|
|
10113
|
-
const json = await api("/
|
|
10046
|
+
const json = await api("/clustering/", "GET");
|
|
10114
10047
|
const sessions = json?.data ?? [];
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
};
|
|
10048
|
+
const data = sessions.map((s) => ({
|
|
10049
|
+
clusterId: String(s._id ?? ""),
|
|
10050
|
+
seedKeyword: s.seedKeyword,
|
|
10051
|
+
status: s.status,
|
|
10052
|
+
keywordCount: s.keywordCount ?? 0,
|
|
10053
|
+
createdAt: s.createdAt
|
|
10054
|
+
}));
|
|
10055
|
+
return { data };
|
|
10124
10056
|
} catch (e) {
|
|
10125
10057
|
return { error: String(e) };
|
|
10126
10058
|
}
|
|
@@ -10130,7 +10062,7 @@ var plugin = definePlugin({
|
|
|
10130
10062
|
"generateContentBrief",
|
|
10131
10063
|
{
|
|
10132
10064
|
displayName: "Generate Content Brief",
|
|
10133
|
-
description: "Call this once user approves a cluster or passed only a keyword. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'.",
|
|
10065
|
+
description: "Call this once user approves a cluster or passed only a keyword. Returns a briefId. You MUST then poll getBriefStatus until status is 'COMPLETED'. When source is 'cluster', clusterSessionRef is REQUIRED (the clusterId from a completed clusterKeywords run); for source 'standalone' omit clusterSessionRef and pass only keyword.",
|
|
10134
10066
|
parametersSchema: {
|
|
10135
10067
|
type: "object",
|
|
10136
10068
|
properties: {
|
|
@@ -10145,8 +10077,8 @@ var plugin = definePlugin({
|
|
|
10145
10077
|
async (params) => {
|
|
10146
10078
|
const p = params;
|
|
10147
10079
|
try {
|
|
10148
|
-
const
|
|
10149
|
-
return { data };
|
|
10080
|
+
const json = await api("/content-brief/generate", "POST", p);
|
|
10081
|
+
return { data: json?.data ?? {} };
|
|
10150
10082
|
} catch (e) {
|
|
10151
10083
|
return { error: String(e) };
|
|
10152
10084
|
}
|
|
@@ -10166,24 +10098,8 @@ var plugin = definePlugin({
|
|
|
10166
10098
|
async (params) => {
|
|
10167
10099
|
const { briefId } = params;
|
|
10168
10100
|
try {
|
|
10169
|
-
const json = await api(`/
|
|
10170
|
-
|
|
10171
|
-
if (d.status === "COMPLETED" || d.status === "NEEDS_REVIEW") {
|
|
10172
|
-
return {
|
|
10173
|
-
content: `Brief ${d.status} for "${d.keyword}". The 'brief' object is the trimmed writer view \u2014 render it per RENDERING.md.`,
|
|
10174
|
-
data: { briefId, brief: toBriefWriterView(d) }
|
|
10175
|
-
};
|
|
10176
|
-
}
|
|
10177
|
-
if (d.status === "FAILED") {
|
|
10178
|
-
return {
|
|
10179
|
-
content: `Brief generation failed: ${d.error ?? "Unknown error"}`,
|
|
10180
|
-
data: { briefId, status: d.status, error: d.error }
|
|
10181
|
-
};
|
|
10182
|
-
}
|
|
10183
|
-
return {
|
|
10184
|
-
content: `Brief is still running (status: ${d.status}). Poll again in ~10 seconds.`,
|
|
10185
|
-
data: { briefId, status: d.status }
|
|
10186
|
-
};
|
|
10101
|
+
const json = await api(`/content-brief/${briefId}`, "GET");
|
|
10102
|
+
return { data: json?.data ?? {} };
|
|
10187
10103
|
} catch (e) {
|
|
10188
10104
|
return { error: String(e) };
|
|
10189
10105
|
}
|
|
@@ -10203,7 +10119,15 @@ var plugin = definePlugin({
|
|
|
10203
10119
|
const { clusterSessionRef } = params;
|
|
10204
10120
|
try {
|
|
10205
10121
|
const qs = clusterSessionRef ? `?clusterSessionRef=${clusterSessionRef}` : "";
|
|
10206
|
-
const
|
|
10122
|
+
const json = await api(`/content-brief/${qs}`, "GET");
|
|
10123
|
+
const briefs = json?.data ?? [];
|
|
10124
|
+
const data = briefs.map((b) => ({
|
|
10125
|
+
briefId: String(b._id ?? ""),
|
|
10126
|
+
keyword: b.keyword,
|
|
10127
|
+
status: b.status,
|
|
10128
|
+
createdAt: b.createdAt,
|
|
10129
|
+
updatedAt: b.updatedAt
|
|
10130
|
+
}));
|
|
10207
10131
|
return { data };
|
|
10208
10132
|
} catch (e) {
|
|
10209
10133
|
return { error: String(e) };
|
|
@@ -10219,7 +10143,10 @@ var plugin = definePlugin({
|
|
|
10219
10143
|
type: "object",
|
|
10220
10144
|
properties: {
|
|
10221
10145
|
briefId: { type: "string" },
|
|
10222
|
-
stage: {
|
|
10146
|
+
stage: {
|
|
10147
|
+
type: "string",
|
|
10148
|
+
enum: Object.values(RerunStage)
|
|
10149
|
+
}
|
|
10223
10150
|
},
|
|
10224
10151
|
required: ["briefId", "stage"]
|
|
10225
10152
|
}
|
|
@@ -10227,11 +10154,11 @@ var plugin = definePlugin({
|
|
|
10227
10154
|
async (params) => {
|
|
10228
10155
|
const { briefId, stage } = params;
|
|
10229
10156
|
try {
|
|
10230
|
-
const
|
|
10231
|
-
`/
|
|
10157
|
+
const json = await api(
|
|
10158
|
+
`/content-brief/${briefId}/rerun?stage=${stage}`,
|
|
10232
10159
|
"POST"
|
|
10233
10160
|
);
|
|
10234
|
-
return { data };
|
|
10161
|
+
return { data: json?.data ?? {} };
|
|
10235
10162
|
} catch (e) {
|
|
10236
10163
|
return { error: String(e) };
|
|
10237
10164
|
}
|
|
@@ -10257,12 +10184,12 @@ var plugin = definePlugin({
|
|
|
10257
10184
|
async (params) => {
|
|
10258
10185
|
const { briefId, ...bodyParams } = params;
|
|
10259
10186
|
try {
|
|
10260
|
-
const
|
|
10261
|
-
`/
|
|
10187
|
+
const json = await api(
|
|
10188
|
+
`/content-brief/${briefId}/proof-points`,
|
|
10262
10189
|
"PATCH",
|
|
10263
10190
|
bodyParams
|
|
10264
10191
|
);
|
|
10265
|
-
return { data };
|
|
10192
|
+
return { data: json?.data ?? {} };
|
|
10266
10193
|
} catch (e) {
|
|
10267
10194
|
return { error: String(e) };
|
|
10268
10195
|
}
|
|
@@ -10281,7 +10208,7 @@ var plugin = definePlugin({
|
|
|
10281
10208
|
});
|
|
10282
10209
|
ctx.data.register("sessions", async () => {
|
|
10283
10210
|
try {
|
|
10284
|
-
const json = await api("/
|
|
10211
|
+
const json = await api("/keyword-research/", "GET");
|
|
10285
10212
|
return { sessions: json?.data ?? [] };
|
|
10286
10213
|
} catch (err) {
|
|
10287
10214
|
ctx.logger.error("sessions handler failed", { error: String(err) });
|
|
@@ -10297,7 +10224,7 @@ var plugin = definePlugin({
|
|
|
10297
10224
|
}
|
|
10298
10225
|
try {
|
|
10299
10226
|
const json = await api(
|
|
10300
|
-
`/
|
|
10227
|
+
`/keyword-research/${encodeURIComponent(keywordSessionRef)}/status`,
|
|
10301
10228
|
"GET"
|
|
10302
10229
|
);
|
|
10303
10230
|
return {
|