claude-presentation-master 8.1.0 → 8.2.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/index.d.mts CHANGED
@@ -127,6 +127,26 @@ interface QAResults {
127
127
  passed: boolean;
128
128
  /** List of issues found */
129
129
  issues: QAIssue[];
130
+ /** Per-slide scores from visual QA */
131
+ slideScores?: SlideScore[];
132
+ /** Overall QA score */
133
+ score?: number;
134
+ /** QA status */
135
+ status?: string;
136
+ }
137
+ interface SlideScore {
138
+ /** Slide index */
139
+ index: number;
140
+ /** Slide type */
141
+ type: string;
142
+ /** Score 0-100 */
143
+ score: number;
144
+ /** Expert verdict */
145
+ expertVerdict?: string;
146
+ /** Critical issues found */
147
+ criticalIssues?: string[];
148
+ /** All issues */
149
+ issues?: QAIssue[];
130
150
  }
131
151
  interface VisualQAResults {
132
152
  /** Whitespace percentage (target: 35%+ keynote, 25%+ business) */
@@ -391,19 +411,28 @@ interface SparklineStructure {
391
411
  }
392
412
  declare class ValidationError extends Error {
393
413
  errors: string[];
414
+ readonly suggestions: string[];
394
415
  constructor(errors: string[], message?: string);
416
+ private static getSuggestions;
395
417
  }
396
418
  declare class QAFailureError extends Error {
397
419
  score: number;
398
420
  threshold: number;
399
421
  qaResults: QAResults;
422
+ readonly topIssues: string[];
400
423
  constructor(score: number, threshold: number, qaResults: QAResults, message?: string);
401
424
  getIssues(): string[];
425
+ private static getTopIssues;
402
426
  }
403
427
  declare class TemplateNotFoundError extends Error {
404
428
  templatePath: string;
405
429
  constructor(templatePath: string, message?: string);
406
430
  }
431
+ declare class KnowledgeBaseError extends Error {
432
+ field: string;
433
+ context: string;
434
+ constructor(field: string, context: string, message?: string);
435
+ }
407
436
  /**
408
437
  * Content pattern detected from a section
409
438
  * Used to match content to appropriate slide types
@@ -2173,4 +2202,4 @@ declare const _default: {
2173
2202
  VERSION: string;
2174
2203
  };
2175
2204
 
2176
- export { type AccessibilityResults, type ChartData, type ChartDataset, ChartJsProvider, type ChartProvider, type ChartRequest, type ChartResult, type ChartType, type ColorPalette, CompositeChartProvider, CompositeImageProvider, type ContentAnalysis$1 as ContentAnalysis, ContentAnalyzer, type ContentPattern, ContentPatternClassifier, type ContentQAResults, type ContentSection$1 as ContentSection, type ContrastIssue, type ExpertQAResults, type ExpertValidation, type FontSizeIssue, type GlanceTestResult, type ImageData, type ImageProvider, type ImageRequest, type ImageResult, KnowledgeGateway, type LegacySlide, LocalImageProvider, MermaidProvider, type MetricData, type OneIdeaResult, type OutputFormat, PlaceholderImageProvider, PowerPointGenerator, type PresentationConfig, PresentationEngine, type PresentationMetadata, type PresentationMode$1 as PresentationMode, type PresentationQualityScore, type PresentationResult, type PresentationType, QAEngine, QAFailureError, type QAIssue, type QAResults, QuickChartProvider, RevealJsGenerator, type SCQAStructure, ScoreCalculator, type ScoringWeights, type SevenDimensionQAResult, type SignalNoiseResult, type Slide, type SlideContentScore, type SlideData, SlideFactory, SlideGenerator, type SlideTemplate, type SlideType, type SlideValidationResult, type SlideVisualScore, type SparklineStructure, type StoryStructure, TemplateEngine, TemplateNotFoundError, type ThemeName, type TypographyRules, UnsplashImageProvider, VERSION, ValidationError, type ValidationRules, type VisualQAResults, VisualQualityEvaluator, createDefaultChartProvider, createDefaultImageProvider, createSlideFactory, _default as default, evaluatePresentation, generate, getKnowledgeGateway, initSlideGenerator, validate };
2205
+ export { type AccessibilityResults, type ChartData, type ChartDataset, ChartJsProvider, type ChartProvider, type ChartRequest, type ChartResult, type ChartType, type ColorPalette, CompositeChartProvider, CompositeImageProvider, type ContentAnalysis$1 as ContentAnalysis, ContentAnalyzer, type ContentPattern, ContentPatternClassifier, type ContentQAResults, type ContentSection$1 as ContentSection, type ContrastIssue, type ExpertQAResults, type ExpertValidation, type FontSizeIssue, type GlanceTestResult, type ImageData, type ImageProvider, type ImageRequest, type ImageResult, KnowledgeBaseError, KnowledgeGateway, type LegacySlide, LocalImageProvider, MermaidProvider, type MetricData, type OneIdeaResult, type OutputFormat, PlaceholderImageProvider, PowerPointGenerator, type PresentationConfig, PresentationEngine, type PresentationMetadata, type PresentationMode$1 as PresentationMode, type PresentationQualityScore, type PresentationResult, type PresentationType, QAEngine, QAFailureError, type QAIssue, type QAResults, QuickChartProvider, RevealJsGenerator, type SCQAStructure, ScoreCalculator, type ScoringWeights, type SevenDimensionQAResult, type SignalNoiseResult, type Slide, type SlideContentScore, type SlideData, SlideFactory, SlideGenerator, type SlideScore, type SlideTemplate, type SlideType, type SlideValidationResult, type SlideVisualScore, type SparklineStructure, type StoryStructure, TemplateEngine, TemplateNotFoundError, type ThemeName, type TypographyRules, UnsplashImageProvider, VERSION, ValidationError, type ValidationRules, type VisualQAResults, VisualQualityEvaluator, createDefaultChartProvider, createDefaultImageProvider, createSlideFactory, _default as default, evaluatePresentation, generate, getKnowledgeGateway, initSlideGenerator, validate };
package/dist/index.d.ts CHANGED
@@ -127,6 +127,26 @@ interface QAResults {
127
127
  passed: boolean;
128
128
  /** List of issues found */
129
129
  issues: QAIssue[];
130
+ /** Per-slide scores from visual QA */
131
+ slideScores?: SlideScore[];
132
+ /** Overall QA score */
133
+ score?: number;
134
+ /** QA status */
135
+ status?: string;
136
+ }
137
+ interface SlideScore {
138
+ /** Slide index */
139
+ index: number;
140
+ /** Slide type */
141
+ type: string;
142
+ /** Score 0-100 */
143
+ score: number;
144
+ /** Expert verdict */
145
+ expertVerdict?: string;
146
+ /** Critical issues found */
147
+ criticalIssues?: string[];
148
+ /** All issues */
149
+ issues?: QAIssue[];
130
150
  }
131
151
  interface VisualQAResults {
132
152
  /** Whitespace percentage (target: 35%+ keynote, 25%+ business) */
@@ -391,19 +411,28 @@ interface SparklineStructure {
391
411
  }
392
412
  declare class ValidationError extends Error {
393
413
  errors: string[];
414
+ readonly suggestions: string[];
394
415
  constructor(errors: string[], message?: string);
416
+ private static getSuggestions;
395
417
  }
396
418
  declare class QAFailureError extends Error {
397
419
  score: number;
398
420
  threshold: number;
399
421
  qaResults: QAResults;
422
+ readonly topIssues: string[];
400
423
  constructor(score: number, threshold: number, qaResults: QAResults, message?: string);
401
424
  getIssues(): string[];
425
+ private static getTopIssues;
402
426
  }
403
427
  declare class TemplateNotFoundError extends Error {
404
428
  templatePath: string;
405
429
  constructor(templatePath: string, message?: string);
406
430
  }
431
+ declare class KnowledgeBaseError extends Error {
432
+ field: string;
433
+ context: string;
434
+ constructor(field: string, context: string, message?: string);
435
+ }
407
436
  /**
408
437
  * Content pattern detected from a section
409
438
  * Used to match content to appropriate slide types
@@ -2173,4 +2202,4 @@ declare const _default: {
2173
2202
  VERSION: string;
2174
2203
  };
2175
2204
 
2176
- export { type AccessibilityResults, type ChartData, type ChartDataset, ChartJsProvider, type ChartProvider, type ChartRequest, type ChartResult, type ChartType, type ColorPalette, CompositeChartProvider, CompositeImageProvider, type ContentAnalysis$1 as ContentAnalysis, ContentAnalyzer, type ContentPattern, ContentPatternClassifier, type ContentQAResults, type ContentSection$1 as ContentSection, type ContrastIssue, type ExpertQAResults, type ExpertValidation, type FontSizeIssue, type GlanceTestResult, type ImageData, type ImageProvider, type ImageRequest, type ImageResult, KnowledgeGateway, type LegacySlide, LocalImageProvider, MermaidProvider, type MetricData, type OneIdeaResult, type OutputFormat, PlaceholderImageProvider, PowerPointGenerator, type PresentationConfig, PresentationEngine, type PresentationMetadata, type PresentationMode$1 as PresentationMode, type PresentationQualityScore, type PresentationResult, type PresentationType, QAEngine, QAFailureError, type QAIssue, type QAResults, QuickChartProvider, RevealJsGenerator, type SCQAStructure, ScoreCalculator, type ScoringWeights, type SevenDimensionQAResult, type SignalNoiseResult, type Slide, type SlideContentScore, type SlideData, SlideFactory, SlideGenerator, type SlideTemplate, type SlideType, type SlideValidationResult, type SlideVisualScore, type SparklineStructure, type StoryStructure, TemplateEngine, TemplateNotFoundError, type ThemeName, type TypographyRules, UnsplashImageProvider, VERSION, ValidationError, type ValidationRules, type VisualQAResults, VisualQualityEvaluator, createDefaultChartProvider, createDefaultImageProvider, createSlideFactory, _default as default, evaluatePresentation, generate, getKnowledgeGateway, initSlideGenerator, validate };
2205
+ export { type AccessibilityResults, type ChartData, type ChartDataset, ChartJsProvider, type ChartProvider, type ChartRequest, type ChartResult, type ChartType, type ColorPalette, CompositeChartProvider, CompositeImageProvider, type ContentAnalysis$1 as ContentAnalysis, ContentAnalyzer, type ContentPattern, ContentPatternClassifier, type ContentQAResults, type ContentSection$1 as ContentSection, type ContrastIssue, type ExpertQAResults, type ExpertValidation, type FontSizeIssue, type GlanceTestResult, type ImageData, type ImageProvider, type ImageRequest, type ImageResult, KnowledgeBaseError, KnowledgeGateway, type LegacySlide, LocalImageProvider, MermaidProvider, type MetricData, type OneIdeaResult, type OutputFormat, PlaceholderImageProvider, PowerPointGenerator, type PresentationConfig, PresentationEngine, type PresentationMetadata, type PresentationMode$1 as PresentationMode, type PresentationQualityScore, type PresentationResult, type PresentationType, QAEngine, QAFailureError, type QAIssue, type QAResults, QuickChartProvider, RevealJsGenerator, type SCQAStructure, ScoreCalculator, type ScoringWeights, type SevenDimensionQAResult, type SignalNoiseResult, type Slide, type SlideContentScore, type SlideData, SlideFactory, SlideGenerator, type SlideScore, type SlideTemplate, type SlideType, type SlideValidationResult, type SlideVisualScore, type SparklineStructure, type StoryStructure, TemplateEngine, TemplateNotFoundError, type ThemeName, type TypographyRules, UnsplashImageProvider, VERSION, ValidationError, type ValidationRules, type VisualQAResults, VisualQualityEvaluator, createDefaultChartProvider, createDefaultImageProvider, createSlideFactory, _default as default, evaluatePresentation, generate, getKnowledgeGateway, initSlideGenerator, validate };
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CompositeImageProvider: () => CompositeImageProvider,
36
36
  ContentAnalyzer: () => ContentAnalyzer,
37
37
  ContentPatternClassifier: () => ContentPatternClassifier,
38
+ KnowledgeBaseError: () => KnowledgeBaseError,
38
39
  KnowledgeGateway: () => KnowledgeGateway,
39
40
  LocalImageProvider: () => LocalImageProvider,
40
41
  MermaidProvider: () => MermaidProvider,
@@ -67,32 +68,100 @@ __export(index_exports, {
67
68
  module.exports = __toCommonJS(index_exports);
68
69
 
69
70
  // src/types/index.ts
70
- var ValidationError = class extends Error {
71
+ var ValidationError = class _ValidationError extends Error {
71
72
  constructor(errors, message = "Validation failed") {
72
- super(message);
73
+ const errorList = errors.map((e) => ` \u2022 ${e}`).join("\n");
74
+ const suggestions = _ValidationError.getSuggestions(errors);
75
+ const suggestionList = suggestions.length > 0 ? "\n\nHow to fix:\n" + suggestions.map((s) => ` \u2192 ${s}`).join("\n") : "";
76
+ super(`${message}
77
+
78
+ Issues found:
79
+ ${errorList}${suggestionList}`);
73
80
  this.errors = errors;
74
81
  this.name = "ValidationError";
82
+ this.suggestions = suggestions;
83
+ }
84
+ suggestions;
85
+ static getSuggestions(errors) {
86
+ const suggestions = [];
87
+ for (const error of errors) {
88
+ if (error.includes("Content is required")) {
89
+ suggestions.push("Provide markdown content with at least one ## section header");
90
+ }
91
+ if (error.includes("Mode must be")) {
92
+ suggestions.push('Use mode: "keynote" for TED-style or mode: "business" for corporate');
93
+ }
94
+ if (error.includes("Title is required")) {
95
+ suggestions.push('Add a title: "Your Presentation Title" to your config');
96
+ }
97
+ if (error.includes("output format")) {
98
+ suggestions.push('Specify format: ["html"] or format: ["html", "pdf"]');
99
+ }
100
+ }
101
+ return [...new Set(suggestions)];
75
102
  }
76
103
  };
77
- var QAFailureError = class extends Error {
78
- constructor(score, threshold, qaResults, message = `QA score ${score} below threshold ${threshold}`) {
79
- super(message);
104
+ var QAFailureError = class _QAFailureError extends Error {
105
+ constructor(score, threshold, qaResults, message) {
106
+ const topIssues = _QAFailureError.getTopIssues(qaResults);
107
+ const issueList = topIssues.map((i) => ` \u2022 ${i}`).join("\n");
108
+ const helpText = `
109
+
110
+ Score: ${score}/100 (threshold: ${threshold})
111
+
112
+ Top issues to fix:
113
+ ${issueList}
114
+
115
+ How to improve:
116
+ \u2192 Ensure each slide has meaningful content (not just a title)
117
+ \u2192 Keep text concise - aim for <40 words per slide
118
+ \u2192 Use timeline/process slides for step-by-step content
119
+ \u2192 Add visuals for data-heavy slides`;
120
+ super(message || `QA score ${score} below threshold ${threshold}${helpText}`);
80
121
  this.score = score;
81
122
  this.threshold = threshold;
82
123
  this.qaResults = qaResults;
83
124
  this.name = "QAFailureError";
125
+ this.topIssues = topIssues;
84
126
  }
127
+ topIssues;
85
128
  getIssues() {
86
129
  return this.qaResults.issues.map((issue) => issue.message);
87
130
  }
131
+ static getTopIssues(qaResults) {
132
+ const issues = [];
133
+ if (qaResults.slideScores) {
134
+ for (const slide of qaResults.slideScores) {
135
+ if (slide.criticalIssues) {
136
+ issues.push(...slide.criticalIssues);
137
+ }
138
+ }
139
+ }
140
+ if (qaResults.issues) {
141
+ issues.push(...qaResults.issues.map((i) => i.message));
142
+ }
143
+ return [...new Set(issues)].slice(0, 5);
144
+ }
88
145
  };
89
146
  var TemplateNotFoundError = class extends Error {
90
- constructor(templatePath, message = `Template not found: ${templatePath}`) {
91
- super(message);
147
+ constructor(templatePath, message) {
148
+ super(message || `Template not found: ${templatePath}
149
+
150
+ Available templates: title, content, timeline, process, metrics, quote, cta, thank-you`);
92
151
  this.templatePath = templatePath;
93
152
  this.name = "TemplateNotFoundError";
94
153
  }
95
154
  };
155
+ var KnowledgeBaseError = class extends Error {
156
+ constructor(field, context, message) {
157
+ super(message || `Knowledge base configuration error: Missing "${field}" in ${context}
158
+
159
+ Check assets/presentation-knowledge.yaml for the correct structure.`);
160
+ this.field = field;
161
+ this.context = context;
162
+ this.name = "KnowledgeBaseError";
163
+ }
164
+ };
96
165
 
97
166
  // src/core/PresentationEngine.ts
98
167
  var fs2 = __toESM(require("fs"));
@@ -102,7 +171,6 @@ var os = __toESM(require("os"));
102
171
  // src/kb/KnowledgeGateway.ts
103
172
  var import_fs = require("fs");
104
173
  var import_path = require("path");
105
- var import_url = require("url");
106
174
  var yaml = __toESM(require("yaml"));
107
175
 
108
176
  // src/utils/Logger.ts
@@ -173,11 +241,7 @@ var logger = new Logger({
173
241
  });
174
242
 
175
243
  // src/kb/KnowledgeGateway.ts
176
- var import_meta = {};
177
244
  function getModuleDir() {
178
- if (typeof import_meta !== "undefined" && import_meta.url) {
179
- return (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
180
- }
181
245
  if (typeof __dirname !== "undefined") {
182
246
  return __dirname;
183
247
  }
@@ -2867,25 +2931,43 @@ var SlideFactory = class {
2867
2931
  createComparisonSlide(index, section) {
2868
2932
  const comparison = this.classifier.extractComparison(section);
2869
2933
  const labels = this.config.defaults.comparison;
2934
+ const title = this.createTitle(section.header, section);
2935
+ const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
2870
2936
  const leftFallback = labels.optionLabels[0] ?? "Option A";
2871
2937
  const rightFallback = labels.optionLabels[1] ?? "Option B";
2872
- const leftColumn = comparison?.left || section.bullets[0] || leftFallback;
2873
- const rightColumn = comparison?.right || section.bullets[1] || rightFallback;
2938
+ let leftColumn = comparison?.left || section.bullets[0] || "";
2939
+ let rightColumn = comparison?.right || section.bullets[1] || "";
2940
+ const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
2941
+ const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
2942
+ const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
2943
+ const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
2944
+ if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
2945
+ if (section.bullets.length >= 2) {
2946
+ logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
2947
+ return this.createBulletSlide(index, section);
2948
+ }
2949
+ if (leftIsDuplicate && rightIsDuplicate) {
2950
+ logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
2951
+ return null;
2952
+ }
2953
+ if (leftIsDuplicate) leftColumn = leftFallback;
2954
+ if (rightIsDuplicate) rightColumn = rightFallback;
2955
+ }
2874
2956
  return {
2875
2957
  index,
2876
2958
  type: "comparison",
2877
2959
  data: {
2878
- title: this.createTitle(section.header, section),
2960
+ title,
2879
2961
  columns: [
2880
2962
  {
2881
2963
  title: labels.leftLabel,
2882
2964
  // FROM KB - not hardcoded 'Before'
2883
- content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
2965
+ content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
2884
2966
  },
2885
2967
  {
2886
2968
  title: labels.rightLabel,
2887
2969
  // FROM KB - not hardcoded 'After'
2888
- content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
2970
+ content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
2889
2971
  }
2890
2972
  ]
2891
2973
  },
@@ -3156,10 +3238,21 @@ var SlideFactory = class {
3156
3238
  * Used for section headers with strong conclusions.
3157
3239
  */
3158
3240
  createTitleImpactSlide(index, section) {
3241
+ const title = this.cleanText(section.header);
3159
3242
  const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
3160
3243
  const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
3244
+ const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
3245
+ const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
3246
+ const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
3247
+ if (bodyIsDuplicate) {
3248
+ if (section.bullets.length >= 2 && this.config.mode === "business") {
3249
+ return this.createBulletSlide(index, section);
3250
+ }
3251
+ logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
3252
+ return null;
3253
+ }
3161
3254
  const data = {
3162
- title: this.cleanText(section.header)
3255
+ title
3163
3256
  };
3164
3257
  if (truncatedSupport) {
3165
3258
  data.body = truncatedSupport;
@@ -7675,13 +7768,63 @@ var PowerPointGenerator = class {
7675
7768
 
7676
7769
  // src/generators/PDFGenerator.ts
7677
7770
  var import_puppeteer = __toESM(require("puppeteer"));
7771
+ var cachedBrowser = null;
7772
+ var browserIdleTimeout = null;
7773
+ var BROWSER_IDLE_MS = 3e4;
7678
7774
  var PDFGenerator = class {
7679
7775
  defaultOptions = {
7680
7776
  orientation: "landscape",
7681
7777
  printBackground: true,
7682
7778
  format: "Slide",
7683
- scale: 1
7779
+ scale: 1,
7780
+ reuseBrowser: true
7684
7781
  };
7782
+ /**
7783
+ * Get or create a browser instance
7784
+ */
7785
+ async getBrowser(reuse) {
7786
+ if (browserIdleTimeout) {
7787
+ clearTimeout(browserIdleTimeout);
7788
+ browserIdleTimeout = null;
7789
+ }
7790
+ if (reuse && cachedBrowser && cachedBrowser.connected) {
7791
+ logger.progress("Using cached browser instance");
7792
+ return cachedBrowser;
7793
+ }
7794
+ if (cachedBrowser && !cachedBrowser.connected) {
7795
+ cachedBrowser = null;
7796
+ }
7797
+ logger.progress("Launching new browser instance...");
7798
+ const browser = await import_puppeteer.default.launch({
7799
+ headless: true,
7800
+ args: [
7801
+ "--no-sandbox",
7802
+ "--disable-setuid-sandbox",
7803
+ "--disable-dev-shm-usage",
7804
+ "--disable-gpu"
7805
+ ]
7806
+ });
7807
+ if (reuse) {
7808
+ cachedBrowser = browser;
7809
+ }
7810
+ return browser;
7811
+ }
7812
+ /**
7813
+ * Schedule browser cleanup after idle period
7814
+ */
7815
+ scheduleBrowserCleanup() {
7816
+ if (browserIdleTimeout) {
7817
+ clearTimeout(browserIdleTimeout);
7818
+ }
7819
+ browserIdleTimeout = setTimeout(async () => {
7820
+ if (cachedBrowser) {
7821
+ logger.progress("Closing idle browser instance");
7822
+ await cachedBrowser.close().catch(() => {
7823
+ });
7824
+ cachedBrowser = null;
7825
+ }
7826
+ }, BROWSER_IDLE_MS);
7827
+ }
7685
7828
  /**
7686
7829
  * Generate PDF from HTML presentation
7687
7830
  * @param html - The HTML content of the presentation
@@ -7690,18 +7833,14 @@ var PDFGenerator = class {
7690
7833
  */
7691
7834
  async generate(html, options = {}) {
7692
7835
  const opts = { ...this.defaultOptions, ...options };
7836
+ const reuseBrowser = opts.reuseBrowser ?? true;
7693
7837
  logger.progress("\u{1F4C4} Generating PDF...");
7694
- let browser;
7838
+ let browser = null;
7839
+ let shouldCloseBrowser = !reuseBrowser;
7840
+ let page = null;
7695
7841
  try {
7696
- browser = await import_puppeteer.default.launch({
7697
- headless: true,
7698
- args: [
7699
- "--no-sandbox",
7700
- "--disable-setuid-sandbox",
7701
- "--disable-dev-shm-usage"
7702
- ]
7703
- });
7704
- const page = await browser.newPage();
7842
+ browser = await this.getBrowser(reuseBrowser);
7843
+ page = await browser.newPage();
7705
7844
  const printHtml = this.preparePrintHtml(html);
7706
7845
  await page.setViewport({
7707
7846
  width: 1920,
@@ -7737,13 +7876,38 @@ var PDFGenerator = class {
7737
7876
  } catch (error) {
7738
7877
  const errorMessage = error instanceof Error ? error.message : String(error);
7739
7878
  logger.error(`PDF generation failed: ${errorMessage}`);
7879
+ shouldCloseBrowser = true;
7740
7880
  throw new Error(`PDF generation failed: ${errorMessage}`);
7741
7881
  } finally {
7742
- if (browser) {
7743
- await browser.close();
7882
+ if (page) {
7883
+ await page.close().catch(() => {
7884
+ });
7885
+ }
7886
+ if (shouldCloseBrowser && browser) {
7887
+ await browser.close().catch(() => {
7888
+ });
7889
+ if (browser === cachedBrowser) {
7890
+ cachedBrowser = null;
7891
+ }
7892
+ } else if (reuseBrowser) {
7893
+ this.scheduleBrowserCleanup();
7744
7894
  }
7745
7895
  }
7746
7896
  }
7897
+ /**
7898
+ * Manually close the cached browser (call before process exit)
7899
+ */
7900
+ static async closeBrowser() {
7901
+ if (browserIdleTimeout) {
7902
+ clearTimeout(browserIdleTimeout);
7903
+ browserIdleTimeout = null;
7904
+ }
7905
+ if (cachedBrowser) {
7906
+ await cachedBrowser.close().catch(() => {
7907
+ });
7908
+ cachedBrowser = null;
7909
+ }
7910
+ }
7747
7911
  /**
7748
7912
  * Prepare HTML for print/PDF output
7749
7913
  * Modifies the HTML to work better as a printed document
@@ -7757,12 +7921,24 @@ var PDFGenerator = class {
7757
7921
  margin: 0;
7758
7922
  }
7759
7923
 
7924
+ /* Font rendering optimization for print */
7925
+ * {
7926
+ -webkit-font-smoothing: antialiased;
7927
+ -moz-osx-font-smoothing: grayscale;
7928
+ text-rendering: optimizeLegibility;
7929
+ font-feature-settings: "liga" 1, "kern" 1;
7930
+ }
7931
+
7760
7932
  @media print {
7761
7933
  html, body {
7762
7934
  margin: 0;
7763
7935
  padding: 0;
7764
7936
  width: 1920px;
7765
7937
  height: 1080px;
7938
+ /* Print color optimization */
7939
+ color-adjust: exact;
7940
+ -webkit-print-color-adjust: exact;
7941
+ print-color-adjust: exact;
7766
7942
  }
7767
7943
 
7768
7944
  .reveal .slides {
@@ -7779,7 +7955,8 @@ var PDFGenerator = class {
7779
7955
  width: 1920px !important;
7780
7956
  height: 1080px !important;
7781
7957
  margin: 0 !important;
7782
- padding: 60px !important;
7958
+ /* Professional safe margins: 80px (4.2%) */
7959
+ padding: 80px !important;
7783
7960
  box-sizing: border-box;
7784
7961
  position: relative !important;
7785
7962
  top: auto !important;
@@ -7803,7 +7980,7 @@ var PDFGenerator = class {
7803
7980
  }
7804
7981
 
7805
7982
  /* Disable animations for print */
7806
- * {
7983
+ *, *::before, *::after {
7807
7984
  animation: none !important;
7808
7985
  transition: none !important;
7809
7986
  }
@@ -7819,6 +7996,30 @@ var PDFGenerator = class {
7819
7996
  .reveal .navigate-down {
7820
7997
  display: none !important;
7821
7998
  }
7999
+
8000
+ /* Typography refinements for print */
8001
+ h1, h2, h3, h4 {
8002
+ orphans: 3;
8003
+ widows: 3;
8004
+ page-break-after: avoid;
8005
+ }
8006
+
8007
+ p, li {
8008
+ orphans: 2;
8009
+ widows: 2;
8010
+ }
8011
+
8012
+ /* Ensure links are readable in print */
8013
+ a {
8014
+ text-decoration: none;
8015
+ }
8016
+
8017
+ /* Prevent image overflow */
8018
+ img {
8019
+ max-width: 100%;
8020
+ height: auto;
8021
+ page-break-inside: avoid;
8022
+ }
7822
8023
  }
7823
8024
 
7824
8025
  /* Force print mode in Puppeteer */
@@ -7826,6 +8027,11 @@ var PDFGenerator = class {
7826
8027
  page-break-after: always;
7827
8028
  page-break-inside: avoid;
7828
8029
  }
8030
+
8031
+ /* High contrast mode for business presentations */
8032
+ .reveal section {
8033
+ color-scheme: light;
8034
+ }
7829
8035
  </style>
7830
8036
  `;
7831
8037
  if (html.includes("</head>")) {
@@ -8419,6 +8625,7 @@ var index_default = {
8419
8625
  CompositeImageProvider,
8420
8626
  ContentAnalyzer,
8421
8627
  ContentPatternClassifier,
8628
+ KnowledgeBaseError,
8422
8629
  KnowledgeGateway,
8423
8630
  LocalImageProvider,
8424
8631
  MermaidProvider,
package/dist/index.mjs CHANGED
@@ -1,30 +1,98 @@
1
1
  // src/types/index.ts
2
- var ValidationError = class extends Error {
2
+ var ValidationError = class _ValidationError extends Error {
3
3
  constructor(errors, message = "Validation failed") {
4
- super(message);
4
+ const errorList = errors.map((e) => ` \u2022 ${e}`).join("\n");
5
+ const suggestions = _ValidationError.getSuggestions(errors);
6
+ const suggestionList = suggestions.length > 0 ? "\n\nHow to fix:\n" + suggestions.map((s) => ` \u2192 ${s}`).join("\n") : "";
7
+ super(`${message}
8
+
9
+ Issues found:
10
+ ${errorList}${suggestionList}`);
5
11
  this.errors = errors;
6
12
  this.name = "ValidationError";
13
+ this.suggestions = suggestions;
14
+ }
15
+ suggestions;
16
+ static getSuggestions(errors) {
17
+ const suggestions = [];
18
+ for (const error of errors) {
19
+ if (error.includes("Content is required")) {
20
+ suggestions.push("Provide markdown content with at least one ## section header");
21
+ }
22
+ if (error.includes("Mode must be")) {
23
+ suggestions.push('Use mode: "keynote" for TED-style or mode: "business" for corporate');
24
+ }
25
+ if (error.includes("Title is required")) {
26
+ suggestions.push('Add a title: "Your Presentation Title" to your config');
27
+ }
28
+ if (error.includes("output format")) {
29
+ suggestions.push('Specify format: ["html"] or format: ["html", "pdf"]');
30
+ }
31
+ }
32
+ return [...new Set(suggestions)];
7
33
  }
8
34
  };
9
- var QAFailureError = class extends Error {
10
- constructor(score, threshold, qaResults, message = `QA score ${score} below threshold ${threshold}`) {
11
- super(message);
35
+ var QAFailureError = class _QAFailureError extends Error {
36
+ constructor(score, threshold, qaResults, message) {
37
+ const topIssues = _QAFailureError.getTopIssues(qaResults);
38
+ const issueList = topIssues.map((i) => ` \u2022 ${i}`).join("\n");
39
+ const helpText = `
40
+
41
+ Score: ${score}/100 (threshold: ${threshold})
42
+
43
+ Top issues to fix:
44
+ ${issueList}
45
+
46
+ How to improve:
47
+ \u2192 Ensure each slide has meaningful content (not just a title)
48
+ \u2192 Keep text concise - aim for <40 words per slide
49
+ \u2192 Use timeline/process slides for step-by-step content
50
+ \u2192 Add visuals for data-heavy slides`;
51
+ super(message || `QA score ${score} below threshold ${threshold}${helpText}`);
12
52
  this.score = score;
13
53
  this.threshold = threshold;
14
54
  this.qaResults = qaResults;
15
55
  this.name = "QAFailureError";
56
+ this.topIssues = topIssues;
16
57
  }
58
+ topIssues;
17
59
  getIssues() {
18
60
  return this.qaResults.issues.map((issue) => issue.message);
19
61
  }
62
+ static getTopIssues(qaResults) {
63
+ const issues = [];
64
+ if (qaResults.slideScores) {
65
+ for (const slide of qaResults.slideScores) {
66
+ if (slide.criticalIssues) {
67
+ issues.push(...slide.criticalIssues);
68
+ }
69
+ }
70
+ }
71
+ if (qaResults.issues) {
72
+ issues.push(...qaResults.issues.map((i) => i.message));
73
+ }
74
+ return [...new Set(issues)].slice(0, 5);
75
+ }
20
76
  };
21
77
  var TemplateNotFoundError = class extends Error {
22
- constructor(templatePath, message = `Template not found: ${templatePath}`) {
23
- super(message);
78
+ constructor(templatePath, message) {
79
+ super(message || `Template not found: ${templatePath}
80
+
81
+ Available templates: title, content, timeline, process, metrics, quote, cta, thank-you`);
24
82
  this.templatePath = templatePath;
25
83
  this.name = "TemplateNotFoundError";
26
84
  }
27
85
  };
86
+ var KnowledgeBaseError = class extends Error {
87
+ constructor(field, context, message) {
88
+ super(message || `Knowledge base configuration error: Missing "${field}" in ${context}
89
+
90
+ Check assets/presentation-knowledge.yaml for the correct structure.`);
91
+ this.field = field;
92
+ this.context = context;
93
+ this.name = "KnowledgeBaseError";
94
+ }
95
+ };
28
96
 
29
97
  // src/core/PresentationEngine.ts
30
98
  import * as fs2 from "fs";
@@ -33,8 +101,7 @@ import * as os from "os";
33
101
 
34
102
  // src/kb/KnowledgeGateway.ts
35
103
  import { readFileSync } from "fs";
36
- import { join, dirname } from "path";
37
- import { fileURLToPath } from "url";
104
+ import { join } from "path";
38
105
  import * as yaml from "yaml";
39
106
 
40
107
  // src/utils/Logger.ts
@@ -106,9 +173,6 @@ var logger = new Logger({
106
173
 
107
174
  // src/kb/KnowledgeGateway.ts
108
175
  function getModuleDir() {
109
- if (typeof import.meta !== "undefined" && import.meta.url) {
110
- return dirname(fileURLToPath(import.meta.url));
111
- }
112
176
  if (typeof __dirname !== "undefined") {
113
177
  return __dirname;
114
178
  }
@@ -2798,25 +2862,43 @@ var SlideFactory = class {
2798
2862
  createComparisonSlide(index, section) {
2799
2863
  const comparison = this.classifier.extractComparison(section);
2800
2864
  const labels = this.config.defaults.comparison;
2865
+ const title = this.createTitle(section.header, section);
2866
+ const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
2801
2867
  const leftFallback = labels.optionLabels[0] ?? "Option A";
2802
2868
  const rightFallback = labels.optionLabels[1] ?? "Option B";
2803
- const leftColumn = comparison?.left || section.bullets[0] || leftFallback;
2804
- const rightColumn = comparison?.right || section.bullets[1] || rightFallback;
2869
+ let leftColumn = comparison?.left || section.bullets[0] || "";
2870
+ let rightColumn = comparison?.right || section.bullets[1] || "";
2871
+ const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
2872
+ const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
2873
+ const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
2874
+ const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
2875
+ if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
2876
+ if (section.bullets.length >= 2) {
2877
+ logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
2878
+ return this.createBulletSlide(index, section);
2879
+ }
2880
+ if (leftIsDuplicate && rightIsDuplicate) {
2881
+ logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
2882
+ return null;
2883
+ }
2884
+ if (leftIsDuplicate) leftColumn = leftFallback;
2885
+ if (rightIsDuplicate) rightColumn = rightFallback;
2886
+ }
2805
2887
  return {
2806
2888
  index,
2807
2889
  type: "comparison",
2808
2890
  data: {
2809
- title: this.createTitle(section.header, section),
2891
+ title,
2810
2892
  columns: [
2811
2893
  {
2812
2894
  title: labels.leftLabel,
2813
2895
  // FROM KB - not hardcoded 'Before'
2814
- content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
2896
+ content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
2815
2897
  },
2816
2898
  {
2817
2899
  title: labels.rightLabel,
2818
2900
  // FROM KB - not hardcoded 'After'
2819
- content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
2901
+ content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
2820
2902
  }
2821
2903
  ]
2822
2904
  },
@@ -3087,10 +3169,21 @@ var SlideFactory = class {
3087
3169
  * Used for section headers with strong conclusions.
3088
3170
  */
3089
3171
  createTitleImpactSlide(index, section) {
3172
+ const title = this.cleanText(section.header);
3090
3173
  const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
3091
3174
  const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
3175
+ const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
3176
+ const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
3177
+ const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
3178
+ if (bodyIsDuplicate) {
3179
+ if (section.bullets.length >= 2 && this.config.mode === "business") {
3180
+ return this.createBulletSlide(index, section);
3181
+ }
3182
+ logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
3183
+ return null;
3184
+ }
3092
3185
  const data = {
3093
- title: this.cleanText(section.header)
3186
+ title
3094
3187
  };
3095
3188
  if (truncatedSupport) {
3096
3189
  data.body = truncatedSupport;
@@ -7606,13 +7699,63 @@ var PowerPointGenerator = class {
7606
7699
 
7607
7700
  // src/generators/PDFGenerator.ts
7608
7701
  import puppeteer from "puppeteer";
7702
+ var cachedBrowser = null;
7703
+ var browserIdleTimeout = null;
7704
+ var BROWSER_IDLE_MS = 3e4;
7609
7705
  var PDFGenerator = class {
7610
7706
  defaultOptions = {
7611
7707
  orientation: "landscape",
7612
7708
  printBackground: true,
7613
7709
  format: "Slide",
7614
- scale: 1
7710
+ scale: 1,
7711
+ reuseBrowser: true
7615
7712
  };
7713
+ /**
7714
+ * Get or create a browser instance
7715
+ */
7716
+ async getBrowser(reuse) {
7717
+ if (browserIdleTimeout) {
7718
+ clearTimeout(browserIdleTimeout);
7719
+ browserIdleTimeout = null;
7720
+ }
7721
+ if (reuse && cachedBrowser && cachedBrowser.connected) {
7722
+ logger.progress("Using cached browser instance");
7723
+ return cachedBrowser;
7724
+ }
7725
+ if (cachedBrowser && !cachedBrowser.connected) {
7726
+ cachedBrowser = null;
7727
+ }
7728
+ logger.progress("Launching new browser instance...");
7729
+ const browser = await puppeteer.launch({
7730
+ headless: true,
7731
+ args: [
7732
+ "--no-sandbox",
7733
+ "--disable-setuid-sandbox",
7734
+ "--disable-dev-shm-usage",
7735
+ "--disable-gpu"
7736
+ ]
7737
+ });
7738
+ if (reuse) {
7739
+ cachedBrowser = browser;
7740
+ }
7741
+ return browser;
7742
+ }
7743
+ /**
7744
+ * Schedule browser cleanup after idle period
7745
+ */
7746
+ scheduleBrowserCleanup() {
7747
+ if (browserIdleTimeout) {
7748
+ clearTimeout(browserIdleTimeout);
7749
+ }
7750
+ browserIdleTimeout = setTimeout(async () => {
7751
+ if (cachedBrowser) {
7752
+ logger.progress("Closing idle browser instance");
7753
+ await cachedBrowser.close().catch(() => {
7754
+ });
7755
+ cachedBrowser = null;
7756
+ }
7757
+ }, BROWSER_IDLE_MS);
7758
+ }
7616
7759
  /**
7617
7760
  * Generate PDF from HTML presentation
7618
7761
  * @param html - The HTML content of the presentation
@@ -7621,18 +7764,14 @@ var PDFGenerator = class {
7621
7764
  */
7622
7765
  async generate(html, options = {}) {
7623
7766
  const opts = { ...this.defaultOptions, ...options };
7767
+ const reuseBrowser = opts.reuseBrowser ?? true;
7624
7768
  logger.progress("\u{1F4C4} Generating PDF...");
7625
- let browser;
7769
+ let browser = null;
7770
+ let shouldCloseBrowser = !reuseBrowser;
7771
+ let page = null;
7626
7772
  try {
7627
- browser = await puppeteer.launch({
7628
- headless: true,
7629
- args: [
7630
- "--no-sandbox",
7631
- "--disable-setuid-sandbox",
7632
- "--disable-dev-shm-usage"
7633
- ]
7634
- });
7635
- const page = await browser.newPage();
7773
+ browser = await this.getBrowser(reuseBrowser);
7774
+ page = await browser.newPage();
7636
7775
  const printHtml = this.preparePrintHtml(html);
7637
7776
  await page.setViewport({
7638
7777
  width: 1920,
@@ -7668,13 +7807,38 @@ var PDFGenerator = class {
7668
7807
  } catch (error) {
7669
7808
  const errorMessage = error instanceof Error ? error.message : String(error);
7670
7809
  logger.error(`PDF generation failed: ${errorMessage}`);
7810
+ shouldCloseBrowser = true;
7671
7811
  throw new Error(`PDF generation failed: ${errorMessage}`);
7672
7812
  } finally {
7673
- if (browser) {
7674
- await browser.close();
7813
+ if (page) {
7814
+ await page.close().catch(() => {
7815
+ });
7816
+ }
7817
+ if (shouldCloseBrowser && browser) {
7818
+ await browser.close().catch(() => {
7819
+ });
7820
+ if (browser === cachedBrowser) {
7821
+ cachedBrowser = null;
7822
+ }
7823
+ } else if (reuseBrowser) {
7824
+ this.scheduleBrowserCleanup();
7675
7825
  }
7676
7826
  }
7677
7827
  }
7828
+ /**
7829
+ * Manually close the cached browser (call before process exit)
7830
+ */
7831
+ static async closeBrowser() {
7832
+ if (browserIdleTimeout) {
7833
+ clearTimeout(browserIdleTimeout);
7834
+ browserIdleTimeout = null;
7835
+ }
7836
+ if (cachedBrowser) {
7837
+ await cachedBrowser.close().catch(() => {
7838
+ });
7839
+ cachedBrowser = null;
7840
+ }
7841
+ }
7678
7842
  /**
7679
7843
  * Prepare HTML for print/PDF output
7680
7844
  * Modifies the HTML to work better as a printed document
@@ -7688,12 +7852,24 @@ var PDFGenerator = class {
7688
7852
  margin: 0;
7689
7853
  }
7690
7854
 
7855
+ /* Font rendering optimization for print */
7856
+ * {
7857
+ -webkit-font-smoothing: antialiased;
7858
+ -moz-osx-font-smoothing: grayscale;
7859
+ text-rendering: optimizeLegibility;
7860
+ font-feature-settings: "liga" 1, "kern" 1;
7861
+ }
7862
+
7691
7863
  @media print {
7692
7864
  html, body {
7693
7865
  margin: 0;
7694
7866
  padding: 0;
7695
7867
  width: 1920px;
7696
7868
  height: 1080px;
7869
+ /* Print color optimization */
7870
+ color-adjust: exact;
7871
+ -webkit-print-color-adjust: exact;
7872
+ print-color-adjust: exact;
7697
7873
  }
7698
7874
 
7699
7875
  .reveal .slides {
@@ -7710,7 +7886,8 @@ var PDFGenerator = class {
7710
7886
  width: 1920px !important;
7711
7887
  height: 1080px !important;
7712
7888
  margin: 0 !important;
7713
- padding: 60px !important;
7889
+ /* Professional safe margins: 80px (4.2%) */
7890
+ padding: 80px !important;
7714
7891
  box-sizing: border-box;
7715
7892
  position: relative !important;
7716
7893
  top: auto !important;
@@ -7734,7 +7911,7 @@ var PDFGenerator = class {
7734
7911
  }
7735
7912
 
7736
7913
  /* Disable animations for print */
7737
- * {
7914
+ *, *::before, *::after {
7738
7915
  animation: none !important;
7739
7916
  transition: none !important;
7740
7917
  }
@@ -7750,6 +7927,30 @@ var PDFGenerator = class {
7750
7927
  .reveal .navigate-down {
7751
7928
  display: none !important;
7752
7929
  }
7930
+
7931
+ /* Typography refinements for print */
7932
+ h1, h2, h3, h4 {
7933
+ orphans: 3;
7934
+ widows: 3;
7935
+ page-break-after: avoid;
7936
+ }
7937
+
7938
+ p, li {
7939
+ orphans: 2;
7940
+ widows: 2;
7941
+ }
7942
+
7943
+ /* Ensure links are readable in print */
7944
+ a {
7945
+ text-decoration: none;
7946
+ }
7947
+
7948
+ /* Prevent image overflow */
7949
+ img {
7950
+ max-width: 100%;
7951
+ height: auto;
7952
+ page-break-inside: avoid;
7953
+ }
7753
7954
  }
7754
7955
 
7755
7956
  /* Force print mode in Puppeteer */
@@ -7757,6 +7958,11 @@ var PDFGenerator = class {
7757
7958
  page-break-after: always;
7758
7959
  page-break-inside: avoid;
7759
7960
  }
7961
+
7962
+ /* High contrast mode for business presentations */
7963
+ .reveal section {
7964
+ color-scheme: light;
7965
+ }
7760
7966
  </style>
7761
7967
  `;
7762
7968
  if (html.includes("</head>")) {
@@ -8349,6 +8555,7 @@ export {
8349
8555
  CompositeImageProvider,
8350
8556
  ContentAnalyzer,
8351
8557
  ContentPatternClassifier,
8558
+ KnowledgeBaseError,
8352
8559
  KnowledgeGateway,
8353
8560
  LocalImageProvider,
8354
8561
  MermaidProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-presentation-master",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "Generate world-class presentations using expert methodologies from Duarte, Reynolds, Gallo, and Anderson. Enforces rigorous quality standards through real visual validation.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",