@liustack/pptfast 0.3.0 → 0.6.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 +34 -28
- package/README.zh-CN.md +33 -27
- package/dist/chunk-2ZOMS6G3.js +564 -0
- package/dist/chunk-2ZOMS6G3.js.map +1 -0
- package/dist/{chunk-ZXWCOWJI.js → chunk-7V73LHUQ.js} +3660 -1745
- package/dist/chunk-7V73LHUQ.js.map +1 -0
- package/dist/chunk-HFRNKYZ6.js +54 -0
- package/dist/chunk-HFRNKYZ6.js.map +1 -0
- package/dist/chunk-L524YK63.js +19 -0
- package/dist/chunk-L524YK63.js.map +1 -0
- package/dist/cli.js +340 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +992 -210
- package/dist/index.js +42 -34
- package/dist/node.d.ts +3 -2
- package/dist/node.js +2 -2
- package/dist/pixel-audit-ZRFTV4V7.js +218 -0
- package/dist/pixel-audit-ZRFTV4V7.js.map +1 -0
- package/dist/registry-V079Jkry.d.ts +40 -0
- package/package.json +10 -3
- package/dist/chunk-G76EVNVT.js +0 -14
- package/dist/chunk-G76EVNVT.js.map +0 -1
- package/dist/chunk-SROEW6BH.js +0 -34
- package/dist/chunk-SROEW6BH.js.map +0 -1
- package/dist/chunk-ZXWCOWJI.js.map +0 -1
- package/dist/registry-CYKxZ0-U.d.ts +0 -11
package/dist/cli.js
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PptxIRV3Schema,
|
|
4
|
+
VERSION,
|
|
5
|
+
assembleDeck,
|
|
6
|
+
disassembleDeck,
|
|
7
|
+
formatInvalidSpecError,
|
|
8
|
+
migrateDeckPlanToSpec,
|
|
9
|
+
migrateIrV3ToV4,
|
|
10
|
+
resolveSpecThemeId,
|
|
11
|
+
specJsonSchema,
|
|
12
|
+
validateSpec
|
|
13
|
+
} from "./chunk-2ZOMS6G3.js";
|
|
14
|
+
import {
|
|
15
|
+
installNodePlatform
|
|
16
|
+
} from "./chunk-HFRNKYZ6.js";
|
|
2
17
|
import {
|
|
3
18
|
AUDIENCE_VALUES,
|
|
4
|
-
|
|
5
|
-
|
|
19
|
+
NARRATIVE_PRESETS,
|
|
20
|
+
PACING_BUDGETS,
|
|
6
21
|
PptfastError,
|
|
7
|
-
|
|
22
|
+
STRATEGY_DEFINITIONS,
|
|
8
23
|
StyleOverrideSchema,
|
|
9
|
-
VERSION,
|
|
10
|
-
assembleDeck,
|
|
11
24
|
auditDeck,
|
|
12
|
-
disassembleDeck,
|
|
13
|
-
formatInvalidPlanError,
|
|
14
25
|
formatIssues,
|
|
26
|
+
formatWarnings,
|
|
15
27
|
generatePptx,
|
|
16
28
|
getInstalledThemeIds,
|
|
17
29
|
irJsonSchema,
|
|
18
30
|
listThemes,
|
|
19
|
-
planJsonSchema,
|
|
20
31
|
renderSlideSvg,
|
|
21
|
-
|
|
22
|
-
resolveScenario,
|
|
32
|
+
resolveNarrative,
|
|
23
33
|
styleJsonSchema,
|
|
24
|
-
validateIr
|
|
25
|
-
|
|
26
|
-
} from "./chunk-ZXWCOWJI.js";
|
|
27
|
-
import {
|
|
28
|
-
installNodePlatform
|
|
29
|
-
} from "./chunk-SROEW6BH.js";
|
|
34
|
+
validateIr
|
|
35
|
+
} from "./chunk-7V73LHUQ.js";
|
|
30
36
|
import {
|
|
31
37
|
getPlatform
|
|
32
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-L524YK63.js";
|
|
33
39
|
|
|
34
40
|
// src/cli.ts
|
|
35
41
|
import { Command } from "commander";
|
|
@@ -163,6 +169,7 @@ async function resolveLocalAssets(ir, baseDir) {
|
|
|
163
169
|
|
|
164
170
|
// src/cli/deck-dir.ts
|
|
165
171
|
var PLAN_FILENAME = "deck.plan.json";
|
|
172
|
+
var SPEC_FILENAME = "deck.spec.json";
|
|
166
173
|
var PAGES_DIRNAME = "pages";
|
|
167
174
|
var ASSETS_DIRNAME = "assets";
|
|
168
175
|
function assertSafeFileSegment(id, context) {
|
|
@@ -202,31 +209,43 @@ async function resolveDeckTarget(arg, config, cwd = process.cwd()) {
|
|
|
202
209
|
}
|
|
203
210
|
function expectedLayoutHint() {
|
|
204
211
|
const rows = [
|
|
205
|
-
[
|
|
212
|
+
[SPEC_FILENAME, "the locked spec (see `pptfast spec validate`)"],
|
|
206
213
|
[`${PAGES_DIRNAME}/<page-id>.json`, "one file per filled page (missing pages become placeholders)"],
|
|
207
214
|
[`${ASSETS_DIRNAME}/`, "optional local images"]
|
|
208
215
|
];
|
|
209
216
|
const width = Math.max(...rows.map(([name]) => name.length)) + 2;
|
|
210
217
|
return rows.map(([name, desc]) => ` ${name.padEnd(width)}${desc}`).join("\n");
|
|
211
218
|
}
|
|
212
|
-
async function
|
|
219
|
+
async function readSpecFile(dir) {
|
|
220
|
+
const specPath = join3(dir, SPEC_FILENAME);
|
|
213
221
|
const planPath = join3(dir, PLAN_FILENAME);
|
|
222
|
+
const [specExists, planExists] = await Promise.all([pathExists(specPath), pathExists(planPath)]);
|
|
223
|
+
if (specExists && planExists) {
|
|
224
|
+
throw new PptfastError(
|
|
225
|
+
`both ${SPEC_FILENAME} and ${PLAN_FILENAME} exist in ${dir} \u2014 ambiguous, refusing to guess which one wins. Delete ${PLAN_FILENAME} once you have confirmed ${SPEC_FILENAME} is correct (\`pptfast migrate\` never deletes the source file it read)`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (!specExists && planExists) {
|
|
229
|
+
throw new PptfastError(
|
|
230
|
+
`${dir} has ${PLAN_FILENAME} but no ${SPEC_FILENAME} \u2014 deck project directories now use ${SPEC_FILENAME}. Run \`pptfast migrate ${dir} -o ${dir}\` to convert it`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
214
233
|
let text;
|
|
215
234
|
try {
|
|
216
|
-
text = await readFile3(
|
|
235
|
+
text = await readFile3(specPath, "utf8");
|
|
217
236
|
} catch (e) {
|
|
218
237
|
if (e.code === "ENOENT") {
|
|
219
238
|
throw new PptfastError(
|
|
220
|
-
`no ${
|
|
239
|
+
`no ${SPEC_FILENAME} in ${dir} \u2014 expected a deck project directory:
|
|
221
240
|
${expectedLayoutHint()}`
|
|
222
241
|
);
|
|
223
242
|
}
|
|
224
|
-
throw new PptfastError(`cannot read
|
|
243
|
+
throw new PptfastError(`cannot read spec file: ${specPath}`);
|
|
225
244
|
}
|
|
226
245
|
try {
|
|
227
246
|
return JSON.parse(text);
|
|
228
247
|
} catch (e) {
|
|
229
|
-
throw new PptfastError(`
|
|
248
|
+
throw new PptfastError(`spec file ${specPath} is not valid JSON: ${e.message}`);
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
async function readPages(dir) {
|
|
@@ -273,9 +292,9 @@ async function scanAssets(dir) {
|
|
|
273
292
|
}
|
|
274
293
|
async function readDeckDir(dir) {
|
|
275
294
|
const deckDir = resolve4(dir);
|
|
276
|
-
const
|
|
295
|
+
const spec2 = await readSpecFile(deckDir);
|
|
277
296
|
const pages = await readPages(deckDir);
|
|
278
|
-
const { ir, generatedSeed, materializedLayoutCount } = assembleDeck(
|
|
297
|
+
const { ir, generatedSeed, materializedLayoutCount } = assembleDeck(spec2, pages);
|
|
279
298
|
const images = await scanAssets(deckDir);
|
|
280
299
|
const merged = { ...ir, assets: { images: { ...ir.assets.images, ...images } } };
|
|
281
300
|
return { ir: merged, generatedSeed, materializedLayoutCount, deckDir };
|
|
@@ -324,10 +343,18 @@ async function writeOneAsset(id, src, assetsDir, sourceBaseDir) {
|
|
|
324
343
|
function escapeHtml(s) {
|
|
325
344
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
326
345
|
}
|
|
327
|
-
function
|
|
346
|
+
function embedJson(value) {
|
|
347
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
348
|
+
}
|
|
349
|
+
function findingBadge(count, className) {
|
|
350
|
+
if (count === 0) return "";
|
|
351
|
+
return `<div class="${className}" aria-hidden="true">${count}</div>`;
|
|
352
|
+
}
|
|
353
|
+
function slideNode(slide, findingCount) {
|
|
328
354
|
const idAttr = slide.id !== void 0 ? ` data-id="${escapeHtml(slide.id)}"` : "";
|
|
329
355
|
const badge = slide.placeholder ? `<div class="pf-badge" aria-hidden="true">unfilled</div>` : "";
|
|
330
|
-
|
|
356
|
+
const fBadge = findingBadge(findingCount, "pf-finding-badge");
|
|
357
|
+
return `<div class="pf-slide" id="pf-slide-${slide.index}" data-index="${slide.index}"${idAttr}>${badge}${fBadge}${slide.svg}</div>`;
|
|
331
358
|
}
|
|
332
359
|
function thumbDescription(slide) {
|
|
333
360
|
const parts = [`slide ${slide.index + 1}`, slide.type];
|
|
@@ -343,10 +370,15 @@ function counterText(slide, total) {
|
|
|
343
370
|
const idPart = slide.id !== void 0 ? ` \xB7 ${slide.id}` : "";
|
|
344
371
|
return escapeHtml(`${slide.index + 1} / ${total}${idPart}`);
|
|
345
372
|
}
|
|
346
|
-
function thumbButton(slide, isActive, slotContent) {
|
|
373
|
+
function thumbButton(slide, isActive, slotContent, findingCount) {
|
|
347
374
|
const description = thumbDescription(slide);
|
|
348
375
|
const badge = slide.placeholder ? `<span class="pf-thumb-badge" aria-hidden="true">unfilled</span>` : "";
|
|
349
|
-
|
|
376
|
+
const fBadge = findingBadge(findingCount, "pf-thumb-finding-badge");
|
|
377
|
+
return `<button type="button" class="pf-thumb${isActive ? " pf-thumb-active" : ""}" id="pf-thumb-${slide.index}" data-index="${slide.index}" title="${description}" aria-label="${description}"><span class="pf-thumb-slot" id="pf-slot-${slide.index}">${slotContent}</span><span class="pf-thumb-label">${positionLabel(slide)}</span>${badge}${fBadge}</button>`;
|
|
378
|
+
}
|
|
379
|
+
function findingPanelEntry(f) {
|
|
380
|
+
const idPart = f.slideId !== void 0 ? ` \xB7 ${escapeHtml(f.slideId)}` : "";
|
|
381
|
+
return `<button type="button" class="pf-finding" data-page-index="${f.page - 1}"><span class="pf-finding-loc">page ${f.page}${idPart}</span><span class="pf-finding-code">[${escapeHtml(f.code)}]</span> <span class="pf-finding-msg">${escapeHtml(f.message)}</span></button>`;
|
|
350
382
|
}
|
|
351
383
|
var CSS = `
|
|
352
384
|
:root{color-scheme:light}
|
|
@@ -356,11 +388,31 @@ body{display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystem
|
|
|
356
388
|
header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;background:#fff;border-bottom:1px solid #ddd;font-size:14px;flex:0 0 auto}
|
|
357
389
|
#pf-title{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
358
390
|
#pf-counter{font-variant-numeric:tabular-nums;color:#555;white-space:nowrap}
|
|
359
|
-
#pf-
|
|
391
|
+
#pf-audit-note{color:#b45309;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
392
|
+
#pf-export-btn{font:inherit;font-size:13px;padding:6px 10px;border:1px solid #2563eb;background:#2563eb;color:#fff;border-radius:4px;cursor:pointer;white-space:nowrap;flex:0 0 auto}
|
|
393
|
+
#pf-export-btn:hover{background:#1d4ed8}
|
|
394
|
+
#pf-stage-wrap{flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;gap:16px;padding:16px}
|
|
360
395
|
#pf-stage{position:relative;background:#000;box-shadow:0 2px 16px rgba(0,0,0,.15);aspect-ratio:16/9;width:min(100%,calc((100vh - 190px) * 16 / 9));max-height:100%}
|
|
361
396
|
#pf-stage,.pf-thumb-slot{position:relative}
|
|
362
397
|
.pf-slide{position:absolute;inset:0}
|
|
363
398
|
.pf-slide svg{display:block;width:100%;height:100%}
|
|
399
|
+
#pf-side{flex:0 0 260px;align-self:stretch;overflow-y:auto;background:#fff;border:1px solid #ddd;border-radius:6px;padding:12px;font-size:13px}
|
|
400
|
+
#pf-side h2{margin:0 0 8px;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#666}
|
|
401
|
+
#pf-side section+section{margin-top:16px;padding-top:16px;border-top:1px solid #eee}
|
|
402
|
+
#pf-audit-checks{margin:0 0 12px;font-size:12px;color:#555}
|
|
403
|
+
.pf-finding{display:block;width:100%;text-align:left;background:#fff;border:1px solid #eee;border-radius:4px;padding:6px 8px;margin-bottom:6px;cursor:pointer;font:inherit}
|
|
404
|
+
.pf-finding:hover{border-color:#93c5fd}
|
|
405
|
+
.pf-finding-loc{display:block;font-size:11px;color:#888}
|
|
406
|
+
.pf-finding-code{display:inline-block;font-size:11px;font-weight:700;color:#b91c1c}
|
|
407
|
+
.pf-finding-msg{font-size:12px;color:#333}
|
|
408
|
+
#pf-annotate-current-label{font-size:11px;color:#888;margin-bottom:6px}
|
|
409
|
+
#pf-annotate-list{list-style:none;margin:0 0 8px;padding:0}
|
|
410
|
+
.pf-annotate-item{display:flex;justify-content:space-between;gap:6px;align-items:flex-start;padding:4px 0;border-bottom:1px solid #f0f0f0;font-size:12px}
|
|
411
|
+
.pf-annotate-remove{border:none;background:none;color:#999;cursor:pointer;font-size:14px;line-height:1;padding:0 2px}
|
|
412
|
+
.pf-annotate-remove:hover{color:#dc2626}
|
|
413
|
+
#pf-annotate-input{width:100%;box-sizing:border-box;font:inherit;font-size:12px;padding:6px;border:1px solid #ddd;border-radius:4px;resize:vertical}
|
|
414
|
+
#pf-annotate-add{font:inherit;font-size:12px;margin-top:6px;width:100%;padding:6px 10px;border:1px solid #2563eb;background:#2563eb;color:#fff;border-radius:4px;cursor:pointer}
|
|
415
|
+
#pf-annotate-add:hover{background:#1d4ed8}
|
|
364
416
|
#pf-filmstrip{display:flex;gap:8px;padding:10px 16px;overflow-x:auto;background:#fff;border-top:1px solid #ddd;flex:0 0 auto}
|
|
365
417
|
.pf-thumb{flex:0 0 auto;width:160px;padding:0;margin:0;border:2px solid transparent;background:#eee;cursor:pointer;border-radius:6px;overflow:hidden;position:relative;font:inherit;text-align:left}
|
|
366
418
|
.pf-thumb:hover{border-color:#93c5fd}
|
|
@@ -368,6 +420,7 @@ header{display:flex;align-items:center;justify-content:space-between;gap:12px;pa
|
|
|
368
420
|
.pf-thumb-slot{display:block;width:100%;aspect-ratio:16/9;background:#ddd}
|
|
369
421
|
.pf-thumb-label{display:block;font-size:11px;line-height:1.4;padding:3px 6px;color:#444;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
370
422
|
.pf-badge,.pf-thumb-badge{position:absolute;top:4px;right:4px;background:#d97706;color:#fff;font-size:10px;font-weight:700;letter-spacing:.03em;padding:2px 6px;border-radius:3px;text-transform:uppercase;z-index:2;pointer-events:none}
|
|
423
|
+
.pf-finding-badge,.pf-thumb-finding-badge{position:absolute;top:4px;left:4px;background:#dc2626;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;z-index:2;pointer-events:none}
|
|
371
424
|
`.trim();
|
|
372
425
|
var JS = `
|
|
373
426
|
(function () {
|
|
@@ -411,6 +464,7 @@ var JS = `
|
|
|
411
464
|
nextThumb.classList.add('pf-thumb-active')
|
|
412
465
|
current = i
|
|
413
466
|
updateCounter(i)
|
|
467
|
+
renderAnnotations()
|
|
414
468
|
}
|
|
415
469
|
|
|
416
470
|
thumbs.forEach(function (t) {
|
|
@@ -427,15 +481,129 @@ var JS = `
|
|
|
427
481
|
activate(parseInt(thumbs[pos - 1].getAttribute('data-index'), 10))
|
|
428
482
|
}
|
|
429
483
|
})
|
|
484
|
+
|
|
485
|
+
// ---- audit findings panel: click a finding, jump to its page (same
|
|
486
|
+
// activate() every thumbnail click already uses) ----
|
|
487
|
+
Array.prototype.slice.call(document.querySelectorAll('.pf-finding')).forEach(function (b) {
|
|
488
|
+
b.addEventListener('click', function () {
|
|
489
|
+
activate(parseInt(b.getAttribute('data-page-index'), 10))
|
|
490
|
+
})
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// ---- annotations: in-memory only, keyed by the slide's 0-based array
|
|
494
|
+
// index (current) \u2014 never by pageId, since an object key coerces every
|
|
495
|
+
// key to a string and would silently collide a numeric page-fallback
|
|
496
|
+
// pageId with a same-looking slide id (or lose the numeric/string type
|
|
497
|
+
// distinction the exported JSON needs); pageIdFor() below resolves the
|
|
498
|
+
// real pageId fresh from the DOM only at render/export time. ----
|
|
499
|
+
var annotations = {}
|
|
500
|
+
var annotateList = document.getElementById('pf-annotate-list')
|
|
501
|
+
var annotateInput = document.getElementById('pf-annotate-input')
|
|
502
|
+
var annotateLabel = document.getElementById('pf-annotate-current-label')
|
|
503
|
+
var annotateAdd = document.getElementById('pf-annotate-add')
|
|
504
|
+
var exportBtn = document.getElementById('pf-export-btn')
|
|
505
|
+
|
|
506
|
+
// Slide id when the active slide has one, else its 1-based page number \u2014
|
|
507
|
+
// mirrors AuditFinding.page's own "1-based page number when there is no
|
|
508
|
+
// slide id" convention (../svg/audit/deck-audit.ts) rather than this
|
|
509
|
+
// file's internal 0-based data-index, so a revision-request.json's
|
|
510
|
+
// pageId lines up with what pptfast audit/validate already print.
|
|
511
|
+
function pageIdFor(i) {
|
|
512
|
+
var el = slideEl(i)
|
|
513
|
+
var id = el ? el.getAttribute('data-id') : null
|
|
514
|
+
return id !== null ? id : thumbPos(i) + 1
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function renderAnnotations() {
|
|
518
|
+
var pid = pageIdFor(current)
|
|
519
|
+
annotateLabel.textContent = 'page ' + (thumbPos(current) + 1) + (typeof pid === 'string' ? ' \xB7 ' + pid : '')
|
|
520
|
+
var list = annotations[current] || []
|
|
521
|
+
annotateList.innerHTML = ''
|
|
522
|
+
list.forEach(function (text, idx) {
|
|
523
|
+
var li = document.createElement('li')
|
|
524
|
+
li.className = 'pf-annotate-item'
|
|
525
|
+
var span = document.createElement('span')
|
|
526
|
+
span.textContent = text
|
|
527
|
+
var rm = document.createElement('button')
|
|
528
|
+
rm.type = 'button'
|
|
529
|
+
rm.className = 'pf-annotate-remove'
|
|
530
|
+
rm.setAttribute('aria-label', 'remove annotation')
|
|
531
|
+
rm.textContent = '\\u00d7'
|
|
532
|
+
rm.addEventListener('click', function () {
|
|
533
|
+
list.splice(idx, 1)
|
|
534
|
+
renderAnnotations()
|
|
535
|
+
})
|
|
536
|
+
li.appendChild(span)
|
|
537
|
+
li.appendChild(rm)
|
|
538
|
+
annotateList.appendChild(li)
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
annotateAdd.addEventListener('click', function () {
|
|
543
|
+
var text = annotateInput.value.trim()
|
|
544
|
+
if (!text) return
|
|
545
|
+
if (!annotations[current]) annotations[current] = []
|
|
546
|
+
annotations[current].push(text)
|
|
547
|
+
annotateInput.value = ''
|
|
548
|
+
renderAnnotations()
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
// "Export revision requests": preview.html stays read-only end to end \u2014
|
|
552
|
+
// this never writes back into the deck itself, only produces a JSON file
|
|
553
|
+
// of requests for an agent/human to route through pages/*.json (see
|
|
554
|
+
// skills/pptfast/SKILL.md's phase-6 revision-request handling). Zero
|
|
555
|
+
// network/storage \u2014 an in-memory Blob + a synthetic <a download> click,
|
|
556
|
+
// the standard browser-only download pattern.
|
|
557
|
+
exportBtn.addEventListener('click', function () {
|
|
558
|
+
var requests = []
|
|
559
|
+
Object.keys(annotations).forEach(function (key) {
|
|
560
|
+
var i = parseInt(key, 10)
|
|
561
|
+
var pid = pageIdFor(i)
|
|
562
|
+
annotations[i].forEach(function (text) {
|
|
563
|
+
requests.push({ pageId: pid, annotation: text, createdAt: new Date().toISOString() })
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
var deckTitle = document.getElementById('pf-title').textContent
|
|
567
|
+
var payload = { version: '1', deck: deckTitle, requests: requests }
|
|
568
|
+
var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
|
|
569
|
+
var url = URL.createObjectURL(blob)
|
|
570
|
+
var a = document.createElement('a')
|
|
571
|
+
a.href = url
|
|
572
|
+
a.download = 'revision-request.json'
|
|
573
|
+
document.body.appendChild(a)
|
|
574
|
+
a.click()
|
|
575
|
+
document.body.removeChild(a)
|
|
576
|
+
URL.revokeObjectURL(url)
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
renderAnnotations()
|
|
430
580
|
})()
|
|
431
581
|
`.trim();
|
|
582
|
+
var ANNOTATE_PANEL = `<section id="pf-annotate-panel">
|
|
583
|
+
<h2>Annotations</h2>
|
|
584
|
+
<div id="pf-annotate-current-label"></div>
|
|
585
|
+
<ul id="pf-annotate-list"></ul>
|
|
586
|
+
<textarea id="pf-annotate-input" rows="3" placeholder="Add a note for this page\u2026"></textarea>
|
|
587
|
+
<button type="button" id="pf-annotate-add">Add annotation</button>
|
|
588
|
+
</section>`;
|
|
432
589
|
function buildPreviewHtml(input) {
|
|
433
|
-
const { title, slides } = input;
|
|
590
|
+
const { title, slides, findings = [], auditNote, checks } = input;
|
|
434
591
|
const total = slides.length;
|
|
435
592
|
const escapedTitle = escapeHtml(title);
|
|
436
|
-
const
|
|
437
|
-
const
|
|
593
|
+
const findingsByPage = /* @__PURE__ */ new Map();
|
|
594
|
+
for (const f of findings) {
|
|
595
|
+
const list = findingsByPage.get(f.page);
|
|
596
|
+
if (list) list.push(f);
|
|
597
|
+
else findingsByPage.set(f.page, [f]);
|
|
598
|
+
}
|
|
599
|
+
const countFor = (slide) => findingsByPage.get(slide.index + 1)?.length ?? 0;
|
|
600
|
+
const stageSlide = total > 0 ? slideNode(slides[0], countFor(slides[0])) : "";
|
|
601
|
+
const thumbs = slides.map((s, i) => thumbButton(s, i === 0, i === 0 ? "" : slideNode(s, countFor(s)), countFor(s))).join("");
|
|
438
602
|
const initialCounter = total > 0 ? counterText(slides[0], total) : "0 / 0";
|
|
603
|
+
const auditPanel = findings.length > 0 ? `<section id="pf-audit-panel"><h2>Audit findings (${findings.length})</h2><div id="pf-audit-list">${findings.map(findingPanelEntry).join("")}</div></section>` : "";
|
|
604
|
+
const findingsDataScript = findings.length > 0 ? `<script type="application/json" id="pf-audit-findings">${embedJson(findings)}</script>` : "";
|
|
605
|
+
const auditNoteHtml = auditNote !== void 0 ? `<span id="pf-audit-note">${escapeHtml(auditNote)}</span>` : "";
|
|
606
|
+
const checksLine = checks !== void 0 ? `<div id="pf-audit-checks">audit checks: svg ${checks.svg} \xB7 pixels ${checks.pixels}</div>` : "";
|
|
439
607
|
return `<!doctype html>
|
|
440
608
|
<html lang="en">
|
|
441
609
|
<head>
|
|
@@ -448,9 +616,12 @@ function buildPreviewHtml(input) {
|
|
|
448
616
|
<header>
|
|
449
617
|
<span id="pf-title">${escapedTitle}</span>
|
|
450
618
|
<span id="pf-counter">${initialCounter}</span>
|
|
619
|
+
${auditNoteHtml}
|
|
620
|
+
<button type="button" id="pf-export-btn">Export revision requests</button>
|
|
451
621
|
</header>
|
|
452
|
-
<div id="pf-stage-wrap"><div id="pf-stage">${stageSlide}</div></div>
|
|
622
|
+
<div id="pf-stage-wrap"><div id="pf-stage">${stageSlide}</div><aside id="pf-side">${checksLine}${auditPanel}${ANNOTATE_PANEL}</aside></div>
|
|
453
623
|
<nav id="pf-filmstrip" aria-label="slides">${thumbs}</nav>
|
|
624
|
+
${findingsDataScript}
|
|
454
625
|
<script>${JS}</script>
|
|
455
626
|
</body>
|
|
456
627
|
</html>
|
|
@@ -523,9 +694,9 @@ ${formatIssues(v.errors)}`);
|
|
|
523
694
|
await mkdir2(dirname2(resolve5(opts.output)), { recursive: true });
|
|
524
695
|
await writeFile2(opts.output, bytes);
|
|
525
696
|
const ok = `wrote ${opts.output} (${v.ir.slides.length} slides, ${bytes.length} bytes)`;
|
|
526
|
-
const
|
|
527
|
-
return
|
|
528
|
-
${
|
|
697
|
+
const notes = [warningsNote(v.warnings), normalizedNote(v.normalized)].filter((n) => n !== void 0);
|
|
698
|
+
return notes.length > 0 ? `${ok}
|
|
699
|
+
${notes.join("\n")}` : ok;
|
|
529
700
|
}
|
|
530
701
|
function normalizedNote(normalized) {
|
|
531
702
|
if (!normalized || normalized.length === 0) return void 0;
|
|
@@ -533,6 +704,10 @@ function normalizedNote(normalized) {
|
|
|
533
704
|
return `note: ${n} field alias${n === 1 ? "" : "es"} normalized
|
|
534
705
|
${normalized.map((line) => ` ${line}`).join("\n")}`;
|
|
535
706
|
}
|
|
707
|
+
function warningsNote(warnings) {
|
|
708
|
+
if (!warnings || warnings.length === 0) return void 0;
|
|
709
|
+
return formatWarnings(warnings);
|
|
710
|
+
}
|
|
536
711
|
function placeholderNote(ir) {
|
|
537
712
|
const placeholders = ir.slides.map((slide, i) => ({ slide, page: i + 1 })).filter(({ slide }) => slide.placeholder);
|
|
538
713
|
if (placeholders.length === 0) return void 0;
|
|
@@ -551,6 +726,8 @@ ${formatIssues(v.errors)}`
|
|
|
551
726
|
);
|
|
552
727
|
const ok = `OK \u2014 ${v.ir.slides.length} slides, theme "${v.ir.theme.id}"`;
|
|
553
728
|
const notes = [];
|
|
729
|
+
const warnNote = warningsNote(v.warnings);
|
|
730
|
+
if (warnNote) notes.push(warnNote);
|
|
554
731
|
const aliasNote = normalizedNote(v.normalized);
|
|
555
732
|
if (aliasNote) notes.push(aliasNote);
|
|
556
733
|
if (isDir) {
|
|
@@ -569,6 +746,9 @@ function formatAuditReport(report, ir) {
|
|
|
569
746
|
lines.push(
|
|
570
747
|
`audited ${report.pagesAudited} page${report.pagesAudited === 1 ? "" : "s"}, ${report.pagesSkipped} skipped, ${report.findings.length} finding${report.findings.length === 1 ? "" : "s"}`
|
|
571
748
|
);
|
|
749
|
+
if (report.checks.pixels === "completed") {
|
|
750
|
+
lines.push("pixel-contrast check: completed");
|
|
751
|
+
}
|
|
572
752
|
const note = placeholderNote(ir);
|
|
573
753
|
if (note) lines.push(note);
|
|
574
754
|
return lines.join("\n");
|
|
@@ -586,23 +766,23 @@ ${formatIssues(v.errors)}`
|
|
|
586
766
|
);
|
|
587
767
|
}
|
|
588
768
|
await resolveLocalAssets(v.ir, baseDir);
|
|
589
|
-
const report = auditDeck(v.ir);
|
|
769
|
+
const report = opts.pixels ? await auditDeck(v.ir, { pixels: true }) : auditDeck(v.ir);
|
|
590
770
|
const hasFindings = report.findings.length > 0;
|
|
591
771
|
const output = opts.json ? JSON.stringify(report, null, 2) : formatAuditReport(report, v.ir);
|
|
592
772
|
return { output, hasFindings };
|
|
593
773
|
}
|
|
594
|
-
async function
|
|
595
|
-
const raw = await loadIrFile(
|
|
596
|
-
const v =
|
|
774
|
+
async function runSpecValidate(specPath) {
|
|
775
|
+
const raw = await loadIrFile(specPath, "spec");
|
|
776
|
+
const v = validateSpec(raw);
|
|
597
777
|
if (!v.ok) {
|
|
598
|
-
throw new PptfastError(
|
|
778
|
+
throw new PptfastError(formatInvalidSpecError(v.errors));
|
|
599
779
|
}
|
|
600
|
-
const
|
|
601
|
-
const axes =
|
|
602
|
-
return `OK \u2014 ${
|
|
780
|
+
const spec2 = v.spec;
|
|
781
|
+
const axes = resolveNarrative(spec2.narrative);
|
|
782
|
+
return `OK \u2014 ${spec2.pages.length} pages, narrative ${axes.strategy}/${axes.pacing}/${axes.audience}, theme "${resolveSpecThemeId(spec2)}"`;
|
|
603
783
|
}
|
|
604
784
|
function runSchema(mode) {
|
|
605
|
-
const schema = mode === "style" ? styleJsonSchema() : mode === "
|
|
785
|
+
const schema = mode === "style" ? styleJsonSchema() : mode === "spec" ? specJsonSchema() : irJsonSchema();
|
|
606
786
|
return JSON.stringify(schema, null, 2);
|
|
607
787
|
}
|
|
608
788
|
function runThemes(asJson) {
|
|
@@ -610,22 +790,22 @@ function runThemes(asJson) {
|
|
|
610
790
|
if (asJson) return JSON.stringify(themes, null, 2);
|
|
611
791
|
return themes.map((t) => `${t.id.padEnd(12)} ${t.label}`).join("\n");
|
|
612
792
|
}
|
|
613
|
-
function
|
|
793
|
+
function runNarratives(asJson) {
|
|
614
794
|
if (asJson) {
|
|
615
795
|
return JSON.stringify(
|
|
616
796
|
{
|
|
617
|
-
presets:
|
|
618
|
-
|
|
619
|
-
|
|
797
|
+
presets: NARRATIVE_PRESETS,
|
|
798
|
+
strategies: STRATEGY_DEFINITIONS,
|
|
799
|
+
pacings: PACING_BUDGETS,
|
|
620
800
|
audiences: AUDIENCE_VALUES
|
|
621
801
|
},
|
|
622
802
|
null,
|
|
623
803
|
2
|
|
624
804
|
);
|
|
625
805
|
}
|
|
626
|
-
const rows = Object.values(
|
|
806
|
+
const rows = Object.values(NARRATIVE_PRESETS).map((p) => ({
|
|
627
807
|
id: p.id,
|
|
628
|
-
axes: `${p.axes.
|
|
808
|
+
axes: `${p.axes.strategy}/${p.axes.pacing}/${p.axes.audience}`,
|
|
629
809
|
themes: p.themeRecommendations.join(", ")
|
|
630
810
|
}));
|
|
631
811
|
const idWidth = Math.max(...rows.map((r) => r.id.length));
|
|
@@ -674,6 +854,9 @@ ${formatIssues(v.errors)}`);
|
|
|
674
854
|
if (aliasNote) notes.push(aliasNote);
|
|
675
855
|
if (opts.htmlOut) {
|
|
676
856
|
const htmlPath = join4(outDir, "preview.html");
|
|
857
|
+
const hasPlaceholder = ir.slides.some((slide) => slide.placeholder);
|
|
858
|
+
const auditReport = hasPlaceholder ? void 0 : auditDeck(ir);
|
|
859
|
+
const auditFindings = auditReport?.findings ?? [];
|
|
677
860
|
const html = buildPreviewHtml({
|
|
678
861
|
title: ir.filename,
|
|
679
862
|
slides: ir.slides.map((slide, i) => ({
|
|
@@ -682,10 +865,16 @@ ${formatIssues(v.errors)}`);
|
|
|
682
865
|
type: slide.type,
|
|
683
866
|
svg: svgs[i],
|
|
684
867
|
placeholder: slide.placeholder
|
|
685
|
-
}))
|
|
868
|
+
})),
|
|
869
|
+
findings: auditFindings.map((f) => ({ page: f.page, slideId: f.slideId, code: f.code, message: f.message })),
|
|
870
|
+
auditNote: hasPlaceholder ? "audit overlay skipped \u2014 deck has unfilled placeholder pages; fill every page and re-run `pptfast preview --html` to see audit findings" : void 0,
|
|
871
|
+
checks: auditReport?.checks
|
|
686
872
|
});
|
|
687
873
|
await writeFile2(htmlPath, html);
|
|
688
874
|
notes.push(`note: wrote self-contained preview to ${htmlPath}`);
|
|
875
|
+
if (auditFindings.length > 0) {
|
|
876
|
+
notes.push(`note: audit found ${auditFindings.length} finding${auditFindings.length === 1 ? "" : "s"} \u2014 see preview.html`);
|
|
877
|
+
}
|
|
689
878
|
}
|
|
690
879
|
return notes.length > 0 ? `${ok}
|
|
691
880
|
${notes.join("\n")}` : ok;
|
|
@@ -716,7 +905,7 @@ async function runAssemble(target, opts = {}) {
|
|
|
716
905
|
const summary = `wrote ${outPath} (${outIr.slides.length} slides, ${placeholderCount} placeholder${placeholderCount === 1 ? "" : "s"})`;
|
|
717
906
|
const notes = [];
|
|
718
907
|
if (generatedSeed !== void 0) {
|
|
719
|
-
notes.push(`note: generated seed ${generatedSeed} \u2014 add "seed": ${generatedSeed} to deck.
|
|
908
|
+
notes.push(`note: generated seed ${generatedSeed} \u2014 add "seed": ${generatedSeed} to deck.spec.json for revision stability`);
|
|
720
909
|
}
|
|
721
910
|
if (materializedLayoutCount !== void 0) {
|
|
722
911
|
notes.push(
|
|
@@ -730,16 +919,16 @@ async function runDisassemble(irPath, outDir) {
|
|
|
730
919
|
const v = validateIr(raw);
|
|
731
920
|
if (!v.ok) throw new PptfastError(`invalid IR:
|
|
732
921
|
${formatIssues(v.errors)}`);
|
|
733
|
-
const {
|
|
922
|
+
const { spec: spec2, pages } = disassembleDeck(v.ir);
|
|
734
923
|
const ids = Object.keys(pages);
|
|
735
924
|
for (const id of ids) assertSafeFileSegment(id, "slide id");
|
|
736
|
-
const
|
|
925
|
+
const specPath = join4(outDir, "deck.spec.json");
|
|
737
926
|
await mkdir2(outDir, { recursive: true });
|
|
738
927
|
try {
|
|
739
|
-
await writeFile2(
|
|
928
|
+
await writeFile2(specPath, JSON.stringify(spec2, null, 2) + "\n", { flag: "wx" });
|
|
740
929
|
} catch (e) {
|
|
741
930
|
if (e.code === "EEXIST") {
|
|
742
|
-
throw new PptfastError(`${
|
|
931
|
+
throw new PptfastError(`${specPath} already exists \u2014 refusing to overwrite an existing deck project`);
|
|
743
932
|
}
|
|
744
933
|
throw e;
|
|
745
934
|
}
|
|
@@ -761,13 +950,75 @@ ${formatIssues(v.errors)}`);
|
|
|
761
950
|
);
|
|
762
951
|
const pagesNote = ids.length > 0 ? `${ids.length} page file${ids.length === 1 ? "" : "s"} to ${pagesDir}` : "no pages (every slide was a placeholder)";
|
|
763
952
|
const assetsNote = assetCount > 0 ? `, and ${assetCount} asset file${assetCount === 1 ? "" : "s"} to ${assetsDir}` : "";
|
|
764
|
-
return `wrote ${
|
|
953
|
+
return `wrote ${specPath}, ${pagesNote}${assetsNote}`;
|
|
765
954
|
} catch (e) {
|
|
766
|
-
await rm(
|
|
955
|
+
await rm(specPath, { force: true }).catch(() => {
|
|
767
956
|
});
|
|
768
957
|
throw e;
|
|
769
958
|
}
|
|
770
959
|
}
|
|
960
|
+
async function runMigrate(input, output, cwd = process.cwd()) {
|
|
961
|
+
const resolvedInput = resolve5(cwd, input);
|
|
962
|
+
if (await isDeckDirectory(resolvedInput)) {
|
|
963
|
+
return runMigrateDeckDir(resolvedInput, output, cwd);
|
|
964
|
+
}
|
|
965
|
+
return runMigrateIrFile(resolvedInput, output, cwd);
|
|
966
|
+
}
|
|
967
|
+
async function runMigrateDeckDir(dir, output, cwd) {
|
|
968
|
+
const planPath = join4(dir, PLAN_FILENAME);
|
|
969
|
+
const sourceSpecPath = join4(dir, SPEC_FILENAME);
|
|
970
|
+
if (!await pathExists(planPath) && await pathExists(sourceSpecPath)) {
|
|
971
|
+
throw new PptfastError(
|
|
972
|
+
`${dir} has ${SPEC_FILENAME} but no ${PLAN_FILENAME} \u2014 this deck project is already migrated, nothing to do`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const raw = await loadIrFile(planPath, "plan");
|
|
976
|
+
const migrated = migrateDeckPlanToSpec(raw);
|
|
977
|
+
const outDir = resolve5(cwd, output);
|
|
978
|
+
const specPath = join4(outDir, SPEC_FILENAME);
|
|
979
|
+
await mkdir2(outDir, { recursive: true });
|
|
980
|
+
try {
|
|
981
|
+
await writeFile2(specPath, JSON.stringify(migrated, null, 2) + "\n", { flag: "wx" });
|
|
982
|
+
} catch (e) {
|
|
983
|
+
if (e.code === "EEXIST") {
|
|
984
|
+
throw new PptfastError(`${specPath} already exists \u2014 refusing to overwrite, delete it first or choose a different -o`);
|
|
985
|
+
}
|
|
986
|
+
throw e;
|
|
987
|
+
}
|
|
988
|
+
return `wrote ${specPath} \u2014 run \`pptfast spec validate ${specPath}\` to confirm it, then delete ${planPath} (a directory with both files present is rejected)`;
|
|
989
|
+
}
|
|
990
|
+
async function runMigrateIrFile(filePath, output, cwd) {
|
|
991
|
+
const raw = await loadIrFile(filePath);
|
|
992
|
+
const version = typeof raw === "object" && raw !== null ? raw.version : void 0;
|
|
993
|
+
if (version === "2") {
|
|
994
|
+
throw new PptfastError(
|
|
995
|
+
"pptfast migrate does not support IR v2 (spec \xA715.3: v2 has no real users) \u2014 run `pptfast validate` on the v2 file to see the full v2\u2192v4 combined field mapping and rewrite it by hand"
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
if (version !== "3") {
|
|
999
|
+
throw new PptfastError(
|
|
1000
|
+
`pptfast migrate only converts an IR v3 file (version: "3") or a deck project directory containing ${PLAN_FILENAME} \u2014 got version ${JSON.stringify(version)} in ${filePath}`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
const parsed = PptxIRV3Schema.safeParse(raw);
|
|
1004
|
+
if (!parsed.success) {
|
|
1005
|
+
const detail = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
1006
|
+
throw new PptfastError(`invalid IR v3 file ${filePath}:
|
|
1007
|
+
${detail}`);
|
|
1008
|
+
}
|
|
1009
|
+
const migrated = migrateIrV3ToV4(parsed.data);
|
|
1010
|
+
const outPath = resolve5(cwd, output);
|
|
1011
|
+
await mkdir2(dirname2(outPath), { recursive: true });
|
|
1012
|
+
try {
|
|
1013
|
+
await writeFile2(outPath, JSON.stringify(migrated, null, 2) + "\n", { flag: "wx" });
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
if (e.code === "EEXIST") {
|
|
1016
|
+
throw new PptfastError(`${outPath} already exists \u2014 refusing to overwrite, delete it first or choose a different -o`);
|
|
1017
|
+
}
|
|
1018
|
+
throw e;
|
|
1019
|
+
}
|
|
1020
|
+
return `wrote ${outPath} (migrated IR v3 \u2192 v4)`;
|
|
1021
|
+
}
|
|
771
1022
|
|
|
772
1023
|
// src/cli/update.ts
|
|
773
1024
|
import { execFile } from "child_process";
|
|
@@ -872,43 +1123,60 @@ program.command("validate").description("Validate an IR JSON file, deck project
|
|
|
872
1123
|
}
|
|
873
1124
|
});
|
|
874
1125
|
program.command("audit").description(
|
|
875
|
-
"Deterministic geometry audit (overflow, out-of-bounds, low-contrast, overlap) \u2014 exits 1 when it finds anything"
|
|
876
|
-
).argument("<target>", "IR JSON file, deck project directory, or bare name under ~/.pptfast/decks").option("--json", "machine-readable output (the full AuditReport)").action(async (target, opts) => {
|
|
1126
|
+
"Deterministic geometry audit (overflow, out-of-bounds, low-contrast, overlap, content-truncated, content-dropped), plus an optional --pixels contrast pass \u2014 exits 1 when it finds anything"
|
|
1127
|
+
).argument("<target>", "IR JSON file, deck project directory, or bare name under ~/.pptfast/decks").option("--json", "machine-readable output (the full AuditReport)").option("--pixels", "also run the optional pixel-contrast pass over image-backed text (requires sharp)").action(async (target, opts) => {
|
|
877
1128
|
try {
|
|
878
|
-
const { output, hasFindings } = await runAudit(target, { json: opts.json });
|
|
1129
|
+
const { output, hasFindings } = await runAudit(target, { json: opts.json, pixels: opts.pixels });
|
|
879
1130
|
console.log(output);
|
|
880
1131
|
if (hasFindings) process.exit(1);
|
|
881
1132
|
} catch (e) {
|
|
882
1133
|
fail(e);
|
|
883
1134
|
}
|
|
884
1135
|
});
|
|
885
|
-
program.command("schema").description("Print the IR JSON Schema (feed this to a model before it writes IR)").option("--style", "print the style-override schema instead").option("--
|
|
886
|
-
|
|
887
|
-
);
|
|
888
|
-
|
|
889
|
-
|
|
1136
|
+
program.command("schema").description("Print the IR JSON Schema (feed this to a model before it writes IR)").option("--style", "print the style-override schema instead").option("--spec", "print the deck spec schema instead").option("--plan", "removed \u2014 use --spec instead").action((opts) => {
|
|
1137
|
+
if (opts.plan) {
|
|
1138
|
+
fail(new Error("`pptfast schema --plan` has been renamed to `pptfast schema --spec` \u2014 run `pptfast schema --spec` instead"));
|
|
1139
|
+
}
|
|
1140
|
+
console.log(runSchema(opts.spec ? "spec" : opts.style ? "style" : void 0));
|
|
1141
|
+
});
|
|
1142
|
+
var plan = program.command("plan").description("Removed \u2014 use `pptfast spec` instead");
|
|
1143
|
+
plan.command("validate").description("Removed \u2014 use `pptfast spec validate` instead").argument("<file>").action(() => {
|
|
1144
|
+
fail(new Error("`pptfast plan validate` has been renamed to `pptfast spec validate` \u2014 run `pptfast spec validate <file>` instead"));
|
|
1145
|
+
});
|
|
1146
|
+
var spec = program.command("spec").description("Deck spec commands (spec \xA76)");
|
|
1147
|
+
spec.command("validate").description("Validate a deck spec JSON file against the schema and strategy-aware hard gates").argument("<spec.json>").action(async (specPath) => {
|
|
890
1148
|
try {
|
|
891
|
-
console.log(await
|
|
1149
|
+
console.log(await runSpecValidate(specPath));
|
|
892
1150
|
} catch (e) {
|
|
893
1151
|
fail(e);
|
|
894
1152
|
}
|
|
895
1153
|
});
|
|
896
|
-
program.command("assemble").description("Assemble a deck project directory (deck.
|
|
1154
|
+
program.command("assemble").description("Assemble a deck project directory (deck.spec.json + pages/ + assets/) into an IR JSON file").argument("<dir|name>", "deck project directory, or bare name under ~/.pptfast/decks").option("-o, --output <file>", "output IR JSON path (default: <dir>/deck.json)").action(async (target, opts) => {
|
|
897
1155
|
try {
|
|
898
1156
|
console.log(await runAssemble(target, { output: opts.output }));
|
|
899
1157
|
} catch (e) {
|
|
900
1158
|
fail(e);
|
|
901
1159
|
}
|
|
902
1160
|
});
|
|
903
|
-
program.command("disassemble").description("Split an IR JSON file into a deck project directory (deck.
|
|
1161
|
+
program.command("disassemble").description("Split an IR JSON file into a deck project directory (deck.spec.json + pages/)").argument("<ir.json>", "path to the IR file").requiredOption("-o, --output <dir>", "output deck project directory").action(async (irPath, opts) => {
|
|
904
1162
|
try {
|
|
905
1163
|
console.log(await runDisassemble(irPath, opts.output));
|
|
906
1164
|
} catch (e) {
|
|
907
1165
|
fail(e);
|
|
908
1166
|
}
|
|
909
1167
|
});
|
|
1168
|
+
program.command("migrate").description("Convert a v3 IR file to v4, or a deck.plan.json project directory to deck.spec.json \u2014 deterministic, no model").argument("<input>", "IR v3 JSON file, or a deck project directory containing deck.plan.json").requiredOption("-o, --output <output>", "output path \u2014 an IR JSON file for a v3 file input, a directory for a deck-project-directory input").action(async (input, opts) => {
|
|
1169
|
+
try {
|
|
1170
|
+
console.log(await runMigrate(input, opts.output));
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
fail(e);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
910
1175
|
program.command("themes").description("List built-in themes").option("--json", "machine-readable output").action((opts) => console.log(runThemes(Boolean(opts.json))));
|
|
911
|
-
program.command("
|
|
1176
|
+
program.command("narratives").description("List named narrative presets (strategy/pacing/audience axes + theme recommendations)").option("--json", "machine-readable output").action((opts) => console.log(runNarratives(Boolean(opts.json))));
|
|
1177
|
+
program.command("scenarios").description("Removed \u2014 use `pptfast narratives` instead").action(() => {
|
|
1178
|
+
fail(new Error("`pptfast scenarios` has been renamed to `pptfast narratives` \u2014 run `pptfast narratives` instead"));
|
|
1179
|
+
});
|
|
912
1180
|
program.command("init").description("Scaffold a pptfast.config.json in the current directory").action(async () => {
|
|
913
1181
|
try {
|
|
914
1182
|
console.log(await runInit());
|