@khalid-byldd/seo-tools-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/dist/manifest.js +220 -0
- package/dist/manifest.js.map +7 -0
- package/dist/ui/index.js +32 -0
- package/dist/ui/index.js.map +7 -0
- package/dist/worker.js +10328 -0
- package/dist/worker.js.map +7 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Byldd SEO Tools — Paperclip Plugin
|
|
2
|
+
|
|
3
|
+
A [Paperclip AI](https://paperclip.dev) plugin that connects autonomous SEO agents to the **Byldd SEO Backend**. It exposes keyword research, clustering, and content brief generation as agent tools — enabling a fully automated, Human-in-the-Loop SEO content pipeline.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
This plugin registers **12 agent tools** that proxy requests to the Byldd SEO backend API:
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `runKeywordResearch` | Start an async keyword discovery + enrichment pipeline for a seed keyword |
|
|
12
|
+
| `getKeywordSessionStatus` | Poll a running keyword research job until completion |
|
|
13
|
+
| `listKeywordSessions` | List all past keyword research sessions |
|
|
14
|
+
| `clusterKeywords` | Group enriched keywords into topical hub-and-spoke clusters |
|
|
15
|
+
| `getClusterStatus` | Poll a running clustering job until completion |
|
|
16
|
+
| `listClusterSessions` | List all past clustering sessions |
|
|
17
|
+
| `generateContentBrief` | Generate a full content brief from a cluster or standalone keyword |
|
|
18
|
+
| `getBriefStatus` | Poll a running brief generation job until completion |
|
|
19
|
+
| `listContentBriefs` | List content briefs, optionally filtered by cluster |
|
|
20
|
+
| `rerunBriefStage` | Regenerate a single stage of the brief pipeline |
|
|
21
|
+
| `attachProofPoints` | Inject business-specific proof points (pricing, timelines, outcomes) into a brief |
|
|
22
|
+
|
|
23
|
+
### UI Components
|
|
24
|
+
|
|
25
|
+
| Slot | Type | Description |
|
|
26
|
+
|------|------|-------------|
|
|
27
|
+
| `health-widget` | Dashboard Widget | Shows plugin health status |
|
|
28
|
+
| `keyword-research-tab` | Issue Detail Tab | Keyword research data tab on issues |
|
|
29
|
+
|
|
30
|
+
### Data Handlers
|
|
31
|
+
|
|
32
|
+
| Key | Description |
|
|
33
|
+
|-----|-------------|
|
|
34
|
+
| `health` | Plugin health check |
|
|
35
|
+
| `sessions` | List keyword research sessions (backs the UI tab) |
|
|
36
|
+
| `enrichResult` | Full keyword metrics for a session (backs the UI tab) |
|
|
37
|
+
|
|
38
|
+
## Prerequisites
|
|
39
|
+
|
|
40
|
+
- [Paperclip AI](https://paperclip.dev) instance running locally or deployed
|
|
41
|
+
- [Byldd SEO Backend](https://github.com/byldd/seo-byldd-backend) running and accessible
|
|
42
|
+
- Node.js ≥ 18
|
|
43
|
+
|
|
44
|
+
## Setup
|
|
45
|
+
|
|
46
|
+
### 1. Install Dependencies
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Build
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm run build
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Install Into Paperclip
|
|
59
|
+
|
|
60
|
+
Once published to npm, you can install the plugin directly using its package name:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
paperclipai plugin install @byldd/seo-tools
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Note:** For local development before publishing, you can still install it from the local path:
|
|
67
|
+
> ```bash
|
|
68
|
+
> paperclipai plugin install /path/to/seo-tools
|
|
69
|
+
> ```
|
|
70
|
+
|
|
71
|
+
### 4. Configure Credentials
|
|
72
|
+
|
|
73
|
+
This plugin reads the backend URL and auth token from **Paperclip's plugin instance config** (not hardcoded in source). Set them after installing:
|
|
74
|
+
|
|
75
|
+
**Via CLI:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx paperclipai plugin config:set byldd.seo-tools \
|
|
79
|
+
--payload-json '{"backendUrl":"https://seo-api.byldd.com","authToken":"<YOUR_JWT_TOKEN>"}'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Via Paperclip UI:**
|
|
83
|
+
|
|
84
|
+
1. Open Paperclip → **Settings** → **Plugins** → **Seo Tools**
|
|
85
|
+
2. Fill in:
|
|
86
|
+
- **Backend URL**: Your SEO backend base URL (e.g. `https://seo-api.byldd.com`)
|
|
87
|
+
- **Auth Token**: A valid JWT token for the backend
|
|
88
|
+
|
|
89
|
+
> **Note:** The auth token is a JWT with an expiry. Rotate it in the Paperclip UI when it expires — no code changes needed.
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm run dev # watch-mode rebuild (worker + manifest + UI)
|
|
95
|
+
npm run dev:ui # local UI dev server with hot-reload
|
|
96
|
+
npm test # run tests with vitest
|
|
97
|
+
npm run typecheck # type-check without emitting
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`npm run dev` rebuilds into `dist/` on file changes. Paperclip watches the output directory and hot-reloads the plugin worker automatically for local installs.
|
|
101
|
+
|
|
102
|
+
## Project Structure
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
seo-tools/
|
|
106
|
+
├── src/
|
|
107
|
+
│ ├── worker.ts # Plugin worker — tool handlers + data handlers
|
|
108
|
+
│ ├── manifest.ts # Plugin manifest — tools, capabilities, config schema
|
|
109
|
+
│ ├── types/
|
|
110
|
+
│ │ └── index.ts # TypeScript interfaces for tool params + cluster shapes
|
|
111
|
+
│ └── ui/
|
|
112
|
+
│ └── index.tsx # React UI components (dashboard widget, detail tab)
|
|
113
|
+
├── tests/
|
|
114
|
+
│ └── plugin.spec.ts # Plugin tests
|
|
115
|
+
├── dist/ # Build output (gitignored)
|
|
116
|
+
├── esbuild.config.mjs # esbuild bundler config
|
|
117
|
+
├── rollup.config.mjs # Rollup bundler config (alternative)
|
|
118
|
+
├── tsconfig.json
|
|
119
|
+
├── vitest.config.ts
|
|
120
|
+
└── package.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Configuration Schema
|
|
124
|
+
|
|
125
|
+
The plugin declares an `instanceConfigSchema` in the manifest. Operators fill these fields in the Paperclip UI:
|
|
126
|
+
|
|
127
|
+
| Field | Type | Required | Description |
|
|
128
|
+
|-------|------|----------|-------------|
|
|
129
|
+
| `backendUrl` | `string` | ✅ | Base URL of the SEO backend API |
|
|
130
|
+
| `authToken` | `string` | ✅ | JWT auth token for the backend |
|
|
131
|
+
|
|
132
|
+
## Capabilities
|
|
133
|
+
|
|
134
|
+
This plugin requests the following Paperclip capabilities:
|
|
135
|
+
|
|
136
|
+
| Capability | Purpose |
|
|
137
|
+
|------------|---------|
|
|
138
|
+
| `agent.tools.register` | Register SEO tools for agent use |
|
|
139
|
+
| `http.outbound` | Make HTTP requests to the SEO backend |
|
|
140
|
+
| `ui.detailTab.register` | Render the Keyword Research tab on issues |
|
|
141
|
+
| `ui.dashboardWidget.register` | Render the health dashboard widget |
|
|
142
|
+
| `events.subscribe` | Listen to Paperclip domain events |
|
|
143
|
+
| `plugin.state.read` | Read plugin-scoped state |
|
|
144
|
+
| `plugin.state.write` | Write plugin-scoped state |
|
|
145
|
+
|
|
146
|
+
## Build Options
|
|
147
|
+
|
|
148
|
+
- `npm run build` — esbuild (default, faster)
|
|
149
|
+
- `npm run build:rollup` — rollup (alternative)
|
|
150
|
+
|
|
151
|
+
Both output to `dist/`.
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/manifest.ts
|
|
2
|
+
var manifest = {
|
|
3
|
+
id: "byldd.seo-tools",
|
|
4
|
+
apiVersion: 1,
|
|
5
|
+
version: "0.1.0",
|
|
6
|
+
displayName: "Seo Tools",
|
|
7
|
+
description: "A Paperclip plugin",
|
|
8
|
+
author: "Plugin Author",
|
|
9
|
+
categories: ["connector"],
|
|
10
|
+
capabilities: [
|
|
11
|
+
"agent.tools.register",
|
|
12
|
+
"http.outbound",
|
|
13
|
+
"ui.detailTab.register",
|
|
14
|
+
"events.subscribe",
|
|
15
|
+
"plugin.state.read",
|
|
16
|
+
"plugin.state.write",
|
|
17
|
+
"ui.dashboardWidget.register"
|
|
18
|
+
],
|
|
19
|
+
instanceConfigSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
backendUrl: {
|
|
23
|
+
type: "string",
|
|
24
|
+
title: "Backend URL",
|
|
25
|
+
description: "Base URL of the SEO Byldd Server"
|
|
26
|
+
},
|
|
27
|
+
authToken: {
|
|
28
|
+
type: "string",
|
|
29
|
+
title: "Auth Token",
|
|
30
|
+
description: "JWT auth token for the SEO Byldd Server"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ["backendUrl", "authToken"]
|
|
34
|
+
},
|
|
35
|
+
entrypoints: {
|
|
36
|
+
worker: "./dist/worker.js",
|
|
37
|
+
ui: "./dist/ui"
|
|
38
|
+
},
|
|
39
|
+
ui: {
|
|
40
|
+
slots: [
|
|
41
|
+
{
|
|
42
|
+
type: "dashboardWidget",
|
|
43
|
+
id: "health-widget",
|
|
44
|
+
displayName: "Seo Tools Health",
|
|
45
|
+
exportName: "DashboardWidget"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: "detailTab",
|
|
49
|
+
id: "keyword-research-tab",
|
|
50
|
+
displayName: "Keyword Research",
|
|
51
|
+
exportName: "KeywordResearchTab",
|
|
52
|
+
entityTypes: ["issue"]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
tools: [
|
|
57
|
+
{
|
|
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).",
|
|
61
|
+
parametersSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
keyword: { type: "string" }
|
|
65
|
+
},
|
|
66
|
+
required: ["keyword"]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "getKeywordSessionStatus",
|
|
71
|
+
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).",
|
|
73
|
+
parametersSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
keywordSessionRef: { type: "string" }
|
|
77
|
+
},
|
|
78
|
+
required: ["keywordSessionRef"]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
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",
|
|
107
|
+
displayName: "List Keyword Sessions",
|
|
108
|
+
description: "List all past keyword research sessions.",
|
|
109
|
+
parametersSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "clusterKeywords",
|
|
116
|
+
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).",
|
|
118
|
+
parametersSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
keywordSessionRef: { type: "string" }
|
|
122
|
+
},
|
|
123
|
+
required: ["keywordSessionRef"]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "getClusterStatus",
|
|
128
|
+
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).",
|
|
130
|
+
parametersSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
clusterId: { type: "string" }
|
|
134
|
+
},
|
|
135
|
+
required: ["clusterId"]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "listClusterSessions",
|
|
140
|
+
displayName: "List Cluster Sessions",
|
|
141
|
+
description: "List all past clustering sessions.",
|
|
142
|
+
parametersSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "generateContentBrief",
|
|
149
|
+
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.",
|
|
151
|
+
parametersSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
source: { type: "string", enum: ["cluster", "standalone"] },
|
|
155
|
+
keyword: { type: "string" },
|
|
156
|
+
clusterSessionRef: { type: "string" },
|
|
157
|
+
isSelfReferentialListicle: { type: "boolean" }
|
|
158
|
+
},
|
|
159
|
+
required: ["source", "keyword"]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "getBriefStatus",
|
|
164
|
+
displayName: "Get Brief Status",
|
|
165
|
+
description: "Fetch the current state of a brief. Poll this until status is COMPLETED.",
|
|
166
|
+
parametersSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
briefId: { type: "string" }
|
|
170
|
+
},
|
|
171
|
+
required: ["briefId"]
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "listContentBriefs",
|
|
176
|
+
displayName: "List Content Briefs",
|
|
177
|
+
description: "List content briefs, optionally filtered by cluster.",
|
|
178
|
+
parametersSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
clusterSessionRef: { type: "string" }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "rerunBriefStage",
|
|
187
|
+
displayName: "Rerun Brief Stage",
|
|
188
|
+
description: "Regenerate a single stage of the brief pipeline.",
|
|
189
|
+
parametersSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
briefId: { type: "string" },
|
|
193
|
+
stage: { type: "string" }
|
|
194
|
+
},
|
|
195
|
+
required: ["briefId", "stage"]
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "attachProofPoints",
|
|
200
|
+
displayName: "Attach Proof Points",
|
|
201
|
+
description: "Inject business-specific proof points into a brief.",
|
|
202
|
+
parametersSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
briefId: { type: "string" },
|
|
206
|
+
pricingRanges: { type: "string" },
|
|
207
|
+
timelines: { type: "string" },
|
|
208
|
+
clientOutcomes: { type: "array", items: { type: "string" } },
|
|
209
|
+
methodology: { type: "string" }
|
|
210
|
+
},
|
|
211
|
+
required: ["briefId"]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
var manifest_default = manifest;
|
|
217
|
+
export {
|
|
218
|
+
manifest_default as default
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
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": []
|
|
7
|
+
}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/ui/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
usePluginData
|
|
4
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function KeywordResearchTab(_props) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
function DashboardWidget(_props) {
|
|
10
|
+
const { data, loading, error } = usePluginData("health");
|
|
11
|
+
if (loading) return /* @__PURE__ */ jsx("div", { children: "Loading plugin health..." });
|
|
12
|
+
if (error) return /* @__PURE__ */ jsxs("div", { children: [
|
|
13
|
+
"Plugin error: ",
|
|
14
|
+
error.message
|
|
15
|
+
] });
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.5rem" }, children: [
|
|
17
|
+
/* @__PURE__ */ jsx("strong", { children: "Seo Tools" }),
|
|
18
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
19
|
+
"Health: ",
|
|
20
|
+
data?.status ?? "unknown"
|
|
21
|
+
] }),
|
|
22
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
23
|
+
"Checked: ",
|
|
24
|
+
data?.checkedAt ?? "never"
|
|
25
|
+
] })
|
|
26
|
+
] });
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
DashboardWidget,
|
|
30
|
+
KeywordResearchTab
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/index.tsx"],
|
|
4
|
+
"sourcesContent": ["import {\n usePluginData,\n type PluginWidgetProps,\n type PluginDetailTabProps,\n} from \"@paperclipai/plugin-sdk/ui\";\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype HealthData = {\n status: \"ok\" | \"degraded\" | \"error\";\n checkedAt: string;\n};\n\n// \u2500\u2500\u2500 Keyword Research Tab (UI rendering disabled) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function KeywordResearchTab(_props: PluginDetailTabProps) {\n return null;\n}\n\n// \u2500\u2500\u2500 Dashboard widget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function DashboardWidget(_props: PluginWidgetProps) {\n const { data, loading, error } = usePluginData<HealthData>(\"health\");\n\n if (loading) return <div>Loading plugin health...</div>;\n if (error) return <div>Plugin error: {error.message}</div>;\n\n return (\n <div style={{ display: \"grid\", gap: \"0.5rem\" }}>\n <strong>Seo Tools</strong>\n <div>Health: {data?.status ?? \"unknown\"}</div>\n <div>Checked: {data?.checkedAt ?? \"never\"}</div>\n </div>\n );\n}\n"],
|
|
5
|
+
"mappings": ";AAAA;AAAA,EACE;AAAA,OAGK;AAoBe,cACF,YADE;AATf,SAAS,mBAAmB,QAA8B;AAC/D,SAAO;AACT;AAIO,SAAS,gBAAgB,QAA2B;AACzD,QAAM,EAAE,MAAM,SAAS,MAAM,IAAI,cAA0B,QAAQ;AAEnE,MAAI,QAAS,QAAO,oBAAC,SAAI,sCAAwB;AACjD,MAAI,MAAO,QAAO,qBAAC,SAAI;AAAA;AAAA,IAAe,MAAM;AAAA,KAAQ;AAEpD,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,SAAS,GAC3C;AAAA,wBAAC,YAAO,uBAAS;AAAA,IACjB,qBAAC,SAAI;AAAA;AAAA,MAAS,MAAM,UAAU;AAAA,OAAU;AAAA,IACxC,qBAAC,SAAI;AAAA;AAAA,MAAU,MAAM,aAAa;AAAA,OAAQ;AAAA,KAC5C;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|