44reports-mcp 1.0.8 → 1.1.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 +52 -1
- package/dist/api-client.js +22 -0
- package/dist/index.js +95 -4
- package/dist/tools/folders.d.ts +3 -0
- package/dist/tools/folders.js +9 -4
- package/dist/tools/products.d.ts +132 -0
- package/dist/tools/products.js +124 -0
- package/package.json +1 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface Folder {
|
|
|
52
52
|
name: string;
|
|
53
53
|
description: string | null;
|
|
54
54
|
allowed_domains: string[];
|
|
55
|
+
allowed_emails: string[];
|
|
55
56
|
created_at: string;
|
|
56
57
|
updated_at: string;
|
|
57
58
|
}
|
|
@@ -92,7 +93,8 @@ export declare class ApiClient {
|
|
|
92
93
|
name: string;
|
|
93
94
|
slug?: string;
|
|
94
95
|
description?: string;
|
|
95
|
-
allowed_domains
|
|
96
|
+
allowed_domains?: string[];
|
|
97
|
+
allowed_emails?: string[];
|
|
96
98
|
}): Promise<{
|
|
97
99
|
folder: Folder;
|
|
98
100
|
}>;
|
|
@@ -104,6 +106,7 @@ export declare class ApiClient {
|
|
|
104
106
|
name?: string;
|
|
105
107
|
description?: string;
|
|
106
108
|
allowed_domains?: string[];
|
|
109
|
+
allowed_emails?: string[];
|
|
107
110
|
}): Promise<{
|
|
108
111
|
folder: Folder;
|
|
109
112
|
}>;
|
|
@@ -117,4 +120,52 @@ export declare class ApiClient {
|
|
|
117
120
|
removeReportFromFolder(folderSlug: string, reportSlug: string): Promise<{
|
|
118
121
|
success: boolean;
|
|
119
122
|
}>;
|
|
123
|
+
listProducts(): Promise<{
|
|
124
|
+
products: Array<{
|
|
125
|
+
id: string;
|
|
126
|
+
slug: string;
|
|
127
|
+
name: string;
|
|
128
|
+
description: string | null;
|
|
129
|
+
json_file_count: number;
|
|
130
|
+
binary_file_count: number;
|
|
131
|
+
}>;
|
|
132
|
+
}>;
|
|
133
|
+
getProduct(slug: string): Promise<{
|
|
134
|
+
product: {
|
|
135
|
+
id: string;
|
|
136
|
+
slug: string;
|
|
137
|
+
name: string;
|
|
138
|
+
description: string | null;
|
|
139
|
+
created_at: string;
|
|
140
|
+
updated_at: string;
|
|
141
|
+
};
|
|
142
|
+
knowledge: Record<string, unknown>;
|
|
143
|
+
binary_files: Array<{
|
|
144
|
+
path: string;
|
|
145
|
+
size: number;
|
|
146
|
+
content_type: string;
|
|
147
|
+
}>;
|
|
148
|
+
}>;
|
|
149
|
+
getProductFile(slug: string, filepath: string): Promise<{
|
|
150
|
+
path: string;
|
|
151
|
+
content: unknown;
|
|
152
|
+
content_type: string;
|
|
153
|
+
size?: number;
|
|
154
|
+
}>;
|
|
155
|
+
updateProductFile(slug: string, filepath: string, body: {
|
|
156
|
+
content: unknown;
|
|
157
|
+
updated_by?: string;
|
|
158
|
+
}): Promise<{
|
|
159
|
+
success: boolean;
|
|
160
|
+
path: string;
|
|
161
|
+
merged?: unknown;
|
|
162
|
+
}>;
|
|
163
|
+
uploadProductFiles(slug: string, files: Array<{
|
|
164
|
+
path: string;
|
|
165
|
+
content: string;
|
|
166
|
+
content_type?: string;
|
|
167
|
+
}>): Promise<{
|
|
168
|
+
success: boolean;
|
|
169
|
+
uploaded: string[];
|
|
170
|
+
}>;
|
|
120
171
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -90,4 +90,26 @@ export class ApiClient {
|
|
|
90
90
|
method: 'DELETE',
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
+
// --- Product Knowledge ---
|
|
94
|
+
async listProducts() {
|
|
95
|
+
return this.request('/api/products');
|
|
96
|
+
}
|
|
97
|
+
async getProduct(slug) {
|
|
98
|
+
return this.request(`/api/products/${slug}`);
|
|
99
|
+
}
|
|
100
|
+
async getProductFile(slug, filepath) {
|
|
101
|
+
return this.request(`/api/products/${slug}/files/${filepath}`);
|
|
102
|
+
}
|
|
103
|
+
async updateProductFile(slug, filepath, body) {
|
|
104
|
+
return this.request(`/api/products/${slug}/files/${filepath}`, {
|
|
105
|
+
method: 'PUT',
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async uploadProductFiles(slug, files) {
|
|
110
|
+
return this.request(`/api/products/${slug}/files`, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
body: JSON.stringify({ files }),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
93
115
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ 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
12
|
const config = loadConfig();
|
|
12
13
|
const client = new ApiClient(config);
|
|
13
14
|
function prop(type, description, extra = {}) {
|
|
@@ -18,7 +19,7 @@ const FOLDER_PATH_DESC = 'RECOMMENDED for most contexts. Uploads all supported f
|
|
|
18
19
|
const toolDefinitions = [
|
|
19
20
|
{
|
|
20
21
|
name: 'deploy_context',
|
|
21
|
-
description: 'Deploy
|
|
22
|
+
description: 'Deploy an output artifact to the context repository — use for one-time or versioned outputs: research reports, data analysis, dashboards, HTML visualizations, CSV exports, agent session summaries.\n\nIMPORTANT: Do NOT use this for brand or product knowledge (guidelines, copy, ad accounts, CPPs, product positioning). For persistent product knowledge that agents read and update over time, use update_product_knowledge instead.',
|
|
22
23
|
inputSchema: {
|
|
23
24
|
type: 'object',
|
|
24
25
|
properties: {
|
|
@@ -40,7 +41,7 @@ const toolDefinitions = [
|
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
43
|
name: 'update_context',
|
|
43
|
-
description: 'Update an existing context. The URL remains stable. You can update metadata, content, or both.',
|
|
44
|
+
description: 'Update an existing output artifact context. The URL remains stable. You can update metadata, content, or both.\n\nIMPORTANT: Do NOT use this for brand or product knowledge. Use update_product_knowledge for product profiles, brand guidelines, copy, and ad accounts.',
|
|
44
45
|
inputSchema: {
|
|
45
46
|
type: 'object',
|
|
46
47
|
properties: {
|
|
@@ -116,7 +117,7 @@ const toolDefinitions = [
|
|
|
116
117
|
},
|
|
117
118
|
{
|
|
118
119
|
name: 'manage_folders',
|
|
119
|
-
description: 'Manage folders for
|
|
120
|
+
description: 'Manage folders for access control. Folders let you share contexts with external users by email domain (e.g., vgames.vc) or individual email address (e.g., investor@gmail.com).',
|
|
120
121
|
inputSchema: {
|
|
121
122
|
type: 'object',
|
|
122
123
|
properties: {
|
|
@@ -126,7 +127,8 @@ const toolDefinitions = [
|
|
|
126
127
|
slug: prop('string', 'Folder slug (required for get/update/delete/add_reports/remove_report)'),
|
|
127
128
|
name: prop('string', 'Folder name (required for create)'),
|
|
128
129
|
description: prop('string', 'Folder description'),
|
|
129
|
-
allowed_domains: prop('array', 'Email domains that can access this folder
|
|
130
|
+
allowed_domains: prop('array', 'Email domains that can access this folder', { items: { type: 'string' } }),
|
|
131
|
+
allowed_emails: prop('array', 'Individual email addresses that can access this folder', { items: { type: 'string' } }),
|
|
130
132
|
report_slugs: prop('array', 'Report slugs to add to folder (for add_reports)', { items: { type: 'string' } }),
|
|
131
133
|
report_slug: prop('string', 'Report slug to remove (for remove_report)'),
|
|
132
134
|
},
|
|
@@ -135,6 +137,95 @@ const toolDefinitions = [
|
|
|
135
137
|
schema: manageFoldersSchema,
|
|
136
138
|
handler: createManageFoldersTool(client),
|
|
137
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: 'list_products',
|
|
142
|
+
description: 'List all 44pixels products in the knowledge repository (Cue, Deep, Pixi, Clara, Vivi, Wordcast, etc.) with their knowledge file inventory. Call this first to discover available products and which knowledge files are already populated.',
|
|
143
|
+
inputSchema: { type: 'object', properties: {} },
|
|
144
|
+
schema: listProductsSchema,
|
|
145
|
+
handler: createListProductsTool(client),
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'get_product',
|
|
149
|
+
description: 'Get a product\'s complete knowledge — returns ALL JSON knowledge files (product.json, brand.json, copy.json, meta-ads.json, cpps.json) fully parsed, plus a listing of binary assets. This is the primary tool to read before generating creative work, writing ad copy, or configuring campaigns — it gives you brand voice, product positioning, hooks, and ad account IDs in one call.',
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
slug: prop('string', 'Product slug'),
|
|
154
|
+
},
|
|
155
|
+
required: ['slug'],
|
|
156
|
+
},
|
|
157
|
+
schema: getProductSchema,
|
|
158
|
+
handler: createGetProductTool(client),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
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 — especially useful for binary files (logos, PDFs) returned as base64 data URIs.',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
slug: prop('string', 'Product slug'),
|
|
167
|
+
filepath: prop('string', 'File path within the product (e.g., "meta-ads.json", "brand-assets/logo.png")'),
|
|
168
|
+
},
|
|
169
|
+
required: ['slug', 'filepath'],
|
|
170
|
+
},
|
|
171
|
+
schema: readProductFileSchema,
|
|
172
|
+
handler: createReadProductFileTool(client),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
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. This is the canonical way to store brand knowledge: ALWAYS use this (not deploy_context) for product profiles, brand guidelines, copy, ad accounts, and CPP config.\n\nKnowledge files per product:\n- product.json — core info, target audience, pain points, selling points, competitors\n- brand.json — colors, typography, voice/tone, brand archetypes, power words\n- copy.json — taglines, hooks, CTAs, approved/banned claims\n- meta-ads.json — Meta account IDs, pixels, creative test config\n- cpps.json — CPP motivations and custom product page config\n\nAlways pass updated_by with your agent name so humans can see who wrote each update.',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
slug: prop('string', 'Product slug'),
|
|
181
|
+
filepath: prop('string', 'Knowledge file to update (e.g., "meta-ads.json", "brand.json", "product.json", "copy.json", "cpps.json")'),
|
|
182
|
+
content: prop('object', 'JSON object to deep merge-patch into the knowledge file. Use null values to delete keys.'),
|
|
183
|
+
updated_by: prop('string', 'Agent name or ID writing the update (optional)'),
|
|
184
|
+
},
|
|
185
|
+
required: ['slug', 'filepath', 'content'],
|
|
186
|
+
},
|
|
187
|
+
schema: updateProductKnowledgeSchema,
|
|
188
|
+
handler: createUpdateProductKnowledgeTool(client),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'upload_product_files',
|
|
192
|
+
description: 'Upload binary assets to a product — logos, brand books, App Store screenshots, ad creatives, videos. Files are organized into sections:\n- brand-assets/ — logos, brand book PDFs, color swatches\n- store-listings/ — App Store / Play Store screenshots and preview videos\n- assets/ — ad creatives, icons, campaign videos',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
slug: prop('string', 'Product slug'),
|
|
197
|
+
files: prop('array', 'Files to upload', {
|
|
198
|
+
items: {
|
|
199
|
+
type: 'object',
|
|
200
|
+
properties: {
|
|
201
|
+
path: { type: 'string', description: 'File path within the product (e.g., "brand-assets/logo.png")' },
|
|
202
|
+
content: { type: 'string', description: 'File content as text, base64 data URI, or local file path' },
|
|
203
|
+
content_type: { type: 'string', description: 'MIME type (auto-detected if not provided)' },
|
|
204
|
+
},
|
|
205
|
+
required: ['path', 'content'],
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
required: ['slug', 'files'],
|
|
210
|
+
},
|
|
211
|
+
schema: uploadProductFilesSchema,
|
|
212
|
+
handler: createUploadProductFilesTool(client),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'pull_product_files',
|
|
216
|
+
description: 'Download binary files from a product to a local directory.',
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
slug: prop('string', 'Product slug'),
|
|
221
|
+
output_dir: prop('string', 'Local directory to write files to. Will be created if needed.'),
|
|
222
|
+
files: prop('array', 'Specific file paths to pull. If omitted, all binary files are pulled.', { items: { type: 'string' } }),
|
|
223
|
+
},
|
|
224
|
+
required: ['slug', 'output_dir'],
|
|
225
|
+
},
|
|
226
|
+
schema: pullProductFilesSchema,
|
|
227
|
+
handler: createPullProductFilesTool(client),
|
|
228
|
+
},
|
|
138
229
|
];
|
|
139
230
|
const toolHandlers = Object.fromEntries(toolDefinitions.map((t) => [t.name, { schema: t.schema, handler: t.handler }]));
|
|
140
231
|
const server = new Server({ name: '44reports', version: '1.0.0' }, { capabilities: { tools: {} } });
|
package/dist/tools/folders.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const manageFoldersSchema: z.ZodObject<{
|
|
|
6
6
|
name: z.ZodOptional<z.ZodString>;
|
|
7
7
|
description: z.ZodOptional<z.ZodString>;
|
|
8
8
|
allowed_domains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
allowed_emails: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
10
|
report_slugs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
11
|
report_slug: z.ZodOptional<z.ZodString>;
|
|
11
12
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -14,6 +15,7 @@ export declare const manageFoldersSchema: z.ZodObject<{
|
|
|
14
15
|
slug?: string | undefined;
|
|
15
16
|
description?: string | undefined;
|
|
16
17
|
allowed_domains?: string[] | undefined;
|
|
18
|
+
allowed_emails?: string[] | undefined;
|
|
17
19
|
report_slugs?: string[] | undefined;
|
|
18
20
|
report_slug?: string | undefined;
|
|
19
21
|
}, {
|
|
@@ -22,6 +24,7 @@ export declare const manageFoldersSchema: z.ZodObject<{
|
|
|
22
24
|
slug?: string | undefined;
|
|
23
25
|
description?: string | undefined;
|
|
24
26
|
allowed_domains?: string[] | undefined;
|
|
27
|
+
allowed_emails?: string[] | undefined;
|
|
25
28
|
report_slugs?: string[] | undefined;
|
|
26
29
|
report_slug?: string | undefined;
|
|
27
30
|
}>;
|
package/dist/tools/folders.js
CHANGED
|
@@ -5,7 +5,8 @@ export const manageFoldersSchema = z.object({
|
|
|
5
5
|
slug: z.string().optional().describe('Folder slug (required for get, update, delete, add_reports, remove_report)'),
|
|
6
6
|
name: z.string().optional().describe('Folder name (required for create)'),
|
|
7
7
|
description: z.string().optional().describe('Folder description'),
|
|
8
|
-
allowed_domains: z.array(z.string()).optional().describe('Email domains that can access this folder
|
|
8
|
+
allowed_domains: z.array(z.string()).optional().describe('Email domains that can access this folder'),
|
|
9
|
+
allowed_emails: z.array(z.string()).optional().describe('Individual email addresses that can access this folder'),
|
|
9
10
|
report_slugs: z.array(z.string()).optional().describe('Report slugs to add (for add_reports action)'),
|
|
10
11
|
report_slug: z.string().optional().describe('Report slug to remove (for remove_report action)'),
|
|
11
12
|
});
|
|
@@ -19,14 +20,17 @@ export function createManageFoldersTool(client) {
|
|
|
19
20
|
case 'create': {
|
|
20
21
|
if (!input.name)
|
|
21
22
|
throw new Error('name is required for create');
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const domains = input.allowed_domains ?? [];
|
|
24
|
+
const emails = input.allowed_emails ?? [];
|
|
25
|
+
if (domains.length === 0 && emails.length === 0) {
|
|
26
|
+
throw new Error('at least one of allowed_domains or allowed_emails is required for create');
|
|
24
27
|
}
|
|
25
28
|
return client.createFolder({
|
|
26
29
|
name: input.name,
|
|
27
30
|
slug: input.slug,
|
|
28
31
|
description: input.description,
|
|
29
|
-
allowed_domains:
|
|
32
|
+
allowed_domains: domains.length > 0 ? domains : undefined,
|
|
33
|
+
allowed_emails: emails.length > 0 ? emails : undefined,
|
|
30
34
|
});
|
|
31
35
|
}
|
|
32
36
|
case 'get': {
|
|
@@ -41,6 +45,7 @@ export function createManageFoldersTool(client) {
|
|
|
41
45
|
name: input.name,
|
|
42
46
|
description: input.description,
|
|
43
47
|
allowed_domains: input.allowed_domains,
|
|
48
|
+
allowed_emails: input.allowed_emails,
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
case 'delete': {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ApiClient } from '../api-client.js';
|
|
3
|
+
export declare const listProductsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
4
|
+
export declare const getProductSchema: z.ZodObject<{
|
|
5
|
+
slug: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
slug: string;
|
|
8
|
+
}, {
|
|
9
|
+
slug: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const readProductFileSchema: z.ZodObject<{
|
|
12
|
+
slug: z.ZodString;
|
|
13
|
+
filepath: z.ZodString;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
slug: string;
|
|
16
|
+
filepath: string;
|
|
17
|
+
}, {
|
|
18
|
+
slug: string;
|
|
19
|
+
filepath: string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const updateProductKnowledgeSchema: z.ZodObject<{
|
|
22
|
+
slug: z.ZodString;
|
|
23
|
+
filepath: z.ZodString;
|
|
24
|
+
content: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
25
|
+
updated_by: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
slug: string;
|
|
28
|
+
content: Record<string, unknown>;
|
|
29
|
+
filepath: string;
|
|
30
|
+
updated_by?: string | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
slug: string;
|
|
33
|
+
content: Record<string, unknown>;
|
|
34
|
+
filepath: string;
|
|
35
|
+
updated_by?: string | undefined;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const uploadProductFilesSchema: z.ZodObject<{
|
|
38
|
+
slug: z.ZodString;
|
|
39
|
+
files: z.ZodArray<z.ZodObject<{
|
|
40
|
+
path: z.ZodString;
|
|
41
|
+
content: z.ZodString;
|
|
42
|
+
content_type: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
path: string;
|
|
45
|
+
content: string;
|
|
46
|
+
content_type?: string | undefined;
|
|
47
|
+
}, {
|
|
48
|
+
path: string;
|
|
49
|
+
content: string;
|
|
50
|
+
content_type?: string | undefined;
|
|
51
|
+
}>, "many">;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
slug: string;
|
|
54
|
+
files: {
|
|
55
|
+
path: string;
|
|
56
|
+
content: string;
|
|
57
|
+
content_type?: string | undefined;
|
|
58
|
+
}[];
|
|
59
|
+
}, {
|
|
60
|
+
slug: string;
|
|
61
|
+
files: {
|
|
62
|
+
path: string;
|
|
63
|
+
content: string;
|
|
64
|
+
content_type?: string | undefined;
|
|
65
|
+
}[];
|
|
66
|
+
}>;
|
|
67
|
+
export declare const pullProductFilesSchema: z.ZodObject<{
|
|
68
|
+
slug: z.ZodString;
|
|
69
|
+
output_dir: z.ZodString;
|
|
70
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
71
|
+
}, "strip", z.ZodTypeAny, {
|
|
72
|
+
slug: string;
|
|
73
|
+
output_dir: string;
|
|
74
|
+
files?: string[] | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
slug: string;
|
|
77
|
+
output_dir: string;
|
|
78
|
+
files?: string[] | undefined;
|
|
79
|
+
}>;
|
|
80
|
+
export declare function createListProductsTool(client: ApiClient): (_input: z.infer<typeof listProductsSchema>) => Promise<{
|
|
81
|
+
products: Array<{
|
|
82
|
+
id: string;
|
|
83
|
+
slug: string;
|
|
84
|
+
name: string;
|
|
85
|
+
description: string | null;
|
|
86
|
+
json_file_count: number;
|
|
87
|
+
binary_file_count: number;
|
|
88
|
+
}>;
|
|
89
|
+
}>;
|
|
90
|
+
export declare function createGetProductTool(client: ApiClient): (input: z.infer<typeof getProductSchema>) => Promise<{
|
|
91
|
+
product: {
|
|
92
|
+
id: string;
|
|
93
|
+
slug: string;
|
|
94
|
+
name: string;
|
|
95
|
+
description: string | null;
|
|
96
|
+
created_at: string;
|
|
97
|
+
updated_at: string;
|
|
98
|
+
};
|
|
99
|
+
knowledge: Record<string, unknown>;
|
|
100
|
+
binary_files: Array<{
|
|
101
|
+
path: string;
|
|
102
|
+
size: number;
|
|
103
|
+
content_type: string;
|
|
104
|
+
}>;
|
|
105
|
+
}>;
|
|
106
|
+
export declare function createReadProductFileTool(client: ApiClient): (input: z.infer<typeof readProductFileSchema>) => Promise<{
|
|
107
|
+
path: string;
|
|
108
|
+
content: unknown;
|
|
109
|
+
content_type: string;
|
|
110
|
+
size?: number;
|
|
111
|
+
}>;
|
|
112
|
+
export declare function createUpdateProductKnowledgeTool(client: ApiClient): (input: z.infer<typeof updateProductKnowledgeSchema>) => Promise<{
|
|
113
|
+
success: boolean;
|
|
114
|
+
path: string;
|
|
115
|
+
merged?: unknown;
|
|
116
|
+
}>;
|
|
117
|
+
export declare function createUploadProductFilesTool(client: ApiClient): (input: z.infer<typeof uploadProductFilesSchema>) => Promise<{
|
|
118
|
+
success: boolean;
|
|
119
|
+
uploaded: string[];
|
|
120
|
+
}>;
|
|
121
|
+
export declare function createPullProductFilesTool(client: ApiClient): (input: z.infer<typeof pullProductFilesSchema>) => Promise<{
|
|
122
|
+
product: {
|
|
123
|
+
name: string;
|
|
124
|
+
slug: string;
|
|
125
|
+
};
|
|
126
|
+
files_pulled: number;
|
|
127
|
+
output_dir: string;
|
|
128
|
+
files: {
|
|
129
|
+
path: string;
|
|
130
|
+
size: number;
|
|
131
|
+
}[];
|
|
132
|
+
}>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
// --- Schemas ---
|
|
5
|
+
export const listProductsSchema = z.object({});
|
|
6
|
+
export const getProductSchema = z.object({
|
|
7
|
+
slug: z.string().describe('Product slug'),
|
|
8
|
+
});
|
|
9
|
+
export const readProductFileSchema = z.object({
|
|
10
|
+
slug: z.string().describe('Product slug'),
|
|
11
|
+
filepath: z.string().describe('File path within the product (e.g., "meta-ads.json", "brand-assets/logo.png")'),
|
|
12
|
+
});
|
|
13
|
+
export const updateProductKnowledgeSchema = z.object({
|
|
14
|
+
slug: z.string().describe('Product slug'),
|
|
15
|
+
filepath: z.string().describe('Knowledge file to update (e.g., "meta-ads.json", "brand.json", "product.json", "copy.json", "cpps.json")'),
|
|
16
|
+
content: z.record(z.unknown()).describe('JSON object to deep merge-patch into the knowledge file. Use null values to delete keys.'),
|
|
17
|
+
updated_by: z.string().optional().describe('Agent name or ID writing the update'),
|
|
18
|
+
});
|
|
19
|
+
export const uploadProductFilesSchema = z.object({
|
|
20
|
+
slug: z.string().describe('Product slug'),
|
|
21
|
+
files: z.array(z.object({
|
|
22
|
+
path: z.string().describe('File path within the product (e.g., "brand-assets/logo.png")'),
|
|
23
|
+
content: z.string().describe('File content as text or base64 data URI (e.g., "data:image/png;base64,...")'),
|
|
24
|
+
content_type: z.string().optional().describe('MIME type (auto-detected if not provided)'),
|
|
25
|
+
})).describe('Files to upload'),
|
|
26
|
+
});
|
|
27
|
+
export const pullProductFilesSchema = z.object({
|
|
28
|
+
slug: z.string().describe('Product slug'),
|
|
29
|
+
output_dir: z.string().describe('Local directory to write files to. Will be created if needed.'),
|
|
30
|
+
files: z.array(z.string()).optional().describe('Specific file paths to pull. If omitted, all binary files are pulled.'),
|
|
31
|
+
});
|
|
32
|
+
// --- Tool factories ---
|
|
33
|
+
export function createListProductsTool(client) {
|
|
34
|
+
return async (_input) => {
|
|
35
|
+
const result = await client.listProducts();
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function createGetProductTool(client) {
|
|
40
|
+
return async (input) => {
|
|
41
|
+
const result = await client.getProduct(input.slug);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createReadProductFileTool(client) {
|
|
46
|
+
return async (input) => {
|
|
47
|
+
const result = await client.getProductFile(input.slug, input.filepath);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function createUpdateProductKnowledgeTool(client) {
|
|
52
|
+
return async (input) => {
|
|
53
|
+
const result = await client.updateProductFile(input.slug, input.filepath, {
|
|
54
|
+
content: input.content,
|
|
55
|
+
updated_by: input.updated_by,
|
|
56
|
+
});
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function createUploadProductFilesTool(client) {
|
|
61
|
+
return async (input) => {
|
|
62
|
+
const processedFiles = await Promise.all(input.files.map(async (file) => {
|
|
63
|
+
let content = file.content;
|
|
64
|
+
if (content.startsWith('/') || content.startsWith('./') || content.startsWith('../')) {
|
|
65
|
+
const filePath = path.resolve(content);
|
|
66
|
+
const buffer = await fs.readFile(filePath);
|
|
67
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
68
|
+
const mimeTypes = {
|
|
69
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
|
|
70
|
+
gif: 'image/gif', webp: 'image/webp', pdf: 'application/pdf',
|
|
71
|
+
mp4: 'video/mp4', mov: 'video/quicktime', svg: 'image/svg+xml',
|
|
72
|
+
};
|
|
73
|
+
const mimeType = file.content_type || mimeTypes[ext] || 'application/octet-stream';
|
|
74
|
+
const base64 = buffer.toString('base64');
|
|
75
|
+
content = `data:${mimeType};base64,${base64}`;
|
|
76
|
+
}
|
|
77
|
+
return { ...file, content };
|
|
78
|
+
}));
|
|
79
|
+
const result = await client.uploadProductFiles(input.slug, processedFiles);
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function createPullProductFilesTool(client) {
|
|
84
|
+
return async (input) => {
|
|
85
|
+
const { product, binary_files } = await client.getProduct(input.slug);
|
|
86
|
+
let filesToPull = binary_files.map((f) => f.path);
|
|
87
|
+
if (input.files && input.files.length > 0) {
|
|
88
|
+
filesToPull = input.files;
|
|
89
|
+
}
|
|
90
|
+
if (filesToPull.length === 0) {
|
|
91
|
+
return { product: { name: product.name, slug: product.slug }, files_pulled: 0, output_dir: input.output_dir, files: [] };
|
|
92
|
+
}
|
|
93
|
+
const outputDir = path.resolve(input.output_dir);
|
|
94
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
95
|
+
const written = [];
|
|
96
|
+
const resolvedOutputDir = path.resolve(outputDir);
|
|
97
|
+
for (const filePath of filesToPull) {
|
|
98
|
+
const result = await client.getProductFile(input.slug, filePath);
|
|
99
|
+
const destPath = path.join(outputDir, filePath);
|
|
100
|
+
const resolvedDestPath = path.resolve(destPath);
|
|
101
|
+
if (!resolvedDestPath.startsWith(resolvedOutputDir + path.sep) && resolvedDestPath !== resolvedOutputDir) {
|
|
102
|
+
throw new Error(`Unsafe file path rejected: ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
105
|
+
const content = result.content;
|
|
106
|
+
if (content.startsWith('data:')) {
|
|
107
|
+
const commaIndex = content.indexOf(',');
|
|
108
|
+
const base64Data = content.slice(commaIndex + 1);
|
|
109
|
+
await fs.writeFile(destPath, Buffer.from(base64Data, 'base64'));
|
|
110
|
+
written.push({ path: filePath, size: result.size || 0 });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await fs.writeFile(destPath, content, 'utf-8');
|
|
114
|
+
written.push({ path: filePath, size: content.length });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
product: { name: product.name, slug: product.slug },
|
|
119
|
+
files_pulled: written.length,
|
|
120
|
+
output_dir: outputDir,
|
|
121
|
+
files: written,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
}
|