@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 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: "runKeywordResearch",
59
- displayName: "Run Keyword Research",
60
- description: "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).",
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 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).",
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: "enrichKeywords",
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' (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).",
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: "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).",
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'. Start the async content-brief pipeline.",
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: { type: "string" }
180
+ stage: {
181
+ type: "string",
182
+ enum: Object.values(RerunStage)
183
+ }
194
184
  },
195
185
  required: ["briefId", "stage"]
196
186
  }
@@ -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": "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",
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": "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",
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/utils/brief-view.ts
9777
- var SCHEMA_KEY_LABELS = {
9778
- article: "Article",
9779
- itemList: "ItemList",
9780
- breadcrumbList: "BreadcrumbList",
9781
- faqPage: "FAQPage",
9782
- howTo: "HowTo",
9783
- service: "Service",
9784
- offer: "Offer"
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(`${BASE}${path2}`, {
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
- "runKeywordResearch",
9827
+ "startKeywordResearch",
9898
9828
  {
9899
- displayName: "Run Keyword Research",
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("/api/keyword-research/start", "POST", {
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 runKeywordResearch. 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.",
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
- `/api/keyword-research/${keywordSessionRef}/status`,
9871
+ `/keyword-research/${keywordSessionRef}/status`,
9942
9872
  "GET"
9943
9873
  );
9944
- const d = json?.data ?? {};
9945
- if (d.status === "completed") {
9874
+ const sessionStatus = json?.data ?? {};
9875
+ if (sessionStatus.status === "completed" /* COMPLETED */) {
9946
9876
  return {
9947
- content: `Keyword research for "${d.seedKeyword}" complete: ${d.highCount} HIGH, ${d.medCount} MEDIUM, ${d.lowCount} LOW, ${d.deadCount} DEAD out of ${d.totalEnriched} enriched keywords. keywordSessionRef is "${d.keywordSessionRef}" \u2014 save it for clusterKeywords. Full keyword list is in the 'keywords' array \u2014 render it as a Markdown table now.`,
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: d.status,
9950
- keywordSessionRef: d.keywordSessionRef,
9951
- seedKeyword: d.seedKeyword,
9952
- totalSuggestions: d.totalSuggestions,
9953
- totalEnriched: d.totalEnriched,
9954
- highCount: d.highCount,
9955
- medCount: d.medCount,
9956
- lowCount: d.lowCount,
9957
- deadCount: d.deadCount,
9958
- keywords: d.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 (d.status === "failed") {
9892
+ if (sessionStatus.status === "failed" /* FAILED */) {
9963
9893
  return {
9964
- content: `Keyword research for "${d.seedKeyword}" failed: ${d.errorMessage}`,
9894
+ content: `Keyword research for "${sessionStatus.seedKeyword}" failed: ${sessionStatus.errorMessage}`,
9965
9895
  data: {
9966
- status: d.status,
9967
- keywordSessionRef: d.keywordSessionRef,
9968
- errorMessage: d.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: ${d.status}). Poll again in 10 seconds.`,
9974
- data: { status: d.status, keywordSessionRef: d.keywordSessionRef }
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
- "listKeywordSessions",
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("/api/keyword-research/", "GET");
9923
+ const json = await api("/keyword-research/", "GET");
9991
9924
  const sessions = json?.data ?? [];
9992
- return {
9993
- data: sessions.map((s) => ({
9994
- keywordSessionRef: String(s._id ?? ""),
9995
- seedKeyword: s.seedKeyword,
9996
- status: s.status,
9997
- keywordCount: s.keywordCount ?? 0,
9998
- createdAt: s.createdAt
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 listKeywordSessions). 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.",
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("/api/clustering", "POST", {
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(`/api/clustering/${clusterId}`, "GET");
9979
+ const json = await api(`/clustering/${clusterId}`, "GET");
10048
9980
  const d = json?.data ?? {};
10049
- if (d.status === "completed" || d.status === "COMPLETED") {
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 (d.status === "failed" || d.status === "FAILED") {
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("/api/clustering/", "GET");
10046
+ const json = await api("/clustering/", "GET");
10114
10047
  const sessions = json?.data ?? [];
10115
- return {
10116
- data: sessions.map((s) => ({
10117
- clusterId: String(s._id ?? ""),
10118
- seedKeyword: s.seedKeyword,
10119
- status: s.status,
10120
- keywordCount: s.keywordCount ?? 0,
10121
- createdAt: s.createdAt
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 data = await api("/api/content-brief/generate", "POST", p);
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(`/api/content-brief/${briefId}`, "GET");
10170
- const d = json?.data ?? {};
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 data = await api(`/api/content-brief/${qs}`, "GET");
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: { type: "string" }
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 data = await api(
10231
- `/api/content-brief/${briefId}/rerun?stage=${stage}`,
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 data = await api(
10261
- `/api/content-brief/${briefId}/proof-points`,
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("/api/keyword-research/", "GET");
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
- `/api/keyword-research/${encodeURIComponent(keywordSessionRef)}/status`,
10227
+ `/keyword-research/${encodeURIComponent(keywordSessionRef)}/status`,
10301
10228
  "GET"
10302
10229
  );
10303
10230
  return {