claude-presentation-master 6.1.1 → 7.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/README.md +298 -562
- package/bin/auto-fix-presentation.cjs +446 -0
- package/bin/cli.js +95 -32
- package/bin/qa-presentation.cjs +279 -0
- package/bin/score-presentation.cjs +488 -0
- package/dist/index.d.mts +744 -66
- package/dist/index.d.ts +744 -66
- package/dist/index.js +2745 -549
- package/dist/index.mjs +2736 -549
- package/package.json +1 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AUTOMATED PRESENTATION QUALITY ASSURANCE
|
|
4
|
+
*
|
|
5
|
+
* Iteratively scores and fixes presentations until ALL dimensions hit 95+
|
|
6
|
+
* Target: 100, Minimum: 95
|
|
7
|
+
*
|
|
8
|
+
* Usage: node auto-fix-presentation.cjs <html-file>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { chromium } = require('playwright');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
const TARGET_SCORE = 95;
|
|
16
|
+
const MAX_ITERATIONS = 10;
|
|
17
|
+
|
|
18
|
+
// CSS fixes for common issues
|
|
19
|
+
const CSS_FIXES = {
|
|
20
|
+
smallText: {
|
|
21
|
+
selector: '.small-text-fix',
|
|
22
|
+
css: 'font-size: 1.25rem !important;'
|
|
23
|
+
},
|
|
24
|
+
lowContrast: {
|
|
25
|
+
selector: '.contrast-fix',
|
|
26
|
+
css: 'color: #ffffff !important; text-shadow: 0 2px 10px rgba(0,0,0,0.8) !important;'
|
|
27
|
+
},
|
|
28
|
+
titleContrast: {
|
|
29
|
+
selector: '.title-contrast-fix',
|
|
30
|
+
css: 'color: #ffffff !important; text-shadow: 0 4px 20px rgba(0,0,0,0.9) !important;'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function analyzeSlide(page) {
|
|
35
|
+
return await page.evaluate(() => {
|
|
36
|
+
const slide = document.querySelector('.present') || document.querySelector('section');
|
|
37
|
+
if (!slide) return { error: 'No slide found' };
|
|
38
|
+
|
|
39
|
+
function parseColor(colorStr) {
|
|
40
|
+
if (!colorStr) return null;
|
|
41
|
+
const match = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
42
|
+
if (match) return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getLuminance(rgb) {
|
|
47
|
+
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
|
|
48
|
+
c = c / 255;
|
|
49
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
50
|
+
});
|
|
51
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getContrast(fg, bg) {
|
|
55
|
+
if (!fg || !bg) return 21;
|
|
56
|
+
const l1 = getLuminance(fg);
|
|
57
|
+
const l2 = getLuminance(bg);
|
|
58
|
+
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getEffectiveBg(element) {
|
|
62
|
+
let el = element;
|
|
63
|
+
while (el && el !== document.body) {
|
|
64
|
+
const bg = window.getComputedStyle(el).backgroundColor;
|
|
65
|
+
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
|
|
66
|
+
const parsed = parseColor(bg);
|
|
67
|
+
if (parsed) return parsed;
|
|
68
|
+
}
|
|
69
|
+
el = el.parentElement;
|
|
70
|
+
}
|
|
71
|
+
return { r: 10, g: 10, b: 11 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const textContent = slide.innerText || '';
|
|
75
|
+
const words = textContent.split(/\\s+/).filter(w => w.length > 0);
|
|
76
|
+
const wordCount = words.length;
|
|
77
|
+
|
|
78
|
+
const allText = slide.querySelectorAll('h1, h2, h3, h4, h5, h6, p, li, span, div');
|
|
79
|
+
const textIssues = [];
|
|
80
|
+
|
|
81
|
+
allText.forEach(el => {
|
|
82
|
+
const text = el.innerText?.trim();
|
|
83
|
+
if (!text || text.length < 2) return;
|
|
84
|
+
|
|
85
|
+
const style = window.getComputedStyle(el);
|
|
86
|
+
const fontSize = parseFloat(style.fontSize);
|
|
87
|
+
let color = parseColor(style.color);
|
|
88
|
+
if (!color) color = { r: 255, g: 255, b: 255 };
|
|
89
|
+
const bgColor = getEffectiveBg(el);
|
|
90
|
+
const contrast = getContrast(color, bgColor);
|
|
91
|
+
const isTitle = el.tagName.match(/^H[1-3]$/) || fontSize >= 30;
|
|
92
|
+
|
|
93
|
+
if (fontSize < 18) {
|
|
94
|
+
textIssues.push({ type: 'smallText', element: el.tagName, text: text.substring(0, 30), fontSize });
|
|
95
|
+
}
|
|
96
|
+
if (isTitle && contrast < 7) {
|
|
97
|
+
textIssues.push({ type: 'titleContrast', element: el.tagName, text: text.substring(0, 30), contrast });
|
|
98
|
+
} else if (!isTitle && contrast < 4.5) {
|
|
99
|
+
textIssues.push({ type: 'bodyContrast', element: el.tagName, text: text.substring(0, 30), contrast });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const images = slide.querySelectorAll('img');
|
|
104
|
+
const majorElements = slide.querySelectorAll('h1, h2, h3, img, .card, .stat, ul, ol');
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
wordCount,
|
|
108
|
+
textIssues,
|
|
109
|
+
elementCount: majorElements.length,
|
|
110
|
+
imageCount: images.length,
|
|
111
|
+
hasTitle: !!slide.querySelector('h1, h2, h3')
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function calculateScores(analysis) {
|
|
117
|
+
let layout = 100, contrast = 100, graphics = 100;
|
|
118
|
+
let content = 100, clarity = 100, effectiveness = 100;
|
|
119
|
+
const issues = [];
|
|
120
|
+
|
|
121
|
+
// Layout scoring
|
|
122
|
+
if (analysis.wordCount > 40) {
|
|
123
|
+
layout -= Math.min(30, (analysis.wordCount - 40) * 2);
|
|
124
|
+
issues.push(`Words: ${analysis.wordCount} (max 40)`);
|
|
125
|
+
}
|
|
126
|
+
if (analysis.elementCount > 7) {
|
|
127
|
+
layout -= 15;
|
|
128
|
+
issues.push(`Elements: ${analysis.elementCount} (max 7)`);
|
|
129
|
+
}
|
|
130
|
+
const smallTextCount = analysis.textIssues.filter(i => i.type === 'smallText').length;
|
|
131
|
+
if (smallTextCount > 0) {
|
|
132
|
+
layout -= smallTextCount * 5;
|
|
133
|
+
issues.push(`Small text: ${smallTextCount} elements`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Contrast scoring
|
|
137
|
+
const titleContrastIssues = analysis.textIssues.filter(i => i.type === 'titleContrast');
|
|
138
|
+
const bodyContrastIssues = analysis.textIssues.filter(i => i.type === 'bodyContrast');
|
|
139
|
+
if (titleContrastIssues.length > 0) {
|
|
140
|
+
contrast -= titleContrastIssues.length * 20;
|
|
141
|
+
issues.push(`Title contrast issues: ${titleContrastIssues.length}`);
|
|
142
|
+
}
|
|
143
|
+
if (bodyContrastIssues.length > 0) {
|
|
144
|
+
contrast -= bodyContrastIssues.length * 10;
|
|
145
|
+
issues.push(`Body contrast issues: ${bodyContrastIssues.length}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Graphics scoring
|
|
149
|
+
if (analysis.imageCount === 0 && analysis.wordCount > 20) {
|
|
150
|
+
graphics -= 20;
|
|
151
|
+
issues.push('No images on text-heavy slide');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Content scoring
|
|
155
|
+
if (analysis.wordCount < 10 && analysis.wordCount > 0) content -= 10;
|
|
156
|
+
if (!analysis.hasTitle) {
|
|
157
|
+
content -= 20;
|
|
158
|
+
issues.push('No title');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Clarity scoring
|
|
162
|
+
if (analysis.wordCount > 50) clarity -= 20;
|
|
163
|
+
if (analysis.elementCount > 5) clarity -= (analysis.elementCount - 5) * 5;
|
|
164
|
+
|
|
165
|
+
// Effectiveness
|
|
166
|
+
if (!analysis.hasTitle) effectiveness -= 25;
|
|
167
|
+
if (titleContrastIssues.length > 0) effectiveness -= 30;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
layout: Math.max(0, Math.round(layout)),
|
|
171
|
+
contrast: Math.max(0, Math.round(contrast)),
|
|
172
|
+
graphics: Math.max(0, Math.round(graphics)),
|
|
173
|
+
content: Math.max(0, Math.round(content)),
|
|
174
|
+
clarity: Math.max(0, Math.round(clarity)),
|
|
175
|
+
effectiveness: Math.max(0, Math.round(effectiveness)),
|
|
176
|
+
consistency: 100,
|
|
177
|
+
issues
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function runFullAnalysis(htmlPath) {
|
|
182
|
+
const browser = await chromium.launch();
|
|
183
|
+
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
|
184
|
+
|
|
185
|
+
await page.goto(`file://${path.resolve(htmlPath)}`, { waitUntil: 'networkidle' });
|
|
186
|
+
await page.waitForTimeout(3000);
|
|
187
|
+
|
|
188
|
+
const totalSlides = await page.evaluate(() => {
|
|
189
|
+
if (typeof Reveal !== 'undefined') return Reveal.getTotalSlides();
|
|
190
|
+
return document.querySelectorAll('.slides > section').length || 10;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const slideResults = [];
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < totalSlides; i++) {
|
|
196
|
+
const analysis = await analyzeSlide(page);
|
|
197
|
+
const scores = calculateScores(analysis);
|
|
198
|
+
const overall = Math.round(Object.values(scores).filter(v => typeof v === 'number').reduce((a, b) => a + b, 0) / 7);
|
|
199
|
+
|
|
200
|
+
slideResults.push({
|
|
201
|
+
slide: i + 1,
|
|
202
|
+
scores,
|
|
203
|
+
overall,
|
|
204
|
+
issues: scores.issues,
|
|
205
|
+
analysis
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await page.keyboard.press('ArrowRight');
|
|
209
|
+
await page.waitForTimeout(1000);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await browser.close();
|
|
213
|
+
|
|
214
|
+
// Calculate dimension averages
|
|
215
|
+
const dimensions = ['layout', 'contrast', 'graphics', 'content', 'clarity', 'effectiveness', 'consistency'];
|
|
216
|
+
const averages = {};
|
|
217
|
+
dimensions.forEach(dim => {
|
|
218
|
+
averages[dim] = Math.round(slideResults.reduce((sum, r) => sum + r.scores[dim], 0) / totalSlides);
|
|
219
|
+
});
|
|
220
|
+
averages.total = Math.round(Object.values(averages).reduce((a, b) => a + b, 0) / dimensions.length);
|
|
221
|
+
|
|
222
|
+
return { slideResults, averages, totalSlides };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function printScores(averages, slideResults) {
|
|
226
|
+
console.log('\\n ═══════════════════════════════════════════════════');
|
|
227
|
+
console.log(' DIMENSION SCORES');
|
|
228
|
+
console.log(' ═══════════════════════════════════════════════════');
|
|
229
|
+
console.log(` Layout: ${averages.layout.toString().padStart(3)}/100 ${averages.layout >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
230
|
+
console.log(` Contrast: ${averages.contrast.toString().padStart(3)}/100 ${averages.contrast >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
231
|
+
console.log(` Graphics: ${averages.graphics.toString().padStart(3)}/100 ${averages.graphics >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
232
|
+
console.log(` Content: ${averages.content.toString().padStart(3)}/100 ${averages.content >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
233
|
+
console.log(` Clarity: ${averages.clarity.toString().padStart(3)}/100 ${averages.clarity >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
234
|
+
console.log(` Effectiveness: ${averages.effectiveness.toString().padStart(3)}/100 ${averages.effectiveness >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
235
|
+
console.log(` Consistency: ${averages.consistency.toString().padStart(3)}/100 ${averages.consistency >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
236
|
+
console.log(' ───────────────────────────────────────────────────');
|
|
237
|
+
console.log(` TOTAL: ${averages.total.toString().padStart(3)}/100 ${averages.total >= TARGET_SCORE ? '✅' : '❌'}`);
|
|
238
|
+
console.log(' ═══════════════════════════════════════════════════\\n');
|
|
239
|
+
|
|
240
|
+
// Show failing slides
|
|
241
|
+
const failingSlides = slideResults.filter(r => r.overall < TARGET_SCORE);
|
|
242
|
+
if (failingSlides.length > 0) {
|
|
243
|
+
console.log(' SLIDES BELOW TARGET:');
|
|
244
|
+
failingSlides.forEach(r => {
|
|
245
|
+
console.log(` Slide ${r.slide}: ${r.overall}/100 - ${r.issues.join(', ')}`);
|
|
246
|
+
});
|
|
247
|
+
console.log('');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function identifyFixes(results) {
|
|
252
|
+
const fixes = [];
|
|
253
|
+
|
|
254
|
+
results.slideResults.forEach(r => {
|
|
255
|
+
if (r.scores.layout < TARGET_SCORE) {
|
|
256
|
+
const smallText = r.analysis.textIssues.filter(i => i.type === 'smallText');
|
|
257
|
+
if (smallText.length > 0) {
|
|
258
|
+
fixes.push({
|
|
259
|
+
slide: r.slide,
|
|
260
|
+
type: 'css',
|
|
261
|
+
target: 'small-fonts',
|
|
262
|
+
description: `Increase ${smallText.length} small font sizes`
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (r.analysis.wordCount > 40) {
|
|
266
|
+
fixes.push({
|
|
267
|
+
slide: r.slide,
|
|
268
|
+
type: 'content',
|
|
269
|
+
target: 'word-count',
|
|
270
|
+
description: `Reduce words from ${r.analysis.wordCount} to 40`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (r.scores.contrast < TARGET_SCORE) {
|
|
276
|
+
const contrastIssues = r.analysis.textIssues.filter(i => i.type.includes('Contrast'));
|
|
277
|
+
if (contrastIssues.length > 0) {
|
|
278
|
+
fixes.push({
|
|
279
|
+
slide: r.slide,
|
|
280
|
+
type: 'css',
|
|
281
|
+
target: 'contrast',
|
|
282
|
+
description: `Fix ${contrastIssues.length} contrast issues`
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (r.scores.graphics < TARGET_SCORE && r.analysis.imageCount === 0) {
|
|
288
|
+
fixes.push({
|
|
289
|
+
slide: r.slide,
|
|
290
|
+
type: 'content',
|
|
291
|
+
target: 'add-image',
|
|
292
|
+
description: 'Add image to text-heavy slide'
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return fixes;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function applyFixes(htmlPath, fixes) {
|
|
301
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
302
|
+
let modified = false;
|
|
303
|
+
|
|
304
|
+
// Apply CSS fixes for small fonts and contrast
|
|
305
|
+
const cssFixesToApply = fixes.filter(f => f.type === 'css');
|
|
306
|
+
|
|
307
|
+
if (cssFixesToApply.some(f => f.target === 'small-fonts')) {
|
|
308
|
+
// Increase all small font sizes
|
|
309
|
+
const smallFontPatterns = [
|
|
310
|
+
{ pattern: /font-size:\s*0\.7[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
|
|
311
|
+
{ pattern: /font-size:\s*0\.8[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
|
|
312
|
+
{ pattern: /font-size:\s*0\.9[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
|
|
313
|
+
{ pattern: /font-size:\s*1rem([^0-9])/g, replacement: 'font-size: 1.125rem$1' },
|
|
314
|
+
{ pattern: /font-size:\s*1\.0rem/g, replacement: 'font-size: 1.125rem' },
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
smallFontPatterns.forEach(({ pattern, replacement }) => {
|
|
318
|
+
if (pattern.test(html)) {
|
|
319
|
+
html = html.replace(pattern, replacement);
|
|
320
|
+
modified = true;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (cssFixesToApply.some(f => f.target === 'contrast')) {
|
|
326
|
+
// Enhance text shadows and colors for contrast
|
|
327
|
+
if (!html.includes('text-shadow: 0 4px 20px')) {
|
|
328
|
+
html = html.replace(
|
|
329
|
+
/\.large-text\s*\{([^}]*)\}/,
|
|
330
|
+
'.large-text { $1 text-shadow: 0 4px 20px rgba(0,0,0,0.9) !important; }'
|
|
331
|
+
);
|
|
332
|
+
modified = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Ensure body text is light colored
|
|
336
|
+
html = html.replace(
|
|
337
|
+
/color:\s*var\(--gray\)/g,
|
|
338
|
+
'color: #e0e0e0'
|
|
339
|
+
);
|
|
340
|
+
html = html.replace(
|
|
341
|
+
/color:\s*var\(--muted\)/g,
|
|
342
|
+
'color: #cccccc'
|
|
343
|
+
);
|
|
344
|
+
modified = true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (modified) {
|
|
348
|
+
fs.writeFileSync(htmlPath, html);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function main(htmlPath) {
|
|
355
|
+
console.log('\\n══════════════════════════════════════════════════════════════');
|
|
356
|
+
console.log(' AUTOMATED PRESENTATION QUALITY ASSURANCE');
|
|
357
|
+
console.log(' Target: ALL dimensions 95+ | Maximum iterations: ' + MAX_ITERATIONS);
|
|
358
|
+
console.log('══════════════════════════════════════════════════════════════\\n');
|
|
359
|
+
|
|
360
|
+
let iteration = 0;
|
|
361
|
+
let allPassing = false;
|
|
362
|
+
|
|
363
|
+
while (iteration < MAX_ITERATIONS && !allPassing) {
|
|
364
|
+
iteration++;
|
|
365
|
+
console.log(`\\n ─── ITERATION ${iteration} ───────────────────────────────────\\n`);
|
|
366
|
+
|
|
367
|
+
// Run analysis
|
|
368
|
+
console.log(' Analyzing presentation...');
|
|
369
|
+
const results = await runFullAnalysis(htmlPath);
|
|
370
|
+
|
|
371
|
+
// Print current scores
|
|
372
|
+
printScores(results.averages, results.slideResults);
|
|
373
|
+
|
|
374
|
+
// Check if all dimensions pass
|
|
375
|
+
const dimensions = ['layout', 'contrast', 'graphics', 'content', 'clarity', 'effectiveness', 'consistency'];
|
|
376
|
+
const failingDimensions = dimensions.filter(d => results.averages[d] < TARGET_SCORE);
|
|
377
|
+
|
|
378
|
+
if (failingDimensions.length === 0) {
|
|
379
|
+
allPassing = true;
|
|
380
|
+
console.log(' ✅ ALL DIMENSIONS AT 95+ - PRESENTATION READY!\\n');
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log(` Failing dimensions: ${failingDimensions.join(', ')}\\n`);
|
|
385
|
+
|
|
386
|
+
// Identify and apply fixes
|
|
387
|
+
const fixes = identifyFixes(results);
|
|
388
|
+
if (fixes.length === 0) {
|
|
389
|
+
console.log(' ⚠️ No automatic fixes available. Manual intervention required.\\n');
|
|
390
|
+
console.log(' REQUIRED MANUAL FIXES:');
|
|
391
|
+
failingDimensions.forEach(dim => {
|
|
392
|
+
console.log(` - ${dim}: Currently ${results.averages[dim]}/100, needs ${TARGET_SCORE}+`);
|
|
393
|
+
});
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
console.log(` Applying ${fixes.length} fixes...`);
|
|
398
|
+
fixes.forEach(f => console.log(` - Slide ${f.slide}: ${f.description}`));
|
|
399
|
+
|
|
400
|
+
const applied = await applyFixes(htmlPath, fixes);
|
|
401
|
+
if (!applied) {
|
|
402
|
+
console.log(' ⚠️ Could not apply fixes automatically.\\n');
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log(' Fixes applied. Re-analyzing...\\n');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!allPassing && iteration >= MAX_ITERATIONS) {
|
|
410
|
+
console.log(` ❌ MAX ITERATIONS (${MAX_ITERATIONS}) REACHED - Manual review required\\n`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Final report
|
|
414
|
+
const finalResults = await runFullAnalysis(htmlPath);
|
|
415
|
+
console.log('\\n ═══════════════════════════════════════════════════');
|
|
416
|
+
console.log(' FINAL SCORES');
|
|
417
|
+
console.log(' ═══════════════════════════════════════════════════');
|
|
418
|
+
printScores(finalResults.averages, finalResults.slideResults);
|
|
419
|
+
|
|
420
|
+
// Save report
|
|
421
|
+
const reportPath = path.join(path.dirname(htmlPath), 'qa-final-report.json');
|
|
422
|
+
fs.writeFileSync(reportPath, JSON.stringify({
|
|
423
|
+
file: htmlPath,
|
|
424
|
+
timestamp: new Date().toISOString(),
|
|
425
|
+
iterations: iteration,
|
|
426
|
+
passed: allPassing,
|
|
427
|
+
finalScores: finalResults.averages,
|
|
428
|
+
slideDetails: finalResults.slideResults
|
|
429
|
+
}, null, 2));
|
|
430
|
+
|
|
431
|
+
console.log(` Report saved: ${reportPath}\\n`);
|
|
432
|
+
|
|
433
|
+
process.exit(allPassing ? 0 : 1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Run
|
|
437
|
+
const htmlFile = process.argv[2];
|
|
438
|
+
if (!htmlFile) {
|
|
439
|
+
console.error('Usage: node auto-fix-presentation.cjs <html-file>');
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
main(htmlFile).catch(err => {
|
|
444
|
+
console.error('Error:', err.message);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -20,10 +20,10 @@ const args = process.argv.slice(2);
|
|
|
20
20
|
|
|
21
21
|
// Help text
|
|
22
22
|
const helpText = `
|
|
23
|
-
Claude Presentation Master
|
|
23
|
+
Claude Presentation Master v7.2.0
|
|
24
24
|
Generate world-class presentations using expert methodologies
|
|
25
25
|
|
|
26
|
-
100% FREE • Zero API Keys • Runs Locally
|
|
26
|
+
100% FREE • Zero API Keys • Runs Locally • KB-Driven Quality
|
|
27
27
|
|
|
28
28
|
USAGE:
|
|
29
29
|
npx claude-presentation-master <command> [options]
|
|
@@ -35,20 +35,23 @@ COMMANDS:
|
|
|
35
35
|
info Show package information
|
|
36
36
|
|
|
37
37
|
OPTIONS:
|
|
38
|
-
-o, --output <dir>
|
|
39
|
-
-m, --mode <mode>
|
|
40
|
-
-f, --format <fmt>
|
|
41
|
-
--type <type>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-t, --theme <name>
|
|
46
|
-
--title <title>
|
|
47
|
-
--author <name>
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
-
|
|
51
|
-
-
|
|
38
|
+
-o, --output <dir> Output directory (default: ./output)
|
|
39
|
+
-m, --mode <mode> Presentation mode: keynote or business (default: keynote)
|
|
40
|
+
-f, --format <fmt> Output formats: html, pptx, or both (default: html)
|
|
41
|
+
--type <type> Presentation type (auto-detected if omitted):
|
|
42
|
+
ted_keynote, sales_pitch, consulting_deck,
|
|
43
|
+
investment_banking, investor_pitch,
|
|
44
|
+
technical_presentation, all_hands
|
|
45
|
+
-t, --theme <name> Theme: default, consulting-classic, modern-tech, minimal
|
|
46
|
+
--title <title> Presentation title (default: filename)
|
|
47
|
+
--author <name> Author name
|
|
48
|
+
--min-score <num> Minimum QA score 0-100 (default: 95)
|
|
49
|
+
--threshold <num> Alias for --min-score
|
|
50
|
+
--max-iterations <num> Max auto-fix iterations (default: 5)
|
|
51
|
+
--no-iterative Disable iterative QA (single pass only)
|
|
52
|
+
--skip-qa Skip QA validation (NOT recommended)
|
|
53
|
+
-h, --help Show this help message
|
|
54
|
+
-v, --version Show version number
|
|
52
55
|
|
|
53
56
|
EXAMPLES:
|
|
54
57
|
# Quick one-off: Generate keynote from markdown (npx - no install needed)
|
|
@@ -85,7 +88,7 @@ For more information: https://github.com/Stuinfla/claude-presentation-master
|
|
|
85
88
|
`;
|
|
86
89
|
|
|
87
90
|
// Version
|
|
88
|
-
const version = '
|
|
91
|
+
const version = '7.2.0';
|
|
89
92
|
|
|
90
93
|
// Parse arguments
|
|
91
94
|
function parseArgs(args) {
|
|
@@ -100,6 +103,8 @@ function parseArgs(args) {
|
|
|
100
103
|
title: null,
|
|
101
104
|
author: null,
|
|
102
105
|
threshold: 95,
|
|
106
|
+
maxIterations: 5,
|
|
107
|
+
useIterativeQA: true,
|
|
103
108
|
skipQA: false,
|
|
104
109
|
help: false,
|
|
105
110
|
version: false
|
|
@@ -153,9 +158,18 @@ function parseArgs(args) {
|
|
|
153
158
|
break;
|
|
154
159
|
|
|
155
160
|
case '--threshold':
|
|
161
|
+
case '--min-score':
|
|
156
162
|
options.threshold = parseInt(args[++i], 10);
|
|
157
163
|
break;
|
|
158
164
|
|
|
165
|
+
case '--max-iterations':
|
|
166
|
+
options.maxIterations = parseInt(args[++i], 10);
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case '--no-iterative':
|
|
170
|
+
options.useIterativeQA = false;
|
|
171
|
+
break;
|
|
172
|
+
|
|
159
173
|
case '--skip-qa':
|
|
160
174
|
options.skipQA = true;
|
|
161
175
|
break;
|
|
@@ -221,15 +235,30 @@ Author: Stuart Kerr <stuart@isovision.ai>
|
|
|
221
235
|
License: MIT
|
|
222
236
|
Cost: 100% FREE - Zero API Keys Required
|
|
223
237
|
|
|
238
|
+
NEW IN v7.0.0:
|
|
239
|
+
★ 7-Dimension Quality Scoring (KB-driven)
|
|
240
|
+
★ Iterative Auto-Fix Loop (generates → scores → fixes → repeats)
|
|
241
|
+
★ Knowledge Base Integration (73K+ tokens of expert rules)
|
|
242
|
+
★ Per-dimension breakdown: Layout, Contrast, Graphics, Content, Clarity, Effectiveness, Consistency
|
|
243
|
+
|
|
224
244
|
Features:
|
|
225
245
|
✓ 7 Presentation Types with Expert Methodologies
|
|
226
246
|
✓ Two modes: Keynote (6-25 words) & Business (40-80 words)
|
|
227
247
|
✓ HTML (Reveal.js) + PowerPoint output
|
|
228
248
|
✓ Investment Banking Charts (Football Field, Waterfall, Comps)
|
|
229
249
|
✓ Professional Typography (Google Fonts)
|
|
230
|
-
✓
|
|
231
|
-
✓ Auto-Remediation (fixes issues until
|
|
232
|
-
✓
|
|
250
|
+
✓ KB-Driven Slide Generation (all decisions from knowledge base)
|
|
251
|
+
✓ Auto-Remediation (fixes issues until threshold met)
|
|
252
|
+
✓ 7-Dimension Scoring with Iteration Loop
|
|
253
|
+
|
|
254
|
+
7-Dimension Scoring:
|
|
255
|
+
• Layout (15%) - Whitespace, balance, structure
|
|
256
|
+
• Contrast (15%) - WCAG compliance, readability
|
|
257
|
+
• Graphics (10%) - Images, placement, relevance
|
|
258
|
+
• Content (20%) - Word limits, bullets, structure
|
|
259
|
+
• Clarity (15%) - Message focus, density
|
|
260
|
+
• Effectiveness(15%) - Expert methodology compliance
|
|
261
|
+
• Consistency (10%) - Style uniformity, coherence
|
|
233
262
|
|
|
234
263
|
Presentation Types:
|
|
235
264
|
• ted_keynote Nancy Duarte (Sparkline)
|
|
@@ -241,14 +270,16 @@ Presentation Types:
|
|
|
241
270
|
• all_hands Internal Comms Best Practices
|
|
242
271
|
|
|
243
272
|
Expert Knowledge Base:
|
|
244
|
-
•
|
|
273
|
+
• 73K+ tokens of comprehensive rules
|
|
245
274
|
• 40+ presentation experts
|
|
246
275
|
• SCQA, Sparkline, STAR Moment frameworks
|
|
276
|
+
• Technical presentation templates (ADR, Post-mortem, SDR)
|
|
247
277
|
• Automated type detection and framework selection
|
|
248
278
|
|
|
249
279
|
Usage:
|
|
250
280
|
npx claude-presentation-master generate notes.md -m keynote
|
|
251
281
|
cpm generate strategy.md --type consulting_deck -f pptx
|
|
282
|
+
cpm generate content.md --min-score 98 --max-iterations 10
|
|
252
283
|
`);
|
|
253
284
|
process.exit(0);
|
|
254
285
|
}
|
|
@@ -369,6 +400,8 @@ async function runGenerate(inputPath, options, generate) {
|
|
|
369
400
|
title,
|
|
370
401
|
author: options.author,
|
|
371
402
|
qaThreshold: options.threshold,
|
|
403
|
+
maxIterations: options.maxIterations,
|
|
404
|
+
useIterativeQA: options.useIterativeQA,
|
|
372
405
|
skipQA: options.skipQA,
|
|
373
406
|
images: localImages, // Local images for backgrounds
|
|
374
407
|
imageBasePath: inputDir // Base path for resolving relative image paths
|
|
@@ -376,13 +409,15 @@ async function runGenerate(inputPath, options, generate) {
|
|
|
376
409
|
|
|
377
410
|
console.log(`
|
|
378
411
|
📋 Configuration:
|
|
379
|
-
Mode:
|
|
380
|
-
Type:
|
|
381
|
-
Format:
|
|
382
|
-
Theme:
|
|
383
|
-
Title:
|
|
384
|
-
|
|
385
|
-
|
|
412
|
+
Mode: ${options.mode}
|
|
413
|
+
Type: ${options.type || '(auto-detect)'}
|
|
414
|
+
Format: ${options.format.join(', ')}
|
|
415
|
+
Theme: ${options.theme}
|
|
416
|
+
Title: ${title}
|
|
417
|
+
Min Score: ${options.threshold}/100
|
|
418
|
+
Max Iter: ${options.maxIterations}
|
|
419
|
+
Iterative QA: ${options.useIterativeQA ? 'Yes (7-Dimension)' : 'No (Single Pass)'}
|
|
420
|
+
Skip QA: ${options.skipQA ? 'Yes (NOT RECOMMENDED)' : 'No'}
|
|
386
421
|
`);
|
|
387
422
|
|
|
388
423
|
try {
|
|
@@ -412,21 +447,49 @@ async function runGenerate(inputPath, options, generate) {
|
|
|
412
447
|
|
|
413
448
|
// Show results
|
|
414
449
|
const avgWords = result.metadata.avgWordsPerSlide?.toFixed(1) ?? 'N/A';
|
|
415
|
-
const
|
|
450
|
+
const totalWords = result.metadata.wordCount || result.metadata.totalWords || 0;
|
|
451
|
+
const estimatedDuration = Math.ceil(totalWords / 150); // ~150 words per minute
|
|
416
452
|
|
|
417
453
|
console.log(`
|
|
418
454
|
╔════════════════════════════════════════════════════════╗
|
|
419
455
|
║ QA RESULTS ║
|
|
420
456
|
╚════════════════════════════════════════════════════════╝
|
|
421
457
|
|
|
422
|
-
📊 Score: ${result.score}/100 ${result.score >= options.threshold ? '✅ PASSED' : '❌ FAILED'}
|
|
458
|
+
📊 Overall Score: ${result.score}/100 ${result.score >= options.threshold ? '✅ PASSED' : '❌ FAILED'}
|
|
459
|
+
`);
|
|
423
460
|
|
|
461
|
+
// Show 7-dimension breakdown if available
|
|
462
|
+
if (result.qaResults.dimensions) {
|
|
463
|
+
const dims = result.qaResults.dimensions;
|
|
464
|
+
console.log('7-Dimension Breakdown:');
|
|
465
|
+
console.log('─────────────────────────────────────────────────────────');
|
|
466
|
+
const formatScore = (name, score) => {
|
|
467
|
+
const bar = '█'.repeat(Math.floor(score / 10)) + '░'.repeat(10 - Math.floor(score / 10));
|
|
468
|
+
const status = score >= 95 ? '✅' : score >= 80 ? '⚠️' : '❌';
|
|
469
|
+
return `${status} ${name.padEnd(14)} ${bar} ${score.toString().padStart(3)}/100`;
|
|
470
|
+
};
|
|
471
|
+
console.log(` ${formatScore('Layout', dims.layout)}`);
|
|
472
|
+
console.log(` ${formatScore('Contrast', dims.contrast)}`);
|
|
473
|
+
console.log(` ${formatScore('Graphics', dims.graphics)}`);
|
|
474
|
+
console.log(` ${formatScore('Content', dims.content)}`);
|
|
475
|
+
console.log(` ${formatScore('Clarity', dims.clarity)}`);
|
|
476
|
+
console.log(` ${formatScore('Effectiveness', dims.effectiveness)}`);
|
|
477
|
+
console.log(` ${formatScore('Consistency', dims.consistency)}`);
|
|
478
|
+
console.log('');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Show iteration info if available
|
|
482
|
+
if (result.metadata.qaIterations) {
|
|
483
|
+
console.log(`🔄 Iterations: ${result.metadata.qaIterations}/${result.metadata.qaMaxIterations}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
console.log(`
|
|
424
487
|
📈 Metadata:
|
|
425
488
|
Slides: ${result.metadata.slideCount}
|
|
426
|
-
Words: ${
|
|
489
|
+
Words: ${totalWords}
|
|
427
490
|
Avg/Slide: ${avgWords}
|
|
428
491
|
Duration: ~${estimatedDuration} minutes
|
|
429
|
-
Type: ${result.metadata.presentationType}
|
|
492
|
+
Type: ${result.metadata.presentationType || 'auto-detected'}
|
|
430
493
|
`);
|
|
431
494
|
|
|
432
495
|
console.log('\n✨ Generation complete!');
|