44reports-mcp 1.1.0 → 1.3.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/dist/api-client.d.ts +115 -4
- package/dist/api-client.js +82 -5
- package/dist/index.js +163 -10
- package/dist/tools/backlog.d.ts +58 -0
- package/dist/tools/backlog.js +46 -0
- package/dist/tools/backlog.test.d.ts +1 -0
- package/dist/tools/backlog.test.js +255 -0
- package/dist/tools/domains.d.ts +2 -0
- package/dist/tools/domains.js +1 -0
- package/dist/tools/products.d.ts +117 -8
- package/dist/tools/products.js +82 -31
- package/dist/tools/upload-directory.d.ts +91 -0
- package/dist/tools/upload-directory.js +191 -0
- package/dist/tools/upload-directory.test.d.ts +1 -0
- package/dist/tools/upload-directory.test.js +173 -0
- package/package.json +2 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -56,6 +56,22 @@ export interface Folder {
|
|
|
56
56
|
created_at: string;
|
|
57
57
|
updated_at: string;
|
|
58
58
|
}
|
|
59
|
+
export interface BacklogEntry {
|
|
60
|
+
id: string;
|
|
61
|
+
product_slug: string | null;
|
|
62
|
+
content: string;
|
|
63
|
+
source: string | null;
|
|
64
|
+
source_context: string | null;
|
|
65
|
+
domain_hint: string | null;
|
|
66
|
+
tags: string | null;
|
|
67
|
+
status: string;
|
|
68
|
+
result_summary: string | null;
|
|
69
|
+
applied_changes: string | null;
|
|
70
|
+
rejection_reason: string | null;
|
|
71
|
+
processed_at: string | null;
|
|
72
|
+
created_at: string;
|
|
73
|
+
updated_at: string;
|
|
74
|
+
}
|
|
59
75
|
export declare class ApiClient {
|
|
60
76
|
private readonly config;
|
|
61
77
|
constructor(config: Config);
|
|
@@ -120,17 +136,22 @@ export declare class ApiClient {
|
|
|
120
136
|
removeReportFromFolder(folderSlug: string, reportSlug: string): Promise<{
|
|
121
137
|
success: boolean;
|
|
122
138
|
}>;
|
|
123
|
-
listProducts(
|
|
139
|
+
listProducts(options?: {
|
|
140
|
+
search?: string;
|
|
141
|
+
limit?: number;
|
|
142
|
+
offset?: number;
|
|
143
|
+
}): Promise<{
|
|
124
144
|
products: Array<{
|
|
125
145
|
id: string;
|
|
126
146
|
slug: string;
|
|
127
147
|
name: string;
|
|
128
148
|
description: string | null;
|
|
129
|
-
|
|
130
|
-
|
|
149
|
+
created_at: string;
|
|
150
|
+
updated_at: string;
|
|
131
151
|
}>;
|
|
152
|
+
total: number;
|
|
132
153
|
}>;
|
|
133
|
-
getProduct(slug: string): Promise<{
|
|
154
|
+
getProduct(slug: string, domain?: string): Promise<{
|
|
134
155
|
product: {
|
|
135
156
|
id: string;
|
|
136
157
|
slug: string;
|
|
@@ -140,10 +161,16 @@ export declare class ApiClient {
|
|
|
140
161
|
updated_at: string;
|
|
141
162
|
};
|
|
142
163
|
knowledge: Record<string, unknown>;
|
|
164
|
+
doc_files: Array<{
|
|
165
|
+
path: string;
|
|
166
|
+
size: number;
|
|
167
|
+
domain: string;
|
|
168
|
+
}>;
|
|
143
169
|
binary_files: Array<{
|
|
144
170
|
path: string;
|
|
145
171
|
size: number;
|
|
146
172
|
content_type: string;
|
|
173
|
+
domain: string;
|
|
147
174
|
}>;
|
|
148
175
|
}>;
|
|
149
176
|
getProductFile(slug: string, filepath: string): Promise<{
|
|
@@ -168,4 +195,88 @@ export declare class ApiClient {
|
|
|
168
195
|
success: boolean;
|
|
169
196
|
uploaded: string[];
|
|
170
197
|
}>;
|
|
198
|
+
getProductHealth(slug: string): Promise<{
|
|
199
|
+
slug: string;
|
|
200
|
+
name: string;
|
|
201
|
+
overall_score: number;
|
|
202
|
+
files: Record<string, unknown>;
|
|
203
|
+
recommendations: string[];
|
|
204
|
+
}>;
|
|
205
|
+
getAllProductsHealth(): Promise<{
|
|
206
|
+
products: Array<{
|
|
207
|
+
slug: string;
|
|
208
|
+
name: string;
|
|
209
|
+
overall_score: number;
|
|
210
|
+
files: Record<string, {
|
|
211
|
+
score: number;
|
|
212
|
+
missing_required: string[];
|
|
213
|
+
}>;
|
|
214
|
+
}>;
|
|
215
|
+
}>;
|
|
216
|
+
createProduct(data: {
|
|
217
|
+
name: string;
|
|
218
|
+
slug?: string;
|
|
219
|
+
description?: string;
|
|
220
|
+
}): Promise<{
|
|
221
|
+
product: {
|
|
222
|
+
id: string;
|
|
223
|
+
slug: string;
|
|
224
|
+
name: string;
|
|
225
|
+
description: string | null;
|
|
226
|
+
created_at: string;
|
|
227
|
+
updated_at: string;
|
|
228
|
+
};
|
|
229
|
+
}>;
|
|
230
|
+
deleteProduct(slug: string): Promise<{
|
|
231
|
+
success: boolean;
|
|
232
|
+
}>;
|
|
233
|
+
deleteProductFile(slug: string, filepath: string): Promise<{
|
|
234
|
+
success: boolean;
|
|
235
|
+
}>;
|
|
236
|
+
getProductIndex(slug: string, domain?: string): Promise<{
|
|
237
|
+
product_slug: string;
|
|
238
|
+
updated_at: string;
|
|
239
|
+
files: Array<{
|
|
240
|
+
path: string;
|
|
241
|
+
title: string;
|
|
242
|
+
summary: string;
|
|
243
|
+
sections: string[];
|
|
244
|
+
domain: string;
|
|
245
|
+
content_type: string;
|
|
246
|
+
size: number;
|
|
247
|
+
updated_at: string;
|
|
248
|
+
}>;
|
|
249
|
+
}>;
|
|
250
|
+
pullProductFiles(slug: string, options?: {
|
|
251
|
+
files?: string[];
|
|
252
|
+
include_knowledge?: boolean;
|
|
253
|
+
}): Promise<{
|
|
254
|
+
files: PulledFile[];
|
|
255
|
+
}>;
|
|
256
|
+
addBacklogEntry(data: {
|
|
257
|
+
content: string;
|
|
258
|
+
product_slug?: string;
|
|
259
|
+
source?: string;
|
|
260
|
+
source_context?: string;
|
|
261
|
+
domain_hint?: string;
|
|
262
|
+
tags?: string[];
|
|
263
|
+
}): Promise<{
|
|
264
|
+
entry: BacklogEntry;
|
|
265
|
+
}>;
|
|
266
|
+
listBacklog(options?: {
|
|
267
|
+
product_slug?: string;
|
|
268
|
+
status?: string;
|
|
269
|
+
limit?: number;
|
|
270
|
+
offset?: number;
|
|
271
|
+
}): Promise<{
|
|
272
|
+
entries: BacklogEntry[];
|
|
273
|
+
total: number;
|
|
274
|
+
}>;
|
|
275
|
+
deleteBacklogEntry(id: string): Promise<{
|
|
276
|
+
success: boolean;
|
|
277
|
+
}>;
|
|
278
|
+
processBacklog(options?: {
|
|
279
|
+
product_slug?: string;
|
|
280
|
+
dry_run?: boolean;
|
|
281
|
+
}): Promise<unknown>;
|
|
171
282
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -16,7 +16,11 @@ export class ApiClient {
|
|
|
16
16
|
});
|
|
17
17
|
if (!response.ok) {
|
|
18
18
|
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
19
|
-
|
|
19
|
+
const message = error.error || `HTTP ${response.status}`;
|
|
20
|
+
if (response.status === 401) {
|
|
21
|
+
throw new Error(`${message}. Reporter token rejected. Generate or rotate yours at ${this.config.apiUrl.replace('reporter.44pixels.workers.dev', 'reporter-directory.pages.dev')} → Settings, then update REPORTER_API_KEY in your Claude config and restart Claude Code.`);
|
|
22
|
+
}
|
|
23
|
+
throw new Error(message);
|
|
20
24
|
}
|
|
21
25
|
return response.json();
|
|
22
26
|
}
|
|
@@ -91,11 +95,19 @@ export class ApiClient {
|
|
|
91
95
|
});
|
|
92
96
|
}
|
|
93
97
|
// --- Product Knowledge ---
|
|
94
|
-
async listProducts() {
|
|
95
|
-
|
|
98
|
+
async listProducts(options) {
|
|
99
|
+
const params = new URLSearchParams();
|
|
100
|
+
for (const [key, value] of Object.entries(options ?? {})) {
|
|
101
|
+
if (value !== undefined) {
|
|
102
|
+
params.set(key, String(value));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const query = params.toString();
|
|
106
|
+
return this.request(`/api/products${query ? `?${query}` : ''}`);
|
|
96
107
|
}
|
|
97
|
-
async getProduct(slug) {
|
|
98
|
-
|
|
108
|
+
async getProduct(slug, domain) {
|
|
109
|
+
const query = domain ? `?domain=${encodeURIComponent(domain)}` : '';
|
|
110
|
+
return this.request(`/api/products/${slug}${query}`);
|
|
99
111
|
}
|
|
100
112
|
async getProductFile(slug, filepath) {
|
|
101
113
|
return this.request(`/api/products/${slug}/files/${filepath}`);
|
|
@@ -112,4 +124,69 @@ export class ApiClient {
|
|
|
112
124
|
body: JSON.stringify({ files }),
|
|
113
125
|
});
|
|
114
126
|
}
|
|
127
|
+
async getProductHealth(slug) {
|
|
128
|
+
return this.request(`/api/products/${slug}/health`);
|
|
129
|
+
}
|
|
130
|
+
async getAllProductsHealth() {
|
|
131
|
+
return this.request('/api/products/health');
|
|
132
|
+
}
|
|
133
|
+
async createProduct(data) {
|
|
134
|
+
return this.request('/api/products', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
body: JSON.stringify(data),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async deleteProduct(slug) {
|
|
140
|
+
return this.request(`/api/products/${slug}`, {
|
|
141
|
+
method: 'DELETE',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async deleteProductFile(slug, filepath) {
|
|
145
|
+
return this.request(`/api/products/${slug}/files/${filepath}`, {
|
|
146
|
+
method: 'DELETE',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async getProductIndex(slug, domain) {
|
|
150
|
+
const query = domain ? `?domain=${encodeURIComponent(domain)}` : '';
|
|
151
|
+
return this.request(`/api/products/${slug}/index${query}`);
|
|
152
|
+
}
|
|
153
|
+
async pullProductFiles(slug, options) {
|
|
154
|
+
return this.request(`/api/products/${slug}/pull`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: JSON.stringify(options ?? {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// --- Agent Backlog ---
|
|
160
|
+
async addBacklogEntry(data) {
|
|
161
|
+
return this.request('/api/backlog', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify(data),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async listBacklog(options) {
|
|
167
|
+
const params = new URLSearchParams();
|
|
168
|
+
for (const [key, value] of Object.entries(options ?? {})) {
|
|
169
|
+
if (value !== undefined) {
|
|
170
|
+
params.set(key, String(value));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const query = params.toString();
|
|
174
|
+
return this.request(`/api/backlog${query ? `?${query}` : ''}`);
|
|
175
|
+
}
|
|
176
|
+
async deleteBacklogEntry(id) {
|
|
177
|
+
return this.request(`/api/backlog/${id}`, {
|
|
178
|
+
method: 'DELETE',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async processBacklog(options) {
|
|
182
|
+
const params = new URLSearchParams();
|
|
183
|
+
if (options?.product_slug)
|
|
184
|
+
params.set('product_slug', options.product_slug);
|
|
185
|
+
if (options?.dry_run !== undefined)
|
|
186
|
+
params.set('dry_run', String(options.dry_run));
|
|
187
|
+
const query = params.toString();
|
|
188
|
+
return this.request(`/api/backlog/process${query ? `?${query}` : ''}`, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
115
192
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,9 @@ import { deployReportSchema, updateReportSchema, createDeployTool, createUpdateT
|
|
|
8
8
|
import { listReportsSchema, getReportSchema, deleteReportSchema, createListTool, createGetTool, createDeleteTool, } from './tools/reports.js';
|
|
9
9
|
import { pullContextSchema, createPullTool, } from './tools/pull.js';
|
|
10
10
|
import { manageFoldersSchema, createManageFoldersTool, } from './tools/folders.js';
|
|
11
|
-
import { listProductsSchema, getProductSchema, readProductFileSchema, updateProductKnowledgeSchema, uploadProductFilesSchema, pullProductFilesSchema, createListProductsTool, createGetProductTool, createReadProductFileTool, createUpdateProductKnowledgeTool, createUploadProductFilesTool, createPullProductFilesTool, } from './tools/products.js';
|
|
11
|
+
import { listProductsSchema, getProductSchema, readProductFileSchema, updateProductKnowledgeSchema, uploadProductFilesSchema, pullProductFilesSchema, createProductSchema, deleteProductSchema, deleteProductFileSchema, getProductHealthSchema, getProductIndexSchema, createListProductsTool, createGetProductTool, createReadProductFileTool, createUpdateProductKnowledgeTool, createUploadProductFilesTool, createPullProductFilesTool, createGetProductHealthTool, createGetProductIndexTool, createCreateProductTool, createDeleteProductTool, createDeleteProductFileTool, } from './tools/products.js';
|
|
12
|
+
import { createUploadProductDirectoryTool, uploadProductDirectorySchema, UPLOAD_DIRECTORY_DESCRIPTION, } from './tools/upload-directory.js';
|
|
13
|
+
import { addToBacklogSchema, listBacklogSchema, processBacklogSchema, createAddToBacklogTool, createListBacklogTool, createProcessBacklogTool, } from './tools/backlog.js';
|
|
12
14
|
const config = loadConfig();
|
|
13
15
|
const client = new ApiClient(config);
|
|
14
16
|
function prop(type, description, extra = {}) {
|
|
@@ -139,18 +141,24 @@ const toolDefinitions = [
|
|
|
139
141
|
},
|
|
140
142
|
{
|
|
141
143
|
name: 'list_products',
|
|
142
|
-
description: 'List all
|
|
143
|
-
inputSchema: {
|
|
144
|
+
description: 'List all products in the knowledge repository with their knowledge file inventory. Call this first to discover available products and which knowledge files are already populated.',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
search: prop('string', 'Search term to filter products by name or description'),
|
|
149
|
+
},
|
|
150
|
+
},
|
|
144
151
|
schema: listProductsSchema,
|
|
145
152
|
handler: createListProductsTool(client),
|
|
146
153
|
},
|
|
147
154
|
{
|
|
148
155
|
name: 'get_product',
|
|
149
|
-
description: 'Get a product\'s complete knowledge — returns
|
|
156
|
+
description: 'Get a product\'s complete knowledge — returns all JSON config files (meta-ads.json, cpps.json, brand-config.json) fully parsed, plus listings of markdown docs and binary assets. This is the primary tool to read before generating creative work, writing ad copy, or configuring campaigns. For narrative knowledge (brand voice, product positioning, research), use get_product_index to find the right markdown doc, then read_product_file to get its content.\n\nOptionally filter by domain to get only files relevant to a specific area: product-research, brand, copy, advertising, or app-store. Note: JSON config files are domain-scoped (meta-ads.json → advertising, brand-config.json → brand, cpps.json → app-store), so domains without a config file will return an empty knowledge object.',
|
|
150
157
|
inputSchema: {
|
|
151
158
|
type: 'object',
|
|
152
159
|
properties: {
|
|
153
160
|
slug: prop('string', 'Product slug'),
|
|
161
|
+
domain: prop('string', 'Filter files by domain', { enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'] }),
|
|
154
162
|
},
|
|
155
163
|
required: ['slug'],
|
|
156
164
|
},
|
|
@@ -159,12 +167,12 @@ const toolDefinitions = [
|
|
|
159
167
|
},
|
|
160
168
|
{
|
|
161
169
|
name: 'read_product_file',
|
|
162
|
-
description: 'Read a single file from a product by path. Use get_product to read all knowledge files at once (more efficient). Use read_product_file only when you need one specific file —
|
|
170
|
+
description: 'Read a single file from a product by path. Use get_product to read all knowledge files at once (more efficient). Use read_product_file only when you need one specific file — works for markdown docs, JSON configs, and binary files (logos, PDFs returned as base64 data URIs).\n\nTypical workflow: get_product_index → find the file you need → read_product_file.',
|
|
163
171
|
inputSchema: {
|
|
164
172
|
type: 'object',
|
|
165
173
|
properties: {
|
|
166
174
|
slug: prop('string', 'Product slug'),
|
|
167
|
-
filepath: prop('string', 'File path within the product (e.g., "meta-ads.json", "brand-assets/logo.png")'),
|
|
175
|
+
filepath: prop('string', 'File path within the product (e.g., "docs/research/user-research.md", "meta-ads.json", "brand-assets/logo.png")'),
|
|
168
176
|
},
|
|
169
177
|
required: ['slug', 'filepath'],
|
|
170
178
|
},
|
|
@@ -173,12 +181,12 @@ const toolDefinitions = [
|
|
|
173
181
|
},
|
|
174
182
|
{
|
|
175
183
|
name: 'update_product_knowledge',
|
|
176
|
-
description: 'Write or update product knowledge using deep merge-patch — only the keys you send are changed, all other data is preserved.
|
|
184
|
+
description: 'Write or update product knowledge JSON config using deep merge-patch — only the keys you send are changed, all other data is preserved. Use this for programmatic config files that agents plug into API calls.\n\nConfig files per product:\n- meta-ads.json — Meta account IDs, pixels, creative test config\n- cpps.json — CPP motivations and custom product page config\n- brand-config.json — brand color palette hex values, font families\n\nFor narrative knowledge (product info, brand voice, copy, research), create or update Markdown docs using upload_product_files instead. Place docs in the correct domain prefix:\n- docs/research/ — user research, competitive analysis, hypotheses, GTM strategy, roadmap\n- docs/brand/ — brand voice, positioning\n- docs/copy/ — copy guidelines, messaging\n- docs/advertising/ — campaign briefs, ad copy\n- docs/app-store/ — store listing copy, metadata\n\nAlways pass updated_by with your agent name so humans can see who wrote each update.',
|
|
177
185
|
inputSchema: {
|
|
178
186
|
type: 'object',
|
|
179
187
|
properties: {
|
|
180
188
|
slug: prop('string', 'Product slug'),
|
|
181
|
-
filepath: prop('string', '
|
|
189
|
+
filepath: prop('string', 'Config file to update (e.g., "meta-ads.json", "cpps.json", "brand-config.json")'),
|
|
182
190
|
content: prop('object', 'JSON object to deep merge-patch into the knowledge file. Use null values to delete keys.'),
|
|
183
191
|
updated_by: prop('string', 'Agent name or ID writing the update (optional)'),
|
|
184
192
|
},
|
|
@@ -189,7 +197,12 @@ const toolDefinitions = [
|
|
|
189
197
|
},
|
|
190
198
|
{
|
|
191
199
|
name: 'upload_product_files',
|
|
192
|
-
description: 'Upload
|
|
200
|
+
description: 'Upload one or more files to a product. For multiple files in a directory, prefer ' +
|
|
201
|
+
'upload_product_directory.\n\n' +
|
|
202
|
+
'Place files at media/<domain>/ for binaries, docs/<domain>/ for markdown:\n' +
|
|
203
|
+
' media/brand/, media/product-research/, media/copy/, media/advertising/, media/app-store/\n' +
|
|
204
|
+
' docs/brand/, docs/research/, docs/copy/, docs/advertising/, docs/app-store/\n\n' +
|
|
205
|
+
'Pass content as a LOCAL PATH (preferred), a base64 data URI, or plain text.',
|
|
193
206
|
inputSchema: {
|
|
194
207
|
type: 'object',
|
|
195
208
|
properties: {
|
|
@@ -211,21 +224,161 @@ const toolDefinitions = [
|
|
|
211
224
|
schema: uploadProductFilesSchema,
|
|
212
225
|
handler: createUploadProductFilesTool(client),
|
|
213
226
|
},
|
|
227
|
+
{
|
|
228
|
+
name: 'upload_product_directory',
|
|
229
|
+
description: UPLOAD_DIRECTORY_DESCRIPTION,
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
slug: prop('string', 'Product slug'),
|
|
234
|
+
local_dir: prop('string', 'Absolute or relative path to the local directory to upload'),
|
|
235
|
+
domain: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'],
|
|
238
|
+
description: 'Product domain — determines target folder (media/<domain>/)',
|
|
239
|
+
},
|
|
240
|
+
dest_prefix: prop('string', 'Subpath under media/<domain>/. Defaults to basename of local_dir.'),
|
|
241
|
+
glob: prop('string', 'Include filter, e.g. "**/*.{png,jpg,mp4}". Default: "**/*".'),
|
|
242
|
+
exclude: prop('array', 'Exclude patterns. Default: [".*", "**/.*", "node_modules/**"].', {
|
|
243
|
+
items: { type: 'string' },
|
|
244
|
+
}),
|
|
245
|
+
dry_run: prop('boolean', 'If true, returns the planned uploads without sending. Default: false.'),
|
|
246
|
+
},
|
|
247
|
+
required: ['slug', 'local_dir', 'domain'],
|
|
248
|
+
},
|
|
249
|
+
schema: uploadProductDirectorySchema,
|
|
250
|
+
handler: createUploadProductDirectoryTool(client),
|
|
251
|
+
},
|
|
214
252
|
{
|
|
215
253
|
name: 'pull_product_files',
|
|
216
|
-
description: 'Download
|
|
254
|
+
description: 'Download files from a product to a local directory. By default pulls binary assets (logos, screenshots, videos). Set include_knowledge to also pull JSON config files. To pull markdown docs, specify their paths in the files array (e.g., ["docs/research/user-research.md"]).',
|
|
217
255
|
inputSchema: {
|
|
218
256
|
type: 'object',
|
|
219
257
|
properties: {
|
|
220
258
|
slug: prop('string', 'Product slug'),
|
|
221
259
|
output_dir: prop('string', 'Local directory to write files to. Will be created if needed.'),
|
|
222
260
|
files: prop('array', 'Specific file paths to pull. If omitted, all binary files are pulled.', { items: { type: 'string' } }),
|
|
261
|
+
include_knowledge: prop('boolean', 'Include knowledge JSON files in addition to binary files. Default: false.'),
|
|
223
262
|
},
|
|
224
263
|
required: ['slug', 'output_dir'],
|
|
225
264
|
},
|
|
226
265
|
schema: pullProductFilesSchema,
|
|
227
266
|
handler: createPullProductFilesTool(client),
|
|
228
267
|
},
|
|
268
|
+
{
|
|
269
|
+
name: 'get_product_health',
|
|
270
|
+
description: 'Get knowledge completeness scores for a product or all products. Scores both JSON config files (field-level completeness) and expected markdown docs (presence check). Shows missing required/optional fields and missing docs, with an overall health score (0-100). Use to identify gaps before generating content.',
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
slug: prop('string', 'Product slug. If omitted, returns health summary for all products.'),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
schema: getProductHealthSchema,
|
|
278
|
+
handler: createGetProductHealthTool(client),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'get_product_index',
|
|
282
|
+
description: 'Get a lightweight file index for a product — a table of contents listing every file with its title, summary, sections, domain, and size. Use this FIRST to discover what knowledge is available before pulling specific files. Much faster than get_product since it returns metadata only, not file contents.\n\nTypical workflow: list_products → get_product_index → read_product_file (for specific docs) or get_product (for all JSON configs at once).\n\nOptionally filter by domain to see only files in a specific area: product-research, brand, copy, advertising, or app-store.',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
slug: prop('string', 'Product slug'),
|
|
287
|
+
domain: prop('string', 'Filter index entries by domain', { enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'] }),
|
|
288
|
+
},
|
|
289
|
+
required: ['slug'],
|
|
290
|
+
},
|
|
291
|
+
schema: getProductIndexSchema,
|
|
292
|
+
handler: createGetProductIndexTool(client),
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'create_product',
|
|
296
|
+
description: 'Create a new product in the knowledge repository. The slug is auto-generated from the name if not provided. Use this before uploading knowledge files or assets.',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
name: prop('string', 'Product name'),
|
|
301
|
+
slug: prop('string', 'URL-friendly slug (auto-generated from name if not provided)'),
|
|
302
|
+
description: prop('string', 'Product description'),
|
|
303
|
+
},
|
|
304
|
+
required: ['name'],
|
|
305
|
+
},
|
|
306
|
+
schema: createProductSchema,
|
|
307
|
+
handler: createCreateProductTool(client),
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'delete_product',
|
|
311
|
+
description: 'Delete a product and all its associated knowledge files and assets from the repository. This action is irreversible.',
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: 'object',
|
|
314
|
+
properties: {
|
|
315
|
+
slug: prop('string', 'Product slug to delete'),
|
|
316
|
+
},
|
|
317
|
+
required: ['slug'],
|
|
318
|
+
},
|
|
319
|
+
schema: deleteProductSchema,
|
|
320
|
+
handler: createDeleteProductTool(client),
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'delete_product_file',
|
|
324
|
+
description: 'Delete a single file from a product. Use for removing outdated assets or resetting a knowledge file.',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
slug: prop('string', 'Product slug'),
|
|
329
|
+
filepath: prop('string', 'File path within the product to delete (e.g., "brand-assets/old-logo.png")'),
|
|
330
|
+
},
|
|
331
|
+
required: ['slug', 'filepath'],
|
|
332
|
+
},
|
|
333
|
+
schema: deleteProductFileSchema,
|
|
334
|
+
handler: createDeleteProductFileTool(client),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'add_to_backlog',
|
|
338
|
+
description: 'Add an unstructured observation to the agent backlog for later processing. Low-friction way for any agent to contribute signals — competitor insights, user feedback, research findings, or any data that should eventually enrich product knowledge. Only content is required; everything else is optional.\n\nIf you know which product this relates to, pass product_slug. Otherwise, omit it — the backlog processor will auto-route it to the right product (or reject it as noise).',
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: 'object',
|
|
341
|
+
properties: {
|
|
342
|
+
content: prop('string', 'The observation or data (100-2000 chars typical). Be specific — include sources, numbers, quotes when available.'),
|
|
343
|
+
product_slug: prop('string', 'Product slug if known. Omit for global entries that will be auto-routed.'),
|
|
344
|
+
source: prop('string', 'Your agent name or ID (e.g., "research-agent", "competitor-monitor")'),
|
|
345
|
+
source_context: prop('string', 'Context slug this observation came from, if applicable'),
|
|
346
|
+
domain_hint: prop('string', 'Knowledge domain hint', { enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'] }),
|
|
347
|
+
tags: prop('array', 'Free-form tags for categorization', { items: { type: 'string' } }),
|
|
348
|
+
},
|
|
349
|
+
required: ['content'],
|
|
350
|
+
},
|
|
351
|
+
schema: addToBacklogSchema,
|
|
352
|
+
handler: createAddToBacklogTool(client),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'list_backlog',
|
|
356
|
+
description: 'List backlog entries — inspect the queue of pending observations waiting to be processed into product knowledge. Filter by product, status, or both.',
|
|
357
|
+
inputSchema: {
|
|
358
|
+
type: 'object',
|
|
359
|
+
properties: {
|
|
360
|
+
product_slug: prop('string', 'Filter by product slug. Use "global" for unrouted entries without a product.'),
|
|
361
|
+
status: prop('string', 'Filter by status', { enum: ['pending', 'processing', 'applied', 'rejected', 'partial'] }),
|
|
362
|
+
limit: prop('number', 'Maximum number of results (default: 50)'),
|
|
363
|
+
offset: prop('number', 'Offset for pagination'),
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
schema: listBacklogSchema,
|
|
367
|
+
handler: createListBacklogTool(client),
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'process_backlog',
|
|
371
|
+
description: 'Trigger backlog processing — routes global entries to products, then uses LLM to triage and apply changes to product knowledge (JSON configs and markdown docs). Defaults to dry_run=true for safety.\n\nProcessing stages:\n1. Route: Global entries (no product_slug) are matched to products or rejected\n2. Process: Per-product entries are classified and generate JSON patches or markdown operations\n3. Apply: Only high-confidence changes are auto-applied; medium/low are marked as partial with suggestions',
|
|
372
|
+
inputSchema: {
|
|
373
|
+
type: 'object',
|
|
374
|
+
properties: {
|
|
375
|
+
product_slug: prop('string', 'Process entries for a specific product only. Omit to process all products including global routing.'),
|
|
376
|
+
dry_run: prop('boolean', 'Preview changes without applying them. Default: true.'),
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
schema: processBacklogSchema,
|
|
380
|
+
handler: createProcessBacklogTool(client),
|
|
381
|
+
},
|
|
229
382
|
];
|
|
230
383
|
const toolHandlers = Object.fromEntries(toolDefinitions.map((t) => [t.name, { schema: t.schema, handler: t.handler }]));
|
|
231
384
|
const server = new Server({ name: '44reports', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ApiClient } from '../api-client.js';
|
|
3
|
+
export declare const addToBacklogSchema: z.ZodObject<{
|
|
4
|
+
content: z.ZodString;
|
|
5
|
+
product_slug: z.ZodOptional<z.ZodString>;
|
|
6
|
+
source: z.ZodOptional<z.ZodString>;
|
|
7
|
+
source_context: z.ZodOptional<z.ZodString>;
|
|
8
|
+
domain_hint: z.ZodOptional<z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>>;
|
|
9
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
content: string;
|
|
12
|
+
product_slug?: string | undefined;
|
|
13
|
+
tags?: string[] | undefined;
|
|
14
|
+
source?: string | undefined;
|
|
15
|
+
source_context?: string | undefined;
|
|
16
|
+
domain_hint?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
content: string;
|
|
19
|
+
product_slug?: string | undefined;
|
|
20
|
+
tags?: string[] | undefined;
|
|
21
|
+
source?: string | undefined;
|
|
22
|
+
source_context?: string | undefined;
|
|
23
|
+
domain_hint?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const listBacklogSchema: z.ZodObject<{
|
|
26
|
+
product_slug: z.ZodOptional<z.ZodString>;
|
|
27
|
+
status: z.ZodOptional<z.ZodEnum<["pending", "processing", "applied", "rejected", "partial"]>>;
|
|
28
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
status?: "pending" | "processing" | "applied" | "rejected" | "partial" | undefined;
|
|
32
|
+
limit?: number | undefined;
|
|
33
|
+
offset?: number | undefined;
|
|
34
|
+
product_slug?: string | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
status?: "pending" | "processing" | "applied" | "rejected" | "partial" | undefined;
|
|
37
|
+
limit?: number | undefined;
|
|
38
|
+
offset?: number | undefined;
|
|
39
|
+
product_slug?: string | undefined;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const processBacklogSchema: z.ZodObject<{
|
|
42
|
+
product_slug: z.ZodOptional<z.ZodString>;
|
|
43
|
+
dry_run: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
dry_run: boolean;
|
|
46
|
+
product_slug?: string | undefined;
|
|
47
|
+
}, {
|
|
48
|
+
product_slug?: string | undefined;
|
|
49
|
+
dry_run?: boolean | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function createAddToBacklogTool(client: ApiClient): (input: z.infer<typeof addToBacklogSchema>) => Promise<{
|
|
52
|
+
entry: import("../api-client.js").BacklogEntry;
|
|
53
|
+
}>;
|
|
54
|
+
export declare function createListBacklogTool(client: ApiClient): (input: z.infer<typeof listBacklogSchema>) => Promise<{
|
|
55
|
+
entries: import("../api-client.js").BacklogEntry[];
|
|
56
|
+
total: number;
|
|
57
|
+
}>;
|
|
58
|
+
export declare function createProcessBacklogTool(client: ApiClient): (input: z.infer<typeof processBacklogSchema>) => Promise<unknown>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const DOMAIN_VALUES = ['product-research', 'brand', 'copy', 'advertising', 'app-store'];
|
|
3
|
+
const STATUS_VALUES = ['pending', 'processing', 'applied', 'rejected', 'partial'];
|
|
4
|
+
// --- Schemas ---
|
|
5
|
+
export const addToBacklogSchema = z.object({
|
|
6
|
+
content: z.string().describe('The observation or data to add to the backlog'),
|
|
7
|
+
product_slug: z.string().optional().describe('Product slug — omit for global entries that will be auto-routed'),
|
|
8
|
+
source: z.string().optional().describe('Agent name or ID submitting this entry'),
|
|
9
|
+
source_context: z.string().optional().describe('Context slug this observation came from'),
|
|
10
|
+
domain_hint: z.enum(DOMAIN_VALUES).optional().describe('Knowledge domain hint'),
|
|
11
|
+
tags: z.array(z.string()).optional().describe('Free-form tags for categorization'),
|
|
12
|
+
});
|
|
13
|
+
export const listBacklogSchema = z.object({
|
|
14
|
+
product_slug: z.string().optional().describe('Filter by product slug. Use "global" for unrouted entries.'),
|
|
15
|
+
status: z.enum(STATUS_VALUES).optional().describe('Filter by status'),
|
|
16
|
+
limit: z.number().optional().describe('Maximum number of results (default: 50)'),
|
|
17
|
+
offset: z.number().optional().describe('Offset for pagination'),
|
|
18
|
+
});
|
|
19
|
+
export const processBacklogSchema = z.object({
|
|
20
|
+
product_slug: z.string().optional().describe('Process entries for a specific product. Omit to process all including global routing.'),
|
|
21
|
+
dry_run: z.boolean().optional().default(true).describe('Preview changes without applying. Default: true.'),
|
|
22
|
+
});
|
|
23
|
+
// --- Tool factories ---
|
|
24
|
+
export function createAddToBacklogTool(client) {
|
|
25
|
+
return async (input) => {
|
|
26
|
+
return client.addBacklogEntry(input);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function createListBacklogTool(client) {
|
|
30
|
+
return async (input) => {
|
|
31
|
+
return client.listBacklog({
|
|
32
|
+
product_slug: input.product_slug,
|
|
33
|
+
status: input.status,
|
|
34
|
+
limit: input.limit,
|
|
35
|
+
offset: input.offset,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function createProcessBacklogTool(client) {
|
|
40
|
+
return async (input) => {
|
|
41
|
+
return client.processBacklog({
|
|
42
|
+
product_slug: input.product_slug,
|
|
43
|
+
dry_run: input.dry_run,
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|