@pkagentic/mcp 1.4.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.
@@ -0,0 +1,855 @@
1
+ import axios from "axios";
2
+ import FormData from "form-data";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import sharp from "sharp";
6
+ import { fileURLToPath } from "url";
7
+ import { finished } from "stream/promises";
8
+ import { Readability } from "@mozilla/readability";
9
+ import { JSDOM, VirtualConsole } from "jsdom";
10
+ import TurndownService from "turndown";
11
+ import { GoogleGenAI } from "@google/genai";
12
+ import postcss from "postcss";
13
+ import tailwindcss from "@tailwindcss/postcss";
14
+ // Resolve the directory of this compiled file so we can locate bundled guide files
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+ export class ImageGenerationApi {
18
+ client;
19
+ modelId;
20
+ constructor(apiKey, modelId) {
21
+ this.modelId = modelId || "gemini-3-pro-image-preview";
22
+ if (apiKey) {
23
+ this.client = new GoogleGenAI({ apiKey, apiVersion: "v1beta" });
24
+ }
25
+ }
26
+ async generateImage(args) {
27
+ if (!this.client) {
28
+ throw new Error("GEMINI_API_KEY is not set. Please set it in your MCP configuration (e.g., in claude_desktop_config.json or .env file) and restart the server.");
29
+ }
30
+ const { prompt_path, image_name, page_name, aspect_ratio = "1:1" } = args;
31
+ // 1. Read prompt from file
32
+ if (!fs.existsSync(prompt_path)) {
33
+ throw new Error(`Prompt file not found at ${prompt_path}. Please create it first.`);
34
+ }
35
+ const prompt = fs.readFileSync(prompt_path, "utf-8").trim();
36
+ try {
37
+ // 2. Call Gemini SDK (Nano Banana Pro)
38
+ // Gemini 3 models support native image generation via generateContent
39
+ const response = await this.client.models.generateContent({
40
+ model: this.modelId,
41
+ contents: prompt,
42
+ config: {
43
+ imageConfig: { aspectRatio: aspect_ratio },
44
+ responseModalities: ["IMAGE"]
45
+ }
46
+ });
47
+ if (!response.candidates || response.candidates.length === 0) {
48
+ throw new Error("No candidates returned from Gemini API.");
49
+ }
50
+ const parts = response.candidates[0].content?.parts;
51
+ if (!parts) {
52
+ throw new Error("No parts found in the response. The model might have been blocked by safety filters.");
53
+ }
54
+ let base64Data;
55
+ let mimeType = "image/png";
56
+ for (const part of parts) {
57
+ if (part.inlineData) {
58
+ base64Data = part.inlineData.data;
59
+ mimeType = part.inlineData.mimeType || "image/png";
60
+ break;
61
+ }
62
+ }
63
+ if (!base64Data) {
64
+ throw new Error("No image data found in the response parts. The model might have returned text instead.");
65
+ }
66
+ const extension = mimeType.split("/")[1] || "png";
67
+ // 3. Save image to folder
68
+ const outputDir = path.join("workspace", "generated-images");
69
+ if (!fs.existsSync(outputDir)) {
70
+ fs.mkdirSync(outputDir, { recursive: true });
71
+ }
72
+ const fileName = `${image_name}.${extension}`;
73
+ const outputPath = path.join(outputDir, fileName);
74
+ fs.writeFileSync(outputPath, Buffer.from(base64Data, "base64"));
75
+ return {
76
+ success: true,
77
+ message: `Image generated using Nano Banana Pro and saved to ${outputPath}`,
78
+ file_path: outputPath,
79
+ image_name: fileName,
80
+ page_name: page_name,
81
+ model: this.modelId
82
+ };
83
+ }
84
+ catch (error) {
85
+ const errorMessage = error.message || "Unknown error occurred during image generation.";
86
+ throw new Error(`Gemini SDK Error: ${errorMessage}`);
87
+ }
88
+ }
89
+ }
90
+ export class UtilityApi {
91
+ async initProject(args) {
92
+ const rootPath = args.path || ".";
93
+ const dirs = [
94
+ ".pk-agentic/agent-guide",
95
+ "workspace/templates",
96
+ "workspace/scripts",
97
+ "workspace/contents/pages",
98
+ "workspace/contents/posts",
99
+ "resources/assets",
100
+ "resources/context",
101
+ ];
102
+ // Clear agent-guide contents so the latest guide is always used
103
+ const agentGuideDir = path.join(rootPath, ".pk-agentic/agent-guide");
104
+ if (fs.existsSync(agentGuideDir)) {
105
+ for (const entry of fs.readdirSync(agentGuideDir)) {
106
+ fs.rmSync(path.join(agentGuideDir, entry), { recursive: true, force: true });
107
+ }
108
+ }
109
+ for (const dir of dirs) {
110
+ const fullPath = path.join(rootPath, dir);
111
+ if (!fs.existsSync(fullPath)) {
112
+ fs.mkdirSync(fullPath, { recursive: true });
113
+ // Create .gitkeep to ensure empty directories are kept in version control
114
+ fs.writeFileSync(path.join(fullPath, ".gitkeep"), "");
115
+ }
116
+ }
117
+ return {
118
+ success: true,
119
+ message: "Project structure initialized successfully.",
120
+ root: path.resolve(rootPath),
121
+ directories: dirs
122
+ };
123
+ }
124
+ async getAgentGuide(args) {
125
+ const { guide } = args;
126
+ // Source: the agent-guide/ folder shipped alongside this MCP server
127
+ const sourceDir = path.resolve(__dirname, "..", "agent-guide");
128
+ const sourceFile = path.join(sourceDir, `${guide}.md`);
129
+ if (!fs.existsSync(sourceFile)) {
130
+ throw new Error(`Guide file not found in MCP server: agent-guide/${guide}.md`);
131
+ }
132
+ // Destination: agent-guide/ in the agent's current working directory
133
+ const destDir = path.resolve(".pk-agentic/agent-guide");
134
+ if (!fs.existsSync(destDir)) {
135
+ fs.mkdirSync(destDir, { recursive: true });
136
+ }
137
+ const destFile = path.join(destDir, `${guide}.md`);
138
+ fs.copyFileSync(sourceFile, destFile);
139
+ const relativePath = `.pk-agentic/agent-guide/${guide}.md`;
140
+ return {
141
+ success: true,
142
+ message: `Guide written to ${relativePath}. Read that file now to learn about '${guide}'.`,
143
+ file_written: relativePath,
144
+ absolute_path: destFile,
145
+ };
146
+ }
147
+ async optimizeImage(args) {
148
+ const { file_path, output_path, width, height, quality = 80 } = args;
149
+ if (!fs.existsSync(file_path)) {
150
+ throw new Error(`File not found: ${file_path}`);
151
+ }
152
+ const ext = path.extname(file_path).toLowerCase().replace(".", "");
153
+ const convertToWebP = ["png", "jpg", "jpeg", "bmp", "tiff", "tif", "heic", "heif"].includes(ext);
154
+ // Output path: replace extension with .webp, or use provided path
155
+ const baseName = path.basename(file_path, path.extname(file_path));
156
+ const inputDir = path.dirname(file_path);
157
+ const resolvedOutput = output_path || path.join(inputDir, `${baseName}.webp`);
158
+ const outputDir = path.dirname(resolvedOutput);
159
+ if (!fs.existsSync(outputDir)) {
160
+ fs.mkdirSync(outputDir, { recursive: true });
161
+ }
162
+ const originalSize = fs.statSync(file_path).size;
163
+ let image = sharp(file_path);
164
+ const metadata = await image.metadata();
165
+ const originalWidth = metadata.width ?? 0;
166
+ const originalHeight = metadata.height ?? 0;
167
+ // Only resize if the image exceeds the specified dimension
168
+ const resizeWidth = (width && originalWidth > width) ? width : undefined;
169
+ const resizeHeight = (height && originalHeight > height) ? height : undefined;
170
+ if (resizeWidth || resizeHeight) {
171
+ image = image.resize({
172
+ width: resizeWidth,
173
+ height: resizeHeight,
174
+ fit: "inside",
175
+ withoutEnlargement: true,
176
+ });
177
+ }
178
+ const outputExt = path.extname(resolvedOutput).toLowerCase();
179
+ if (convertToWebP || outputExt === ".webp") {
180
+ image = image.webp({ quality });
181
+ }
182
+ const info = await image.toFile(resolvedOutput);
183
+ const newSize = fs.statSync(resolvedOutput).size;
184
+ const reduction = originalSize > 0
185
+ ? `${Math.round((1 - newSize / originalSize) * 100)}%`
186
+ : "0%";
187
+ return {
188
+ success: true,
189
+ message: "Image optimized successfully.",
190
+ input_path: file_path,
191
+ output_path: resolvedOutput,
192
+ original: {
193
+ width: originalWidth,
194
+ height: originalHeight,
195
+ size_bytes: originalSize,
196
+ format: ext,
197
+ },
198
+ output: {
199
+ width: info.width,
200
+ height: info.height,
201
+ size_bytes: newSize,
202
+ format: info.format,
203
+ },
204
+ resized: !!(resizeWidth || resizeHeight),
205
+ converted_to_webp: convertToWebP || outputExt === ".webp",
206
+ size_reduction: reduction,
207
+ };
208
+ }
209
+ async downloadImage(args) {
210
+ const { url, path: filePath } = args;
211
+ // Ensure directory exists
212
+ const dir = path.dirname(filePath);
213
+ if (!fs.existsSync(dir)) {
214
+ fs.mkdirSync(dir, { recursive: true });
215
+ }
216
+ const writer = fs.createWriteStream(filePath);
217
+ const response = await axios({
218
+ method: "get",
219
+ url: url,
220
+ responseType: "stream",
221
+ });
222
+ response.data.pipe(writer);
223
+ await finished(writer);
224
+ return {
225
+ success: true,
226
+ message: `Image downloaded successfully to ${filePath}`,
227
+ path: filePath
228
+ };
229
+ }
230
+ }
231
+ export class TailwindApi {
232
+ async optimize(args) {
233
+ const { tailwind_config } = args;
234
+ // 1. Setup workspace/optimize directory
235
+ const outputDir = path.join("workspace", "optimize");
236
+ if (!fs.existsSync(outputDir)) {
237
+ fs.mkdirSync(outputDir, { recursive: true });
238
+ }
239
+ const tempConfigPath = path.resolve(path.join(outputDir, "tailwind.config.cjs"));
240
+ const tempCssPath = path.resolve(path.join(outputDir, "input.css"));
241
+ const outputPath = path.resolve(path.join(outputDir, "optimized-tailwind.css"));
242
+ // 2. Write temporary config file
243
+ const configContent = `
244
+ module.exports = {
245
+ content: ["./workspace/**/*.php", "./workspace/**/*.html"],
246
+ theme: {
247
+ extend: ${JSON.stringify(tailwind_config, null, 2)}
248
+ },
249
+ prefix: "pkt-",
250
+ };
251
+ `;
252
+ fs.writeFileSync(tempConfigPath, configContent);
253
+ // 3. Write input CSS file
254
+ const cssContent = `
255
+ @import "tailwindcss";
256
+ @config "${tempConfigPath.replace(/\\/g, "/")}";
257
+ `;
258
+ fs.writeFileSync(tempCssPath, cssContent);
259
+ try {
260
+ // 4. Run PostCSS
261
+ const css = fs.readFileSync(tempCssPath, "utf-8");
262
+ // We need to use the PostCSS plugin from tailwindcss
263
+ const result = await postcss([
264
+ tailwindcss()
265
+ ]).process(css, { from: tempCssPath, to: outputPath });
266
+ // 5. Write optimized CSS
267
+ fs.writeFileSync(outputPath, result.css);
268
+ // 6. Cleanup temporary files
269
+ if (fs.existsSync(tempConfigPath))
270
+ fs.unlinkSync(tempConfigPath);
271
+ if (fs.existsSync(tempCssPath))
272
+ fs.unlinkSync(tempCssPath);
273
+ return {
274
+ success: true,
275
+ message: `Tailwind CSS optimized successfully. Output saved to ${path.relative(process.cwd(), outputPath)}`,
276
+ output_path: path.relative(process.cwd(), outputPath),
277
+ css_size: result.css.length
278
+ };
279
+ }
280
+ catch (error) {
281
+ // Cleanup on error
282
+ if (fs.existsSync(tempConfigPath))
283
+ fs.unlinkSync(tempConfigPath);
284
+ if (fs.existsSync(tempCssPath))
285
+ fs.unlinkSync(tempCssPath);
286
+ throw new Error(`Tailwind Optimization Error: ${error.message}`);
287
+ }
288
+ }
289
+ }
290
+ export class WebCrawlerApi {
291
+ turndownService;
292
+ constructor() {
293
+ this.turndownService = new TurndownService({
294
+ headingStyle: "atx",
295
+ codeBlockStyle: "fenced"
296
+ });
297
+ }
298
+ async readWebPage(args) {
299
+ const { url, render_js = true, wait_selector, css_selector } = args;
300
+ let dom;
301
+ if (render_js) {
302
+ const virtualConsole = new VirtualConsole();
303
+ // Forward virtual console logs to terminal if needed for debugging
304
+ // virtualConsole.sendTo(console);
305
+ try {
306
+ dom = await JSDOM.fromURL(url, {
307
+ resources: "usable",
308
+ runScripts: "dangerously",
309
+ virtualConsole,
310
+ pretendToBeVisual: true,
311
+ });
312
+ // Wait for JS to execute. JSDOM doesn't have a perfect "networkidle"
313
+ // so we wait for the wait_selector or a fixed timeout.
314
+ if (wait_selector) {
315
+ await new Promise((resolve) => {
316
+ const start = Date.now();
317
+ const interval = setInterval(() => {
318
+ if (dom.window.document.querySelector(wait_selector) || Date.now() - start > 10000) {
319
+ clearInterval(interval);
320
+ resolve();
321
+ }
322
+ }, 100);
323
+ });
324
+ }
325
+ else {
326
+ // Default wait for some potential async rendering
327
+ await new Promise(resolve => setTimeout(resolve, 2000));
328
+ }
329
+ }
330
+ catch (error) {
331
+ return {
332
+ success: false,
333
+ message: `Failed to load page with JSDOM: ${error.message}`
334
+ };
335
+ }
336
+ }
337
+ else {
338
+ try {
339
+ const response = await axios.get(url);
340
+ dom = new JSDOM(response.data, { url });
341
+ }
342
+ catch (error) {
343
+ return {
344
+ success: false,
345
+ message: `Failed to fetch page with Axios: ${error.message}`
346
+ };
347
+ }
348
+ }
349
+ const reader = new Readability(dom.window.document);
350
+ const article = reader.parse();
351
+ if (!article || !article.content) {
352
+ // If readability fails, try to get raw HTML from selector or body
353
+ let contentHtml;
354
+ if (css_selector) {
355
+ const el = dom.window.document.querySelector(css_selector);
356
+ contentHtml = el ? el.innerHTML : dom.window.document.body.innerHTML;
357
+ }
358
+ else {
359
+ contentHtml = dom.window.document.body.innerHTML;
360
+ }
361
+ if (!contentHtml) {
362
+ return {
363
+ success: false,
364
+ message: "Failed to extract content from the page."
365
+ };
366
+ }
367
+ const markdown = this.turndownService.turndown(contentHtml);
368
+ return {
369
+ success: true,
370
+ url,
371
+ title: dom.window.document.title,
372
+ content: markdown
373
+ };
374
+ }
375
+ const markdown = this.turndownService.turndown(article.content);
376
+ return {
377
+ success: true,
378
+ url,
379
+ title: article.title || dom.window.document.title,
380
+ byline: article.byline,
381
+ excerpt: article.excerpt,
382
+ content: markdown
383
+ };
384
+ }
385
+ }
386
+ export class PKAgentApi {
387
+ axiosInstance;
388
+ constructor(baseURL, key, email) {
389
+ this.axiosInstance = axios.create({
390
+ baseURL: baseURL.endsWith("/") ? baseURL : `${baseURL}/`,
391
+ headers: {
392
+ "X-PK-Agent-Key": key,
393
+ "X-PK-Agent-Email": email,
394
+ "Content-Type": "application/json",
395
+ },
396
+ });
397
+ // Add interceptors for logging
398
+ this.axiosInstance.interceptors.request.use((config) => {
399
+ console.error(`[API Request] ${config.method?.toUpperCase()} ${config.url}`, config.params || config.data || "");
400
+ return config;
401
+ });
402
+ this.axiosInstance.interceptors.response.use((response) => {
403
+ const logData = typeof response.data === "string" ? response.data.substring(0, 200) : response.data;
404
+ console.error(`[API Response] ${response.status}`, logData);
405
+ return response;
406
+ }, (error) => {
407
+ console.error(`[API Error] ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data || error.message);
408
+ return Promise.reject(error);
409
+ });
410
+ }
411
+ // ── Private helpers ──────────────────────────────────────────────────────
412
+ /**
413
+ * Ensure a directory exists and write JSON content to a file.
414
+ */
415
+ writeJsonFile(filePath, content) {
416
+ const dir = path.dirname(filePath);
417
+ if (!fs.existsSync(dir)) {
418
+ fs.mkdirSync(dir, { recursive: true });
419
+ }
420
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
421
+ }
422
+ /**
423
+ * Write downloaded code fields (html/css/js) from an API item to the
424
+ * correct workspace sub-directory derived from the item's virtual_path.
425
+ *
426
+ * Layout:
427
+ * template → workspace/templates/{slug}/{slug}.php .css .js
428
+ * script → workspace/scripts/{slug}/{slug}.php .css .js
429
+ * content → workspace/contents/{vp_parts}/{slug}.html .css .js
430
+ *
431
+ * Returns relative paths of every file that was written.
432
+ */
433
+ writeItemFiles(data, baseDir = ".") {
434
+ const virtualPath = data.virtual_path || "";
435
+ if (!virtualPath)
436
+ return [];
437
+ const slug = data.slug;
438
+ const isTemplate = virtualPath.startsWith("/templates/");
439
+ const isScript = virtualPath.startsWith("/scripts/");
440
+ const isContent = !isTemplate && !isScript;
441
+ // Build local directory path
442
+ const vpParts = virtualPath.replace(/^\//, "").split("/"); // strip leading "/"
443
+ let dir;
444
+ if (isTemplate) {
445
+ dir = path.join(baseDir, "workspace", "templates", slug);
446
+ }
447
+ else if (isScript) {
448
+ dir = path.join(baseDir, "workspace", "scripts", slug);
449
+ }
450
+ else {
451
+ // content: workspace/contents/pages/{slug} or workspace/contents/{cpt}/{slug}
452
+ dir = path.join(baseDir, "workspace", "contents", ...vpParts);
453
+ }
454
+ fs.mkdirSync(dir, { recursive: true });
455
+ const htmlExt = isContent ? ".html" : ".php";
456
+ const filesWritten = [];
457
+ if (data.html !== undefined && data.html !== null) {
458
+ const fp = path.join(dir, `${slug}${htmlExt}`);
459
+ fs.writeFileSync(fp, data.html ?? "", "utf-8");
460
+ filesWritten.push(path.relative(baseDir, fp));
461
+ }
462
+ if (data.css !== undefined && data.css !== null) {
463
+ const fp = path.join(dir, `${slug}.css`);
464
+ fs.writeFileSync(fp, data.css ?? "", "utf-8");
465
+ filesWritten.push(path.relative(baseDir, fp));
466
+ }
467
+ if (data.js !== undefined && data.js !== null) {
468
+ const fp = path.join(dir, `${slug}.js`);
469
+ fs.writeFileSync(fp, data.js ?? "", "utf-8");
470
+ filesWritten.push(path.relative(baseDir, fp));
471
+ }
472
+ if (data.document_content !== undefined && data.document_content !== null) {
473
+ const fp = path.join(dir, `${slug}.md`);
474
+ fs.writeFileSync(fp, data.document_content ?? "", "utf-8");
475
+ filesWritten.push(path.relative(baseDir, fp));
476
+ }
477
+ return filesWritten;
478
+ }
479
+ // ── API methods ───────────────────────────────────────────────────────────
480
+ async getSiteInfo() {
481
+ const response = await this.axiosInstance.get("site-info");
482
+ const result = response.data;
483
+ // Persist to .pk-agentic/site-info.json
484
+ this.writeJsonFile(".pk-agentic/site-info.json", result);
485
+ const d = result.data || {};
486
+ return {
487
+ success: true,
488
+ message: "Site info written to .pk-agentic/site-info.json. Read that file for the full response. Key values are summarised below.",
489
+ file_written: ".pk-agentic/site-info.json",
490
+ summary: {
491
+ site_name: d.site_name,
492
+ site_url: d.site_url,
493
+ batch_file_limit: d.batch_file_limit,
494
+ css_framework: d.css_framework,
495
+ timezone: d.timezone,
496
+ language: d.language,
497
+ permissions: d.permissions,
498
+ menu_locations: d.menu_locations,
499
+ },
500
+ };
501
+ }
502
+ async syncItems(args) {
503
+ const response = await this.axiosInstance.get("sync", { params: args });
504
+ const result = response.data;
505
+ const filePath = ".pk-agentic/file-list.json";
506
+ const page = args.page ?? 1;
507
+ if (page === 1) {
508
+ // First page — write fresh
509
+ this.writeJsonFile(filePath, result);
510
+ }
511
+ else {
512
+ // Subsequent pages — merge arrays into existing file
513
+ let existing = { data: { templates: [], scripts: [], content: [] } };
514
+ if (fs.existsSync(filePath)) {
515
+ try {
516
+ existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
517
+ }
518
+ catch { /**/ }
519
+ }
520
+ existing.data = existing.data || {};
521
+ existing.data.templates = [...(existing.data.templates ?? []), ...(result.data?.templates ?? [])];
522
+ existing.data.scripts = [...(existing.data.scripts ?? []), ...(result.data?.scripts ?? [])];
523
+ existing.data.content = [...(existing.data.content ?? []), ...(result.data?.content ?? [])];
524
+ existing.pagination = result.pagination; // update pagination to reflect latest page
525
+ this.writeJsonFile(filePath, existing);
526
+ }
527
+ const total = result.total || {};
528
+ const pagination = result.pagination || {};
529
+ return {
530
+ success: true,
531
+ message: `File list written to .pk-agentic/file-list.json. Read that file to get the full item inventory. ${pagination.has_more ? `More pages available — call sync(page=${(pagination.page ?? 1) + 1}) to continue fetching.` : "All items fetched."}`,
532
+ file_written: ".pk-agentic/file-list.json",
533
+ summary: {
534
+ page: pagination.page,
535
+ per_page: pagination.per_page,
536
+ total_items: pagination.total_items,
537
+ total_pages: pagination.total_pages,
538
+ has_more: pagination.has_more,
539
+ items_on_page: {
540
+ templates: total.templates ?? 0,
541
+ scripts: total.scripts ?? 0,
542
+ content: total.content ?? 0,
543
+ },
544
+ },
545
+ };
546
+ }
547
+ async getFile(args) {
548
+ const { id, from_line, to_line, ...params } = args;
549
+ const response = await this.axiosInstance.get(`files/${id}`, {
550
+ params: { ...params, ...(from_line !== undefined && { from_line }), ...(to_line !== undefined && { to_line }) },
551
+ });
552
+ const result = response.data;
553
+ const data = result.data || {};
554
+ const hasLineRange = from_line !== undefined || to_line !== undefined;
555
+ if (hasLineRange) {
556
+ // Partial / inspection download — return content inline, do NOT overwrite local file
557
+ return {
558
+ success: true,
559
+ partial_download: true,
560
+ message: `Partial content returned inline for '${data.title}' (id: ${id}), lines ${data.line_range?.from ?? from_line}–${data.line_range?.to ?? to_line}. File NOT written to disk (partial content would corrupt the local copy).`,
561
+ metadata: {
562
+ id: data.id,
563
+ type: data.type,
564
+ slug: data.slug,
565
+ virtual_path: data.virtual_path,
566
+ download_type: data.download_type,
567
+ total_lines: data.total_lines,
568
+ line_range: data.line_range,
569
+ },
570
+ content: {
571
+ ...(data.html !== undefined && { html: data.html }),
572
+ ...(data.css !== undefined && { css: data.css }),
573
+ ...(data.js !== undefined && { js: data.js }),
574
+ },
575
+ };
576
+ }
577
+ // Full download — write to workspace and return summary
578
+ const filesWritten = this.writeItemFiles(data);
579
+ return {
580
+ success: true,
581
+ message: `'${data.title}' (id: ${id}) written to workspace. ${filesWritten.length} file(s) saved.`,
582
+ files_written: filesWritten,
583
+ files_count: filesWritten.length,
584
+ metadata: {
585
+ id: data.id,
586
+ type: data.type,
587
+ slug: data.slug,
588
+ virtual_path: data.virtual_path,
589
+ is_locked: data.is_locked,
590
+ status: data.status,
591
+ download_type: data.download_type,
592
+ total_lines: data.total_lines,
593
+ description: data.description,
594
+ document_content: data.document_content,
595
+ },
596
+ };
597
+ }
598
+ async getFilesBatch(args) {
599
+ const response = await this.axiosInstance.post("files/batch", args);
600
+ const result = response.data;
601
+ const items = Array.isArray(result.data) ? result.data : [];
602
+ const itemsSummary = [];
603
+ let totalFilesWritten = 0;
604
+ for (const item of items) {
605
+ const filesWritten = this.writeItemFiles(item);
606
+ totalFilesWritten += filesWritten.length;
607
+ itemsSummary.push({
608
+ id: item.id,
609
+ title: item.title,
610
+ slug: item.slug,
611
+ type: item.type,
612
+ virtual_path: item.virtual_path,
613
+ files_written: filesWritten,
614
+ });
615
+ }
616
+ return {
617
+ success: true,
618
+ message: `Batch download complete. ${items.length} item(s) downloaded, ${totalFilesWritten} file(s) written to workspace.`,
619
+ items_downloaded: items.length,
620
+ total_files_written: totalFilesWritten,
621
+ items: itemsSummary,
622
+ };
623
+ }
624
+ async getTemplateInfo(id) {
625
+ const response = await this.axiosInstance.get(`templates/${id}`);
626
+ return response.data;
627
+ }
628
+ async getScriptInfo(id) {
629
+ const response = await this.axiosInstance.get(`scripts/${id}`);
630
+ return response.data;
631
+ }
632
+ async getContentInfo(id) {
633
+ const response = await this.axiosInstance.get(`contents/${id}`);
634
+ return response.data;
635
+ }
636
+ readFileContent(filePath) {
637
+ if (!fs.existsSync(filePath)) {
638
+ throw new Error(`File not found: ${filePath}`);
639
+ }
640
+ try {
641
+ return fs.readFileSync(filePath, "utf-8");
642
+ }
643
+ catch (error) {
644
+ throw new Error(`Failed to read file at ${filePath}: ${error.message}`);
645
+ }
646
+ }
647
+ async saveItem(args) {
648
+ const { type, id, html_path, css_path, js_path, document_content_path, ...rest } = args;
649
+ const data = { ...rest };
650
+ if (html_path)
651
+ data.html = this.readFileContent(html_path);
652
+ if (css_path)
653
+ data.css = this.readFileContent(css_path);
654
+ if (js_path)
655
+ data.js = this.readFileContent(js_path);
656
+ if (document_content_path)
657
+ data.document_content = this.readFileContent(document_content_path);
658
+ const response = await this.axiosInstance.post(`${type}/${id}/save`, data);
659
+ return response.data;
660
+ }
661
+ async publishItem(args) {
662
+ const { type, id } = args;
663
+ const response = await this.axiosInstance.post(`${type}/${id}/publish`);
664
+ return response.data;
665
+ }
666
+ async createItem(args) {
667
+ const { type, html_path, css_path, js_path, ...rest } = args;
668
+ const data = { ...rest };
669
+ if (html_path)
670
+ data.html = this.readFileContent(html_path);
671
+ if (css_path)
672
+ data.css = this.readFileContent(css_path);
673
+ if (js_path)
674
+ data.js = this.readFileContent(js_path);
675
+ const response = await this.axiosInstance.post(`${type}`, data);
676
+ return response.data;
677
+ }
678
+ async toggleItem(args) {
679
+ const { type, id, enabled } = args;
680
+ const response = await this.axiosInstance.post(`${type}/${id}/toggle`, { enabled });
681
+ return response.data;
682
+ }
683
+ async updateConditions(args) {
684
+ const { type, id, conditions } = args;
685
+ const response = await this.axiosInstance.post(`${type}/${id}/conditions`, { conditions });
686
+ return response.data;
687
+ }
688
+ async updateMetadata(args) {
689
+ const { type, id, ...data } = args;
690
+ const response = await this.axiosInstance.post(`${type}/${id}/update`, data);
691
+ return response.data;
692
+ }
693
+ async deleteItem(args) {
694
+ const { type, id } = args;
695
+ const response = await this.axiosInstance.delete(`${type}/${id}`);
696
+ return response.data;
697
+ }
698
+ async getRevisions(args) {
699
+ const { type, id } = args;
700
+ const response = await this.axiosInstance.get(`${type}/${id}/revisions`);
701
+ return response.data;
702
+ }
703
+ async restoreRevision(args) {
704
+ const { type, id, revision_id } = args;
705
+ const response = await this.axiosInstance.post(`${type}/${id}/revisions/${revision_id}/restore`);
706
+ return response.data;
707
+ }
708
+ async searchContents(args) {
709
+ const response = await this.axiosInstance.get("contents/search", { params: args });
710
+ return response.data;
711
+ }
712
+ async markContentEdit(args) {
713
+ const { post_id } = args;
714
+ const response = await this.axiosInstance.post(`posts/${post_id}/mark-edit`);
715
+ return response.data;
716
+ }
717
+ async searchMedia(args) {
718
+ const response = await this.axiosInstance.get("media/search", { params: args });
719
+ return response.data;
720
+ }
721
+ async uploadMedia(args) {
722
+ const { file_path, base64, filename, ...rest } = args;
723
+ if (file_path) {
724
+ if (!fs.existsSync(file_path)) {
725
+ throw new Error(`File not found: ${file_path}`);
726
+ }
727
+ const form = new FormData();
728
+ form.append("file", fs.createReadStream(file_path));
729
+ // Add other parameters
730
+ if (filename)
731
+ form.append("filename", filename);
732
+ if (rest.media_type)
733
+ form.append("media_type", rest.media_type);
734
+ if (rest.alt_text)
735
+ form.append("alt_text", rest.alt_text);
736
+ if (rest.title)
737
+ form.append("title", rest.title);
738
+ const response = await this.axiosInstance.post("media/upload", form, {
739
+ headers: {
740
+ ...form.getHeaders(),
741
+ },
742
+ });
743
+ return response.data;
744
+ }
745
+ else if (base64 && filename) {
746
+ const response = await this.axiosInstance.post("media/upload", args);
747
+ return response.data;
748
+ }
749
+ else {
750
+ throw new Error("Either file_path or both base64 and filename must be provided.");
751
+ }
752
+ }
753
+ async getImageSrcset(args) {
754
+ const response = await this.axiosInstance.get("media/srcset", { params: args });
755
+ return response.data;
756
+ }
757
+ async getRoles() {
758
+ const response = await this.axiosInstance.get("roles");
759
+ return response.data;
760
+ }
761
+ async getTailwindConfig() {
762
+ const response = await this.axiosInstance.get("tailwind-config");
763
+ return response.data;
764
+ }
765
+ async updateTailwindConfig(args) {
766
+ const response = await this.axiosInstance.post("tailwind-config", args);
767
+ return response.data;
768
+ }
769
+ // ── Global Library ──────────────────────────────────────────────────────
770
+ async listLibraries(args) {
771
+ const response = await this.axiosInstance.get("libraries", { params: args });
772
+ const result = response.data;
773
+ // Persist to .pk-agentic/library-info.json
774
+ const filePath = ".pk-agentic/library-info.json";
775
+ const page = args.page ?? 1;
776
+ if (page === 1) {
777
+ this.writeJsonFile(filePath, result);
778
+ }
779
+ else {
780
+ let existing = { data: [] };
781
+ if (fs.existsSync(filePath)) {
782
+ try {
783
+ existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
784
+ }
785
+ catch {
786
+ /**/
787
+ }
788
+ }
789
+ existing.data = [...(existing.data ?? []), ...(result.data ?? [])];
790
+ existing.pagination = result.pagination;
791
+ this.writeJsonFile(filePath, existing);
792
+ }
793
+ return {
794
+ success: true,
795
+ message: `Global Libraries written to ${filePath}.`,
796
+ file_written: filePath,
797
+ data: result.data,
798
+ pagination: result.pagination,
799
+ };
800
+ }
801
+ async createLibrary(args) {
802
+ const response = await this.axiosInstance.post("libraries", args);
803
+ return response.data;
804
+ }
805
+ async updateLibrary(args) {
806
+ const { id, ...data } = args;
807
+ const response = await this.axiosInstance.post(`libraries/${id}/update`, data);
808
+ return response.data;
809
+ }
810
+ async deleteLibrary(args) {
811
+ const { id } = args;
812
+ const response = await this.axiosInstance.delete(`libraries/${id}`);
813
+ return response.data;
814
+ }
815
+ async searchLibraries(args) {
816
+ const response = await this.axiosInstance.get("libraries/search", { params: args });
817
+ return response.data;
818
+ }
819
+ async getLibrary(args) {
820
+ const { id } = args;
821
+ const response = await this.axiosInstance.get(`libraries/${id}`);
822
+ return response.data;
823
+ }
824
+ async cdnDownloadLibrary(args) {
825
+ const { id, urls } = args;
826
+ const response = await this.axiosInstance.post(`libraries/${id}/cdn-download`, { urls });
827
+ return response.data;
828
+ }
829
+ async saveLibraryFiles(args) {
830
+ const { id, files } = args;
831
+ const response = await this.axiosInstance.post(`libraries/${id}/save-files`, { files });
832
+ return response.data;
833
+ }
834
+ async restoreLibraryBackup(args) {
835
+ const { id, backup_key } = args;
836
+ const response = await this.axiosInstance.post(`libraries/${id}/restore-backup`, { backup_key });
837
+ return response.data;
838
+ }
839
+ async updateLibraryConditions(args) {
840
+ const { id, conditions } = args;
841
+ const response = await this.axiosInstance.post(`libraries/${id}/conditions`, { conditions });
842
+ return response.data;
843
+ }
844
+ async toggleLibrary(args) {
845
+ const { id, enabled } = args;
846
+ const response = await this.axiosInstance.post(`libraries/${id}/toggle`, { enabled });
847
+ return response.data;
848
+ }
849
+ async toggleLibraryLock(args) {
850
+ const { id, locked } = args;
851
+ const response = await this.axiosInstance.post(`libraries/${id}/lock`, { locked });
852
+ return response.data;
853
+ }
854
+ }
855
+ //# sourceMappingURL=handlers.js.map