@liustack/pptwise 0.22.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/LICENSE +21 -0
- package/README.md +127 -0
- package/README.zh-CN.md +136 -0
- package/cordis.patch.yml +5 -0
- package/dist/chunk-3ZUKISTY.js +114 -0
- package/dist/chunk-3ZUKISTY.js.map +1 -0
- package/dist/chunk-M35M4QUC.js +1167 -0
- package/dist/chunk-M35M4QUC.js.map +1 -0
- package/dist/chunk-VUOLBHD7.js +19 -0
- package/dist/chunk-VUOLBHD7.js.map +1 -0
- package/dist/chunk-WL5KWYKS.js +49762 -0
- package/dist/chunk-WL5KWYKS.js.map +1 -0
- package/dist/cli.js +4753 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4224 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +7 -0
- package/dist/node.js +11 -0
- package/dist/node.js.map +1 -0
- package/dist/pixel-audit-H5K6JK3X.js +218 -0
- package/dist/pixel-audit-H5K6JK3X.js.map +1 -0
- package/dist/registry-C0GJH7ZT.d.ts +46 -0
- package/dsh/client.js +1398 -0
- package/dsh/index.js +141 -0
- package/dsh/preview-tool.js +1931 -0
- package/dsh/spawnHidden.js +109 -0
- package/package.json +113 -0
- package/skills/pptwise/SKILL.md +100 -0
- package/skills/pptwise/SKILL.zh-CN.md +102 -0
- package/skills/pptwise/references/branding.md +18 -0
- package/skills/pptwise/references/branding.zh-CN.md +21 -0
- package/skills/pptwise/references/components.md +35 -0
- package/skills/pptwise/references/components.zh-CN.md +40 -0
- package/skills/pptwise/references/density.md +17 -0
- package/skills/pptwise/references/density.zh-CN.md +22 -0
- package/skills/pptwise/references/images.md +42 -0
- package/skills/pptwise/references/images.zh-CN.md +47 -0
- package/skills/pptwise/references/layouts.md +37 -0
- package/skills/pptwise/references/layouts.zh-CN.md +42 -0
- package/skills/pptwise/references/spec.md +107 -0
- package/skills/pptwise/references/spec.zh-CN.md +112 -0
- package/skills/pptwise/references/validate.md +82 -0
- package/skills/pptwise/references/validate.zh-CN.md +87 -0
- package/skills/pptwise/scripts/run.ps1 +192 -0
- package/skills/pptwise/scripts/run.sh +229 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli/commands.ts","../src/cli/config.ts","../src/cli/home.ts","../src/cli/product-env.ts","../src/cli/image-config.ts","../src/cli/deck-dir.ts","../src/cli/load-ir.ts","../src/lib/slide-edge.ts","../src/lib/svg-ids.ts","../src/cli/preview-html.ts","../src/cli/preview-manifest.ts","../src/cli/workspace.ts","../src/cli/child.ts","../src/cli/win-exec.ts","../src/cli/path-lookup.ts","../src/cli/secret-input.ts","../src/cli/config-cmd.ts","../src/cli/doctor.ts","../src/cli/image-generators.ts","../src/cli/update.ts","../src/cli/images.ts","../src/cli/redact.ts","../src/cli/image-openverse.ts","../src/cli/ssrf.ts","../src/cli/serve.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\"\nimport { installNodePlatform } from \"./platform/node\"\nimport {\n runAssemble,\n runAssetBrief,\n runAudit,\n runBrandExtract,\n runDisassemble,\n runInit,\n runMigrate,\n runNarratives,\n runPreview,\n runRender,\n runSchema,\n runSpecValidate,\n runThemes,\n runValidate,\n} from \"./cli/commands\"\nimport { runConfigSet, runConfigShow } from \"./cli/config-cmd\"\nimport { runDoctor } from \"./cli/doctor\"\nimport { runImagesFetch, runImagesGenerate, runImagesList, runImagesSearch } from \"./cli/images\"\nimport { DEFAULT_PORT, runServe } from \"./cli/serve\"\nimport { checkForUpdate, createSelfUpdater } from \"./cli/update\"\nimport { VERSION } from \"./version\"\n\ninstallNodePlatform()\n\nconst program = new Command()\nprogram\n .name(\"pptwise\")\n .description(\"Stable, editable PPTX generation for AI agents — semantic IR in, native DrawingML out\")\n .version(VERSION)\n\nfunction fail(e: unknown): never {\n console.error(e instanceof Error ? e.message : String(e))\n process.exit(1)\n}\n\nprogram\n .command(\"render\")\n .description(\"Render an IR JSON file, deck project directory, or bare deck name to a .pptx\")\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"-o, --output <file>\", \"output .pptx path (default: .pptwise/<deck>/<deck>.pptx under the project root)\")\n .option(\"--theme <id>\", \"override the deck theme (see `pptwise themes`)\")\n .option(\"--theme-file <path>\", \"load a custom theme file (see `pptwise brand extract`) and render with it\")\n .option(\"--style <path>\", \"style overrides JSON re-coloring the theme (see `pptwise schema --style`)\")\n .option(\"--draft\", \"allow unfilled placeholder pages (skip the draft gate)\")\n .option(\n \"--allow-dropped-content\",\n \"export anyway when a page holds more than fits and the layout drops blocks (skip the content-drop gate)\",\n )\n .option(\"--no-git-ignore\", \"do not add .pptwise/ to this repository's local exclude file\")\n .action(\n async (\n target: string,\n opts: {\n output?: string\n theme?: string\n themeFile?: string\n style?: string\n draft?: boolean\n allowDroppedContent?: boolean\n gitIgnore?: boolean\n },\n ) => {\n try {\n console.log(\n await runRender(target, {\n output: opts.output,\n theme: opts.theme,\n themeFilePath: opts.themeFile,\n stylePath: opts.style,\n draft: opts.draft,\n allowDroppedContent: opts.allowDroppedContent,\n gitIgnore: opts.gitIgnore,\n cwd: process.cwd(),\n }),\n )\n } catch (e) {\n fail(e)\n }\n },\n )\n\nprogram\n .command(\"validate\")\n .description(\"Validate an IR JSON file, deck project directory, or bare deck name against the schema\")\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"--theme-file <path>\", \"load a custom theme file (see `pptwise brand extract`) before validating\")\n .action(async (target: string, opts: { themeFile?: string }) => {\n try {\n console.log(await runValidate(target, process.cwd(), { themeFilePath: opts.themeFile }))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"audit\")\n .description(\n \"Deterministic geometry audit (overflow, out-of-bounds, low-contrast, overlap, content-truncated, content-dropped), plus an optional --pixels contrast pass — exits 1 when it finds anything\",\n )\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"--json\", \"machine-readable output (the full AuditReport)\")\n .option(\"--pixels\", \"also run the optional pixel-contrast pass over image-backed text (requires sharp)\")\n .option(\"--theme-file <path>\", \"load a custom theme file (see `pptwise brand extract`) and audit with it\")\n .action(async (target: string, opts: { json?: boolean; pixels?: boolean; themeFile?: string }) => {\n try {\n const { output, hasFindings } = await runAudit(target, {\n json: opts.json,\n pixels: opts.pixels,\n themeFilePath: opts.themeFile,\n })\n console.log(output)\n if (hasFindings) process.exit(1)\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"asset-brief\")\n .description(\n \"Image-generation brief for every image slot in a deck: the real rendered frame, fit/crop mode, suggested pixel size, theme palette/mood, and a paste-ready prompt\",\n )\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"--json\", \"machine-readable output (the full AssetBrief)\")\n .action(async (target: string, opts: { json?: boolean }) => {\n try {\n console.log(await runAssetBrief(target, { json: opts.json }))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"schema\")\n .description(\"Print the IR JSON Schema (feed this to a model before it writes IR)\")\n .option(\"--style\", \"print the style-override schema instead\")\n .option(\"--spec\", \"print the deck spec schema instead\")\n .option(\"--plan\", \"removed — use --spec instead\")\n .action((opts: { style?: boolean; spec?: boolean; plan?: boolean }) => {\n // vocabulary-v4 rename (spec §8.2): `--plan` renamed to `--spec`, no\n // long-lived alias — hard-fail pointing at the one new flag rather than\n // silently keep serving the plan schema under its old name.\n if (opts.plan) {\n fail(new Error(\"`pptwise schema --plan` has been renamed to `pptwise schema --spec` — run `pptwise schema --spec` instead\"))\n }\n console.log(runSchema(opts.spec ? \"spec\" : opts.style ? \"style\" : undefined))\n })\n\n// vocabulary-v4 rename (spec §8.2): `pptwise plan validate` renamed to\n// `pptwise spec validate`. The `plan` command group stays registered only so\n// `pptwise plan validate <file>` fails with a message pointing at the new\n// command, rather than commander's own generic \"unknown command\" error.\nconst plan = program.command(\"plan\").description(\"Removed — use `pptwise spec` instead\")\nplan\n .command(\"validate\")\n .description(\"Removed — use `pptwise spec validate` instead\")\n .argument(\"<file>\")\n .action(() => {\n fail(new Error(\"`pptwise plan validate` has been renamed to `pptwise spec validate` — run `pptwise spec validate <file>` instead\"))\n })\n\nconst spec = program.command(\"spec\").description(\"Deck spec commands (spec §6)\")\nspec\n .command(\"validate\")\n .description(\"Validate a deck spec JSON file against the schema and strategy-aware hard gates\")\n .argument(\"<spec.json>\")\n .action(async (specPath: string) => {\n try {\n console.log(await runSpecValidate(specPath))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"assemble\")\n .description(\"Assemble a deck project directory (deck.spec.json + pages/ + assets/) into an IR JSON file\")\n .argument(\"<dir|name>\", \"deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"-o, --output <file>\", \"output IR JSON path (default: <dir>/deck.json)\")\n .action(async (target: string, opts: { output?: string }) => {\n try {\n console.log(await runAssemble(target, { output: opts.output }))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"disassemble\")\n .description(\"Split an IR JSON file into a deck project directory (deck.spec.json + pages/)\")\n .argument(\"<ir.json>\", \"path to the IR file\")\n .requiredOption(\"-o, --output <dir>\", \"output deck project directory\")\n .action(async (irPath: string, opts: { output: string }) => {\n try {\n console.log(await runDisassemble(irPath, opts.output))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"migrate\")\n .description(\"Convert a v3 IR file to v4, rewrite chrome → branding, bloom → classroom, logo_wall → image_grid, or banner-heading → two-column on a v4 IR or deck spec, or convert a deck.plan.json project directory to deck.spec.json — deterministic, no model\")\n .argument(\"<input>\", \"IR v3 JSON file, a v4 IR or deck spec still carrying chrome, bloom, logo_wall, or banner-heading, or a deck project directory containing deck.plan.json\")\n .requiredOption(\"-o, --output <output>\", \"output path — an IR JSON file for a file input, a directory for a deck-project-directory input\")\n .action(async (input: string, opts: { output: string }) => {\n try {\n console.log(await runMigrate(input, opts.output))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"themes\")\n .description(\"List built-in themes\")\n .option(\"--json\", \"machine-readable output\")\n .action((opts: { json?: boolean }) => console.log(runThemes(Boolean(opts.json))))\n\n// `brand` is a command group (not a bare `brand-extract` command) to leave\n// room for future brand-asset extraction (logo from the slide master, etc.)\n// under the same namespace — brand-extract wave, 裁定 1.\nconst brand = program.command(\"brand\").description(\"Brand asset commands — extract your company's colors/fonts from an Office template\")\nbrand\n .command(\"extract\")\n .description(\n \"Extract brand colors and fonts from a .thmx/.potx/.pptx file into a pptwise theme file — runs entirely locally, the file never leaves your machine\",\n )\n .argument(\"<file>\", \"a .thmx theme, .potx template, or .pptx presentation\")\n .requiredOption(\"-o, --output <file>\", \"output theme JSON path (e.g. my-brand.theme.json)\")\n .option(\"--id <id>\", \"theme id to register under (default: slug of the output filename)\")\n .option(\"--label <label>\", \"human-readable theme label (default: the source theme's color-scheme name)\")\n .action(async (file: string, opts: { output: string; id?: string; label?: string }) => {\n try {\n console.log(await runBrandExtract(file, { output: opts.output, id: opts.id, label: opts.label }))\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"narratives\")\n .description(\"List named narrative presets (strategy/pacing/audience axes + theme recommendations)\")\n .option(\"--json\", \"machine-readable output\")\n .action((opts: { json?: boolean }) => console.log(runNarratives(Boolean(opts.json))))\n\n// vocabulary-v4 rename (spec §8.2): `pptwise scenarios` renamed to\n// `pptwise narratives`, no long-lived alias — hard-fail pointing at the new\n// command name.\nprogram\n .command(\"scenarios\")\n .description(\"Removed — use `pptwise narratives` instead\")\n .action(() => {\n fail(new Error(\"`pptwise scenarios` has been renamed to `pptwise narratives` — run `pptwise narratives` instead\"))\n })\n\nconst config = program.command(\"config\").description(\"User-level settings (API keys for optional stock-photo search)\")\nconfig\n .command(\"set <key> [value]\")\n .description(\"Set a user config value. Omit the value for an apiKey or clientSecret to enter it at a hidden prompt\")\n .action(async (key: string, value: string | undefined) => {\n try {\n console.log(await runConfigSet(key, value))\n } catch (e) {\n fail(e)\n }\n })\nconfig\n .command(\"show\")\n .description(\"Show the effective user config (API keys masked)\")\n .action(async () => {\n try {\n console.log(await runConfigShow())\n } catch (e) {\n fail(e)\n }\n })\n\nfunction parsePositiveInt(raw: string, flag: string): number {\n if (!/^[0-9]+$/.test(raw)) {\n fail(new Error(`invalid ${flag} \"${raw}\" — expected a positive integer`))\n }\n return Number(raw)\n}\n\nconst images = program.command(\"images\").description(\"Search and pin stock photos into workspace assets\")\nimages\n .command(\"search <query>\")\n .description(\"Search Pexels, then Pixabay, then Openverse (cc0/pdm) and print attribution lines\")\n .option(\"--orientation <orientation>\", \"landscape, portrait, or square\")\n .option(\"--color <color>\", \"color name or hex for the search API\")\n .option(\"--min-width <px>\", \"client-side minimum width in pixels\")\n .option(\"--min-height <px>\", \"client-side minimum height in pixels\")\n .action(\n async (\n query: string,\n opts: { orientation?: string; color?: string; minWidth?: string; minHeight?: string },\n ) => {\n try {\n console.log(\n await runImagesSearch(query, {\n orientation: opts.orientation,\n color: opts.color,\n minWidth: opts.minWidth !== undefined ? parsePositiveInt(opts.minWidth, \"--min-width\") : undefined,\n minHeight: opts.minHeight !== undefined ? parsePositiveInt(opts.minHeight, \"--min-height\") : undefined,\n }),\n )\n } catch (e) {\n fail(e)\n }\n },\n )\nimages\n .command(\"fetch <ref>\")\n .description(\"Download a photo (pexels:<id>, pixabay:<id>, or openverse:<id>) into .pptwise/<deck>/assets/\")\n .requiredOption(\"--deck <dir>\", \"deck project directory, path, or bare name\")\n .requiredOption(\"--as <asset_id>\", \"local asset id (filename without extension)\")\n .option(\"--query <text>\", \"search query that produced this pick (stored in the sidecar)\")\n .action(async (ref: string, opts: { deck: string; as: string; query?: string }) => {\n try {\n console.log(\n await runImagesFetch(ref, { deck: opts.deck, as: opts.as, query: opts.query, cwd: process.cwd() }),\n )\n } catch (e) {\n fail(e)\n }\n })\nimages\n .command(\"list\")\n .description(\"List pinned stock photos for a deck\")\n .requiredOption(\"--deck <dir>\", \"deck project directory, path, or bare name\")\n .action(async (opts: { deck: string }) => {\n try {\n console.log(await runImagesList({ deck: opts.deck, cwd: process.cwd() }))\n } catch (e) {\n fail(e)\n }\n })\nimages\n .command(\"generate\")\n .description(\"Generate an image with a local CLI (grok, codex, or antigravity) and pin it\")\n .requiredOption(\"--deck <dir>\", \"deck project directory, path, or bare name\")\n .requiredOption(\"--as <asset_id>\", \"local asset id (filename without extension)\")\n .option(\"--prompt <text>\", \"image prompt (otherwise taken from asset-brief)\")\n .action(async (opts: { deck: string; as: string; prompt?: string }) => {\n try {\n console.log(\n await runImagesGenerate({ deck: opts.deck, as: opts.as, prompt: opts.prompt, cwd: process.cwd() }),\n )\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"init\")\n .description(\"Scaffold a pptwise.config.json in the current directory\")\n .action(async () => {\n try {\n console.log(await runInit())\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"preview\")\n .description(\"Render each slide to an SVG file for visual self-check\")\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"-o, --output <dir>\", \"output directory (default: .pptwise/<deck>/ under the project root)\")\n .option(\"--html\", \"also write a self-contained preview.html (all slides inlined — thumbnail strip, keyboard navigation) for human review\")\n .option(\"--theme-file <path>\", \"load a custom theme file (see `pptwise brand extract`) and preview with it\")\n .option(\"--no-git-ignore\", \"do not add .pptwise/ to this repository's local exclude file\")\n .action(async (target: string, opts: { output?: string; html?: boolean; themeFile?: string; gitIgnore?: boolean }) => {\n try {\n console.log(\n await runPreview(target, opts.output, {\n htmlOut: opts.html,\n themeFilePath: opts.themeFile,\n gitIgnore: opts.gitIgnore,\n cwd: process.cwd(),\n }),\n )\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"serve\")\n .description(\"Serve a live-reloading HTML preview of an IR JSON file, deck project directory, or bare deck name over HTTP\")\n .argument(\"<target>\", \"IR JSON file, deck project directory, or bare name under ~/.pptwise/decks\")\n .option(\"--port <number>\", `port to listen on (default ${DEFAULT_PORT})`)\n .option(\"--no-open\", \"do not open the URL in a browser after starting\")\n .option(\"--theme-file <path>\", \"load a custom theme file (see `pptwise brand extract`) and serve with it\")\n .action(async (target: string, opts: { port?: string; open: boolean; themeFile?: string }) => {\n try {\n let port: number | undefined\n if (opts.port !== undefined) {\n port = Number(opts.port)\n if (!Number.isInteger(port)) {\n fail(new Error(`invalid --port value \"${opts.port}\" — expected an integer`))\n }\n }\n await runServe(target, { port, open: opts.open, themeFilePath: opts.themeFile })\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"doctor\")\n .description(\n \"Diagnose this machine's install: installed skill copies and what each pins, dsh plugin status, Node/Bun against the engines floor, the optional sharp/soffice capabilities, and a self-test render — exits 1 only on a hard failure\",\n )\n .option(\"--json\", \"machine-readable output (the full DoctorReport)\")\n .action(async (opts: { json?: boolean }) => {\n try {\n const { output, hasErrors } = await runDoctor({ json: opts.json })\n console.log(output)\n if (hasErrors) process.exit(1)\n } catch (e) {\n fail(e)\n }\n })\n\nprogram\n .command(\"check-update\")\n .description(\"Check npm for a newer pptwise release\")\n .action(async () => {\n const info = await checkForUpdate({ currentVersion: VERSION })\n if (!info.checked) fail(new Error(`update check failed: ${info.error}`))\n console.log(\n info.updateAvailable\n ? `update available: ${info.currentVersion} → ${info.latestVersion} (run \\`pptwise self-update\\`)`\n : `pptwise ${info.currentVersion} is up to date`,\n )\n })\n\nprogram\n .command(\"self-update\")\n .description(\"Update the global pptwise install to the latest release\")\n .action(async () => {\n try {\n const result = await createSelfUpdater()({ currentVersion: VERSION })\n console.log(\n result.updated\n ? `updated: ${result.currentVersion} → ${result.latestVersion}`\n : `already at the latest version (${result.currentVersion})`,\n )\n } catch (e) {\n fail(e)\n }\n })\n\nprogram.parseAsync(process.argv, { from: \"node\" }).catch(fail)\n","import { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\"\nimport { basename, dirname, join, relative, resolve } from \"node:path\"\nimport {\n formatIssues,\n formatWarnings,\n generatePptx,\n irJsonSchema,\n listThemes,\n renderSlideSvg,\n styleJsonSchema,\n validateIr,\n type ValidationIssue,\n} from \"../api\"\nimport { CANVAS_H_PX, CANVAS_W_PX } from \"../constants\"\nimport { PptwiseError } from \"../errors\"\nimport { VERSION } from \"../version\"\nimport { StyleOverrideSchema, type PptxIR, type StyleOverride } from \"../ir\"\nimport { PptxIRV3Schema } from \"../ir/legacy-v3\"\nimport {\n migrateBannerHeadingToTwoColumn,\n migrateBloomToClassroom,\n migrateChromeToBranding,\n migrateIrV3ToV4,\n migrateLogoWallToImageGrid,\n} from \"../ir/migrate\"\nimport { disassembleDeck, type PageContent } from \"../spec/assemble\"\nimport { formatInvalidSpecError, specJsonSchema, resolveSpecThemeId, validateSpec } from \"../spec\"\nimport { migrateDeckPlanToSpec } from \"../spec/migrate\"\nimport { AUDIENCE_VALUES, PACING_BUDGETS, STRATEGY_DEFINITIONS, NARRATIVE_PRESETS, resolveNarrative, type NarrativeProfile } from \"../narrative\"\nimport { auditDeck, type AuditChecks, type AuditFinding, type AuditReport } from \"../svg/audit/deck-audit\"\nimport { buildAssetBrief, type AssetBrief, type AssetBriefItem } from \"../svg/asset-brief\"\nimport { assertContrastFloor, getInstalledThemeIds } from \"../themes/definitions\"\nimport { extractBrandTheme, slugify } from \"../themes/brand-extract\"\nimport { parseBrandThemeFile, registerBrandThemeFile } from \"../themes/brand-theme-file\"\nimport { CANONICAL_THEME_IDS } from \"../themes\"\nimport { CONFIG_FILENAME, findConfig, findUserConfig } from \"./config\"\nimport {\n assertSafeFileSegment,\n isDeckDirectory,\n pathExists,\n readDeckDir,\n resolveDeckTarget,\n writeDeckAssets,\n ASSETS_DIRNAME,\n PAGES_DIRNAME,\n PLAN_FILENAME,\n SPEC_FILENAME,\n THEME_FILENAME,\n} from \"./deck-dir\"\nimport { loadIrFile, resolveLocalAssets } from \"./load-ir\"\nimport { buildPreviewHtml } from \"./preview-html\"\nimport { buildPreviewManifest } from \"./preview-manifest\"\nimport {\n prepareWorkspaceDir,\n pruneRenderedSvgs,\n resolveWorkspaceLocation,\n scanWorkspaceAssets,\n type GitRunner,\n} from \"./workspace\"\n\n/** `findUserConfig()`'s own return shape, named here so it can be threaded as\n * a parameter (`loadDeckTarget`/`applyDeckConfig` below) instead of each\n * callee re-fetching it — see `applyDeckConfig`'s own doc comment for why. */\ntype UserConfigHit = Awaited<ReturnType<typeof findUserConfig>>\n\n/** `findConfig()`'s own return shape — the project-layer counterpart to\n * {@link UserConfigHit}, threaded the same way and for the same reason\n * (W5 task 6: `loadDeckTarget` now needs the project layer too, for\n * `decksDir` — see {@link resolveDecksDirSource}). */\ntype ProjectConfigHit = Awaited<ReturnType<typeof findConfig>>\n\nasync function loadStyleFile(path: string): Promise<StyleOverride> {\n const raw = await loadIrFile(path)\n const r = StyleOverrideSchema.safeParse(raw)\n if (!r.success) {\n const detail = r.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"\\n\")\n throw new PptwiseError(`invalid style file ${path}:\\n${detail}`)\n }\n return r.data\n}\n\n/**\n * Read + validate + register a brand theme file (brand-extract wave, 裁定 3:\n * loading always goes through `registerTheme`, so its contrast hard gate\n * fires here, before `validateIr` ever sees the deck). Returns the loaded\n * theme's id. Throws {@link PptwiseError} for an unreadable/malformed file\n * (`parseBrandThemeFile`'s own path-naming message), a builtin-id collision\n * (裁定 4 — a theme file must never shadow a builtin), or a contrast-floor\n * failure (`registerTheme`'s `assertContrastFloor`, whose message names the\n * failing token, the measured ratio, and the background). Re-loading a file\n * whose id is already registered is a no-op (`registerBrandThemeFile`'s own\n * idempotency — `pptwise serve`'s rebuild loop re-runs this every rebuild).\n */\nasync function loadThemeFile(path: string): Promise<string> {\n const raw = await loadIrFile(path, \"theme\")\n return registerBrandThemeFile(parseBrandThemeFile(raw, path))\n}\n\n/**\n * Deck-project `theme.json` auto-discovery (brand-extract wave, 裁定 3's\n * zero-flag convention): a `theme.json` in the deck project directory —\n * typically `pptwise brand extract`'s own output, dropped there so the deck\n * carries its brand with it — is loaded automatically before the deck is\n * assembled, so `deck.spec.json` can reference the custom theme's id with no\n * `--theme-file` flag on any command. Must run *before* `readDeckDir`: the\n * assemble step's own `validateSpec` hard-gates the spec's theme id against\n * `getInstalledThemeIds()`, which only includes the custom id once this has\n * registered it.\n */\nasync function registerDeckThemeFile(deckDir: string): Promise<void> {\n const themePath = join(deckDir, THEME_FILENAME)\n if (await pathExists(themePath)) await loadThemeFile(themePath)\n}\n\n/** Names which of the four precedence layers the (invalid) resolved `theme`\n * value came from, for {@link applyDeckConfig}'s unknown-theme error — a\n * config-file layer names its own path, `--theme` names itself, and the\n * IR's own default has no path to name at all. */\nfunction describeThemeSource(\n opts: { theme?: string },\n projectHit: { path: string; config: { theme?: string } } | null,\n userHit: UserConfigHit,\n): string {\n if (opts.theme !== undefined) return \"--theme\"\n if (projectHit?.config.theme !== undefined) return projectHit.path\n if (userHit?.config.theme !== undefined) return userHit.path\n return \"the deck's own theme\"\n}\n\n/**\n * The `config` argument `resolveDeckTarget` (`./deck-dir.ts`) and its\n * `decksRoot` (`./home.ts`) expect: an object exposing `decksDir`, resolved\n * against whichever base that value's own layer implies. Project\n * `pptwise.config.json`'s own `decksDir` (spec §7's project-level escape\n * hatch, `ConfigSchema` in `./config.ts`, W5 task 6) wins over the user\n * config's (`UserConfigSchema`) when both are set — same project-beats-user\n * precedence as `theme`/`style` (see `applyDeckConfig` below) — but the two\n * layers resolve against different bases (project against the config file's\n * own directory, user against `pptwiseHome()`, `decksRoot`'s one fixed\n * base), so a winning project value is resolved to an absolute path *here*,\n * before being handed down: `decksRoot`'s own\n * `resolve(pptwiseHome(), config?.decksDir ?? \"decks\")` then returns that\n * absolute path unchanged (`path.resolve`'s own semantics for an absolute\n * later segment) — the same \"already-absolute short-circuits the base\"\n * behavior `decksRoot({ decksDir: \"/elsewhere/decks\" })` already exercises\n * for the user layer, reused rather than reimplemented. Falls through to\n * `userHit?.config` untouched when the project layer has no `decksDir` of\n * its own — including when there is no project config at all — so the user\n * layer (or, absent that too, `decksRoot`'s own built-in default) keeps\n * working exactly as before this function existed.\n */\nfunction resolveDecksDirSource(\n projectHit: ProjectConfigHit,\n userHit: UserConfigHit,\n): { decksDir?: string } | undefined {\n if (projectHit?.config.decksDir !== undefined) {\n return { decksDir: resolve(dirname(projectHit.path), projectHit.config.decksDir) }\n }\n return userHit?.config\n}\n\n/**\n * Resolve deck defaults onto the raw (pre-validation) IR.\n * Precedence (spec §7's four-layer chain, W5 task 5): CLI flag > project\n * `pptwise.config.json` (walked up from cwd) > user `~/.pptwise/config.json`\n * (`findUserConfig`, no cwd walk-up — a single fixed path, see `./config.ts`)\n * > whatever the artifact itself already carries (an authored IR's own\n * `theme`, or `PptxIRSchema`'s own \"consulting\" default when nothing\n * anywhere sets one — that bottom fallback is `irTheme.id`/`irTheme.style`\n * below, left `undefined` here for the schema to fill in). `--theme` only\n * swaps theme.id — IR-authored style survives. `--theme-file` (brand-extract\n * wave) slots in between `--theme` and the project config: it registers the\n * file's theme first (see `opts.themeFilePath`'s own doc comment below),\n * then its id competes at flag precedence, losing only to an explicit\n * `--theme`.\n *\n * `opts.projectHit`/`opts.userHit` are the caller's own already-fetched\n * `findConfig(cwd)`/`findUserConfig()` results (`undefined` when the caller\n * has not fetched one — this function fetches whichever is missing itself,\n * so it stays usable standalone). Every real caller (`runRender`/\n * `runValidate`/`runPreview` below) fetches both exactly once — `loadDeckTarget`\n * needs the project layer too now, for `decksDir` (W5 task 6,\n * {@link resolveDecksDirSource}) — and passes them to both this function and\n * `loadDeckTarget`, so a command reads either config file at most once per\n * invocation instead of once per helper that happens to need it.\n *\n * The installed-theme check used to run at config *read* time\n * (`readConfigFile`, `./config.ts`) — eagerly, against every layer's value,\n * whether or not it would ever actually apply. It now runs here instead,\n * once, against `theme` (the value that actually wins the four-layer\n * chain): a stale/unknown theme sitting in a config layer that a `--theme`\n * flag (or a higher-precedence config layer) overrides anyway must not\n * hard-fail a command over a value nothing was ever going to use.\n */\nexport async function applyDeckConfig(\n raw: unknown,\n opts: {\n theme?: string\n /** `--theme-file <path>` (brand-extract wave): loads + registers the\n * theme file ({@link loadThemeFile} — `registerTheme`'s contrast gate\n * fires here, before `validateIr`), then applies the loaded id at the\n * CLI-flag precedence layer — an explicit `--theme` still wins the id\n * *selection* (the file stays registered either way, so `--theme\n * <the-file's-own-id>` is redundant-but-harmless, and `--theme\n * <some-builtin>` deliberately renders that builtin while the file's\n * theme sits unused). */\n themeFilePath?: string\n stylePath?: string\n cwd: string\n projectHit?: ProjectConfigHit\n userHit?: UserConfigHit\n },\n): Promise<void> {\n if (typeof raw !== \"object\" || raw === null) return // schema error surfaces in validateIr\n const deck = raw as Record<string, unknown>\n const irTheme =\n typeof deck.theme === \"object\" && deck.theme !== null\n ? (deck.theme as Record<string, unknown>)\n : {}\n const themeFileId = opts.themeFilePath !== undefined ? await loadThemeFile(opts.themeFilePath) : undefined\n const [projectHit, userHit] = await Promise.all([\n opts.projectHit !== undefined ? Promise.resolve(opts.projectHit) : findConfig(opts.cwd),\n opts.userHit !== undefined ? Promise.resolve(opts.userHit) : findUserConfig(),\n ])\n const theme =\n opts.theme ?? themeFileId ?? projectHit?.config.theme ?? userHit?.config.theme ?? (irTheme.id as string | undefined)\n const style = opts.stylePath\n ? await loadStyleFile(opts.stylePath)\n : (projectHit?.config.style ?? userHit?.config.style ?? irTheme.style)\n if (theme !== undefined) {\n const installedThemeIds = getInstalledThemeIds()\n if (!installedThemeIds.includes(theme)) {\n throw new PptwiseError(\n `unknown theme \"${theme}\" (from ${describeThemeSource(opts, projectHit, userHit)}) — available: ${installedThemeIds.join(\", \")} (see \\`pptwise themes\\`)`,\n )\n }\n }\n if (theme === undefined && style === undefined) return\n deck.theme = { ...irTheme, id: theme, ...(style !== undefined ? { style } : {}) }\n}\n\n/**\n * Shared \"turn a CLI target argument into a raw IR-shaped object plus its\n * asset base directory\" step for `runValidate`/`runRender`/`runPreview` (W5\n * task 5) — the one piece of logic those three commands would otherwise\n * triplicate. `arg` is resolved through `resolveDeckTarget` (path vs.\n * bare-name, spec §7) using the effective `decksDir` source — project config\n * when it sets one, else the user config's, else `resolveDeckTarget`'s own\n * built-in default (W5 task 6, {@link resolveDecksDirSource}) — then\n * branches on whether the resolved target is a deck project directory:\n *\n * - directory → `readDeckDir` (assemble in memory — spec + pages/ + assets/,\n * `./deck-dir.ts`), asset paths resolve against the deck directory itself.\n * - file → the pre-existing single-file path, byte-for-byte: `loadIrFile`\n * then the same `dirname(resolve(...))` asset base every caller already\n * used. When `arg` is an explicit path (has a separator, or exists\n * locally — true of every pre-W5 caller, since every existing test passes\n * a full path), `resolveDeckTarget` returns it completely unchanged with\n * no `fs` call at all, so this branch degenerates to exactly the old\n * inline code — single-file behavior stays byte-identical.\n *\n * `isDir` is threaded back so `runValidate` can gate its dir-only placeholder\n * note on it (single-file mode must never grow that note, even for a\n * hand-authored IR that happens to set `placeholder: true` itself).\n *\n * `projectHit`/`userHit` are the caller's own already-fetched\n * `findConfig(cwd)`/`findUserConfig()` results (see `applyDeckConfig`'s doc\n * comment above for why both are threaded rather than fetched here too).\n *\n * `resolvedTarget` (serve wave, task S1) is the absolute path `target` itself\n * resolved to — the deck directory (`isDir: true`) or the single IR file\n * (`isDir: false`). `runRender`/`runPreview` use it as the slug source when\n * `-o` is omitted (workspace-artifacts wave). `buildDeckPreview` hands it to\n * `createServeServer` (`./serve.ts`) as the exact path to `fs.watch`, without\n * that module re-deriving the same bare-name/`decksDir` resolution a second\n * time.\n */\nfunction mergeWorkspaceImages(raw: unknown, extras: Record<string, { src: string }>): unknown {\n if (typeof raw !== \"object\" || raw === null) return raw\n const deck = raw as { assets?: { images?: Record<string, { src: string }> } }\n const existing = deck.assets?.images ?? {}\n return { ...deck, assets: { images: { ...extras, ...existing } } }\n}\n\nasync function loadWorkspaceStock(\n cwd: string,\n projectHit: ProjectConfigHit,\n resolvedTarget: string,\n isDir: boolean,\n): Promise<{ workspaceAssetsDir: string; images: Record<string, { src: string }> }> {\n const location = resolveWorkspaceLocation({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n target: resolvedTarget,\n isDir,\n })\n const workspaceAssetsDir = join(location.dir, ASSETS_DIRNAME)\n const images = await scanWorkspaceAssets(workspaceAssetsDir)\n return { workspaceAssetsDir, images }\n}\n\nasync function loadDeckTarget(\n arg: string,\n cwd: string,\n projectHit: ProjectConfigHit,\n userHit: UserConfigHit,\n): Promise<{ raw: unknown; baseDir: string; isDir: boolean; resolvedTarget: string; workspaceAssetsDir: string }> {\n const target = await resolveDeckTarget(arg, resolveDecksDirSource(projectHit, userHit), cwd)\n if (await isDeckDirectory(target)) {\n // Brand-extract wave: a deck-local theme.json must be registered before\n // readDeckDir's own assemble step spec-validates the theme id — see\n // registerDeckThemeFile's doc comment.\n await registerDeckThemeFile(target)\n const { ir, deckDir } = await readDeckDir(target)\n const stock = await loadWorkspaceStock(cwd, projectHit, deckDir, true)\n return {\n raw: mergeWorkspaceImages(ir, stock.images),\n baseDir: deckDir,\n isDir: true,\n resolvedTarget: deckDir,\n workspaceAssetsDir: stock.workspaceAssetsDir,\n }\n }\n const raw = await loadIrFile(target)\n const resolvedFile = resolve(target)\n const stock = await loadWorkspaceStock(cwd, projectHit, resolvedFile, false)\n return {\n raw: mergeWorkspaceImages(raw, stock.images),\n baseDir: dirname(resolvedFile),\n isDir: false,\n resolvedTarget: resolvedFile,\n workspaceAssetsDir: stock.workspaceAssetsDir,\n }\n}\n\n/** Load, apply deck config, validate, and resolve local assets — the same\n * sequence `runAssetBrief` uses, exported so `images generate` can read\n * `suggested_prompt` without duplicating the chain. */\nexport async function loadValidatedDeckIr(target: string, cwd: string): Promise<PptxIR> {\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, workspaceAssetsDir } = await loadDeckTarget(target, cwd, projectHit, userHit)\n await applyDeckConfig(raw, { cwd, projectHit, userHit })\n const v = validateIr(raw)\n if (!v.ok) {\n throw new PptwiseError(\n `invalid IR (${v.errors.length} issue${v.errors.length === 1 ? \"\" : \"s\"}):\\n${formatIssues(v.errors)}`,\n )\n }\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n return v.ir!\n}\n\nexport interface RenderOptions {\n /** `-o <file>`. Optional (workspace-artifacts wave): omitted, the deck\n * renders to `<anchor>/.pptwise/<slug>/<slug>.pptx` — see\n * {@link resolveWorkspaceLocation} (`./workspace.ts`) for how the anchor\n * and slug are derived. A relative value resolves against `cwd`, and the\n * workspace default is never consulted: an explicit path is always the\n * final word, and nothing gets created or ignored on its behalf. */\n output?: string\n theme?: string\n /** `--theme-file <path>` — see `applyDeckConfig`'s own `themeFilePath` doc comment. */\n themeFilePath?: string\n stylePath?: string\n cwd?: string\n /** `--no-git-ignore` sets this false: skip the one-time\n * `.git/info/exclude` line the workspace default would otherwise add\n * (`prepareWorkspaceDir`, `./workspace.ts`). No effect when `-o` is given\n * — that path never touches the workspace at all. */\n gitIgnore?: boolean\n /** Injectable git runner for tests. Production leaves this unset. */\n runGit?: GitRunner\n /** Skip the unfilled-placeholder-pages gate (W5 task 1) — see `generatePptx` in `../api`. */\n draft?: boolean\n /** Skip the content-drop gate — see `checkContentDropGate` in `../pptx/generate`. */\n allowDroppedContent?: boolean\n}\n\n/**\n * `irPath` accepts a single IR/spec JSON file, a deck project directory, or\n * a bare deck name under `~/.pptwise/decks` (W5 task 5, `loadDeckTarget`\n * above) — directory/bare-name input is assembled in memory first, then\n * follows the exact same validate → resolve-assets → generate pipeline a\n * single file always has. `--draft` threads through unchanged either way\n * (`generatePptx`'s own gate, W5 task 1) — a deck project's own placeholder\n * pages are exactly what that gate exists to catch. `--allow-dropped-content`\n * threads the same way for the sibling content-drop gate\n * (`checkContentDropGate`, `../pptx/generate`).\n *\n * Appends the same field-alias {@link normalizedNote} `runValidate` below\n * prints (W5 whole-branch review finding 3 — the README already claimed\n * `render` did this; it never actually threaded `v.normalized` through\n * until now), plus {@link warningsNote} (borrow wave, Task 2) whenever the\n * pre-flight `validateIr` call below returned warn-severity findings —\n * `generatePptx`'s own internal re-validate (`../api.ts`) follows the exact\n * same error-only severity rule, so this pre-flight check and the actual\n * generation it gates in step can never disagree on what counts as blocking.\n */\nexport async function runRender(irPath: string, opts: RenderOptions): Promise<string> {\n const cwd = opts.cwd ?? process.cwd()\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, isDir, resolvedTarget, workspaceAssetsDir } = await loadDeckTarget(irPath, cwd, projectHit, userHit)\n await applyDeckConfig(raw, {\n theme: opts.theme,\n themeFilePath: opts.themeFilePath,\n stylePath: opts.stylePath,\n cwd,\n projectHit,\n userHit,\n })\n const v = validateIr(raw)\n if (!v.ok) throw new PptwiseError(`invalid IR:\\n${formatIssues(v.errors)}`)\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n const bytes = await generatePptx(v.ir!, {\n draft: opts.draft,\n allowDroppedContent: opts.allowDroppedContent,\n })\n const extraNotes: string[] = []\n let output: string\n if (opts.output !== undefined) {\n output = resolve(cwd, opts.output)\n await mkdir(dirname(output), { recursive: true })\n } else {\n const location = resolveWorkspaceLocation({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n target: resolvedTarget,\n isDir,\n })\n extraNotes.push(...(await prepareWorkspaceDir(location, { gitIgnore: opts.gitIgnore, runGit: opts.runGit })))\n output = join(location.dir, `${location.slug}.pptx`)\n }\n await writeFile(output, bytes)\n const ok = `wrote ${output} (${v.ir!.slides.length} slides, ${bytes.length} bytes)`\n const notes = [...extraNotes, warningsNote(v.warnings), normalizedNote(v.normalized)].filter(\n (n): n is string => n !== undefined,\n )\n return notes.length > 0 ? `${ok}\\n${notes.join(\"\\n\")}` : ok\n}\n\n/**\n * `\"note: N field alias(es) normalized\\n path: alias → canonical\\n...\"` —\n * the note line every one of `validateIr`'s callers appends after its own\n * success line when `ValidateResult.normalized` (`../api.ts`) is non-empty,\n * i.e. `validateIr` deterministically rewrote at least one synonym field\n * name before parsing (W5 task 4 — kpi `title`→`label` and friends,\n * `../ir/field-aliases.ts`). Extracted so `runRender`/`runPreview` (W5\n * whole-branch review finding 3 — the README already claimed `validate`\n * *and* `render` both printed this note — `render` never actually did, and\n * `preview` is folded in here too for the same reason) can append the exact\n * same note `runValidate` below has always printed, instead of each\n * re-deriving the formatting a second and third time. `undefined` when\n * nothing was normalized, the same \"let the caller skip the line entirely\"\n * shape {@link placeholderNote} below already uses.\n */\nfunction normalizedNote(normalized: string[] | undefined): string | undefined {\n if (!normalized || normalized.length === 0) return undefined\n const n = normalized.length\n return `note: ${n} field alias${n === 1 ? \"\" : \"es\"} normalized\\n${normalized.map((line) => ` ${line}`).join(\"\\n\")}`\n}\n\n/**\n * `\"warning: page N — path: message\"` block, one line per\n * {@link ValidateResult.warnings} entry (`../api.ts`, borrow wave Task 2's\n * dual-threshold severity split) — printed by `runValidate`/`runRender`\n * alongside their own success line whenever `validateIr` returned at least\n * one warn-severity finding. `undefined` when there are none, same\n * \"let the caller skip the line entirely\" shape {@link normalizedNote}\n * above and {@link placeholderNote} below both use. Exit code is\n * unaffected either way — a warning never turns a `runValidate`/`runRender`\n * call into a thrown `PptwiseError` (only `!v.ok`, i.e. an error-severity\n * finding, does that). This note is purely additive visibility.\n */\nfunction warningsNote(warnings: ValidationIssue[] | undefined): string | undefined {\n if (!warnings || warnings.length === 0) return undefined\n return formatWarnings(warnings)\n}\n\n/**\n * Dir-mode-only informational note (W5 task 5, `runValidate` below): unlike\n * `generatePptx`'s draft gate (a hard error) or the content-quality gate\n * (which skips a placeholder's content rules entirely, `ir-quality.ts`), a\n * placeholder page is schema-valid and produces no validation issue on its\n * own — without this, a deck project with pages still unfilled would\n * validate silently \"OK\" with no signal anything is left to do. `undefined`\n * when there are none, so the caller can skip the note line entirely rather\n * than test its own string for emptiness.\n */\nfunction placeholderNote(ir: PptxIR): string | undefined {\n const placeholders = ir.slides\n .map((slide, i) => ({ slide, page: i + 1 }))\n .filter(({ slide }) => slide.placeholder)\n if (placeholders.length === 0) return undefined\n const refs = placeholders\n .map(({ slide, page }) => (slide.id ? `${slide.id} (page ${page})` : `page ${page}`))\n .join(\", \")\n return `note: ${placeholders.length} unfilled placeholder page${placeholders.length === 1 ? \"\" : \"s\"}: ${refs}`\n}\n\n/**\n * `irPath` accepts a single IR/spec JSON file, a deck project directory, or\n * a bare deck name (same `loadDeckTarget` resolution `runRender` uses).\n * Directory/bare-name input additionally gets a {@link placeholderNote} —\n * gated on `isDir` specifically so single-file mode (including a\n * hand-authored IR that sets `placeholder: true` itself) never grows one,\n * keeping that path's output byte-identical to before this task.\n *\n * Returns human-readable report. Throws PptwiseError when invalid (CLI exit 1).\n * When `validateIr` deterministically rewrote any synonym field names before\n * parsing (W5 task 4 — kpi `title`→`label` and friends, `ir/field-aliases.ts`),\n * appends them as a \"note\" line after the OK summary: visible so the caller\n * knows their input got silently massaged, but never a reason to fail — a\n * fixed alias never makes it into `v.errors`.\n *\n * Borrow wave, Task 2 (dual-threshold severity): also appends\n * {@link warningsNote} whenever `validateIr` returned warn-severity\n * findings — printed as `\"warning: ...\"` lines, exit code 0 either way\n * (only `!v.ok`, above, throws). A deck can print `OK` and still carry\n * warnings — that combination is the point of the split, not a bug.\n *\n * Borrow wave, Task 2 follow-up (review finding, medium): also runs\n * `resolveLocalAssets` on `v.ir!`, same as `runRender`/`runAudit`/\n * `runPreview` already do — `validateIr` itself only sniffs already-inlined\n * `data:` URIs (`checkAssetBytes`, `../api.ts`'s own doc comment on why a\n * local file path is a different, Node-only ingestion form), so without\n * this a deck-dir referencing a corrupt local `.png` printed `OK` here while\n * `render` correctly rejected the exact same input right after — an\n * inconsistency with SKILL.md's Phase 3 contract, which treats `validate`\n * as the authoritative pre-flight check. `resolveLocalAssets` mutating\n * `v.ir!.assets.images[x].src` into a data URI as a side effect is harmless\n * here — nothing this function reads afterward (`slides.length`, `theme.id`,\n * `placeholderNote`) depends on `src` — so there was no reason to write a\n * separate check-only variant; reusing the exact same function guarantees\n * identical rejection semantics with `render` by construction, not by\n * keeping two copies of the same logic in sync by hand.\n */\nexport async function runValidate(\n irPath: string,\n cwd = process.cwd(),\n opts: { themeFilePath?: string } = {},\n): Promise<string> {\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, isDir, workspaceAssetsDir } = await loadDeckTarget(irPath, cwd, projectHit, userHit)\n await applyDeckConfig(raw, { themeFilePath: opts.themeFilePath, cwd, projectHit, userHit })\n const v = validateIr(raw)\n if (!v.ok)\n throw new PptwiseError(\n `invalid IR (${v.errors.length} issue${v.errors.length === 1 ? \"\" : \"s\"}):\\n${formatIssues(v.errors)}`,\n )\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n const ok = `OK — ${v.ir!.slides.length} slides, theme \"${v.ir!.theme.id}\"`\n const notes: string[] = []\n const warnNote = warningsNote(v.warnings)\n if (warnNote) notes.push(warnNote)\n const aliasNote = normalizedNote(v.normalized)\n if (aliasNote) notes.push(aliasNote)\n if (isDir) {\n const note = placeholderNote(v.ir!)\n if (note) notes.push(note)\n }\n return notes.length > 0 ? `${ok}\\n${notes.join(\"\\n\")}` : ok\n}\n\n/**\n * `\"page 3 (p-kpi): [low-contrast] ...\"` — one line per {@link AuditFinding},\n * echoing `formatIssues`' own `\"page N (id) — path: message\"` convention\n * (`../api.ts`) with a bracketed `[code]` standing in for `path` — an\n * `AuditFinding` has no `path` (it is not a schema-location error, see that\n * interface's own doc comment in `../svg/audit/deck-audit.ts`), and `code`\n * is the closest equivalent \"what kind of problem\" tag. The bracket keeps an\n * audit-finding line visually distinct from a validate-error line at a\n * glance, per the plan's own worked example.\n */\nfunction formatAuditFinding(f: AuditFinding): string {\n const idSuffix = f.slideId !== undefined ? ` (${f.slideId})` : \"\"\n return `page ${f.page}${idSuffix}: [${f.code}] ${f.message}`\n}\n\n/**\n * Human-readable `pptwise audit` report (W6 task 2, spec §7 workflow ④):\n * every finding as its own {@link formatAuditFinding} line — already\n * naturally grouped by page, since `auditDeck` pushes findings in slide\n * order (`../svg/audit/deck-audit.ts`) — followed by a trailing summary line\n * in the plan's own literal wording (\"audited N pages, M skipped, K\n * findings\") so an agent can read just the last line to decide whether to\n * keep iterating, instead of counting findings itself. {@link placeholderNote}\n * runs unconditionally (unlike `runValidate`'s dir-mode-only gating on that\n * same helper below) — audit has no pre-existing single-file-mode output to\n * keep byte-identical the way `runValidate` did when that gating was added,\n * so there is no reason to withhold a genuinely useful note from a\n * hand-authored IR that happens to carry placeholders too.\n *\n * `checks.pixels === \"completed\"` (audit-v2 phase B, i.e. `--pixels` was\n * passed) appends one more line — purely additive, gated on that exact\n * value so the far more common no-`--pixels` run stays byte-identical to\n * the wording pinned above (`checks.pixels` is `\"not-requested\"` there,\n * never `\"completed\"`). No line at all for the omitted case rather than an\n * explicit \"not requested\" note: the human already knows whether they\n * passed the flag, and the machine-readable `--json` path (never silent\n * about `checks` either way) is what an agent actually consumes to tell\n * \"not checked\" apart from \"checked and clean\".\n */\nfunction formatAuditReport(report: AuditReport, ir: PptxIR): string {\n const lines = report.findings.map(formatAuditFinding)\n lines.push(\n `audited ${report.pagesAudited} page${report.pagesAudited === 1 ? \"\" : \"s\"}, ${report.pagesSkipped} skipped, ${report.findings.length} finding${report.findings.length === 1 ? \"\" : \"s\"}`,\n )\n if (report.checks.pixels === \"completed\") {\n lines.push(\"pixel-contrast check: completed\")\n }\n const note = placeholderNote(ir)\n if (note) lines.push(note)\n return lines.join(\"\\n\")\n}\n\nexport interface AuditOptions {\n json?: boolean\n cwd?: string\n /** `--pixels` (audit-v2 phase B, spec §4.3/§11.7): also run the optional\n * pixel-contrast pass over image-backed text. Explicit opt-in only — see\n * `auditDeck`'s own overload doc comment for why this is threaded as a\n * ternary with a literal in each arm rather than passed straight through\n * as `{ pixels: opts.pixels }` (a plain `boolean` doesn't match either\n * overload). Missing rasterization capability or a remote asset\n * reference makes this command fail loudly (a rejected `auditDeck`\n * promise propagates straight out of this function, same as the\n * existing invalid-IR `PptwiseError` path) rather than silently\n * reporting a clean pixel check that never ran. */\n pixels?: boolean\n /** `--theme-file <path>` — see `applyDeckConfig`'s own `themeFilePath` doc comment. */\n themeFilePath?: string\n}\n\nexport interface AuditCliResult {\n /** Human report ({@link formatAuditReport}) or, with `opts.json`, the raw\n * `JSON.stringify`'d {@link AuditReport} verbatim — the plan's own \"the\n * full AuditReport\" requirement, unmodified by any CLI-side enrichment. */\n output: string\n /** `true` when `report.findings.length > 0`. The CLI (`../cli.ts`) prints\n * `output` either way, then exits 1 on this signal alone — clean exits 0\n * (spec §7 workflow ④: advisory, not a hard gate, but still\n * agent-judgeable purely from the exit code without parsing output). */\n hasFindings: boolean\n}\n\n/**\n * `pptwise audit <target> [--json]` (W6 task 2, spec §7 workflow ④): resolve\n * `target` through the exact same `loadDeckTarget` path `runValidate`/\n * `runRender`/`runPreview` already use (IR file / deck project directory /\n * bare name under `~/.pptwise/decks`), validate first, then hand the\n * validated IR to `auditDeck` (`../svg/audit/deck-audit.ts`, pure, no I/O).\n *\n * An invalid deck fails exactly like `pptwise validate` — same message\n * shape, same `PptwiseError` → CLI exit-1 path — and never reaches\n * `auditDeck` at all: the geometry/contrast/overlap checks only mean\n * anything over a schema-valid, already-quality-gated deck (`auditDeck`'s\n * own \"advisory, not a hard gate\" doc comment — `validateIr` is the hard\n * gate this command leans on rather than re-implements).\n *\n * `resolveLocalAssets` runs after validation, same as `runRender`/\n * `runPreview` — a deck referencing local (non-`data:`/non-`http(s)`) image\n * files must have them inlined before `auditDeck`'s internal `renderSlideSvg`\n * calls, otherwise a local asset's `src` would still be its raw relative\n * path when the contrast checker's background-region walk inspects it,\n * auditing a slide shape that doesn't match what `render`/`preview` actually\n * produce for the same deck.\n *\n * No `--theme`/`--style` flags (unlike `runRender`) — the plan's CLI surface\n * for this command is deliberately just `<target> [--json]` — but\n * `applyDeckConfig` still runs (with no CLI-flag overrides) so a project/user\n * config's own theme/style default still applies, the same \"config layers\n * apply even with no flag passed\" behavior `runValidate` already has.\n */\nexport async function runAudit(target: string, opts: AuditOptions = {}): Promise<AuditCliResult> {\n const cwd = opts.cwd ?? process.cwd()\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, workspaceAssetsDir } = await loadDeckTarget(target, cwd, projectHit, userHit)\n await applyDeckConfig(raw, { themeFilePath: opts.themeFilePath, cwd, projectHit, userHit })\n const v = validateIr(raw)\n if (!v.ok) {\n throw new PptwiseError(\n `invalid IR (${v.errors.length} issue${v.errors.length === 1 ? \"\" : \"s\"}):\\n${formatIssues(v.errors)}`,\n )\n }\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n const report = opts.pixels ? await auditDeck(v.ir!, { pixels: true }) : auditDeck(v.ir!)\n const hasFindings = report.findings.length > 0\n const output = opts.json ? JSON.stringify(report, null, 2) : formatAuditReport(report, v.ir!)\n return { output, hasFindings }\n}\n\n// ── asset-brief ──────────────────────────────────────────────────────────\n\n/**\n * `\"page 3 (p-kpi, content) — pic (missing): frame 613x307 @ (571,203), aspect\n * 2:1, cover ...\"` — one block per {@link AssetBriefItem}, grouped naturally\n * by page order (`buildAssetBrief` pushes items in slide/document order, same\n * convention {@link formatAuditFinding} relies on for audit findings). A\n * `rendered: false` item prints without the frame/pixel lines (there is\n * nothing real to report — {@link buildAssetBrief}'s own doc comment) but\n * still gets its palette/mood/prompt lines, matching the brief's own \"never\n * silently drop it\" contract. A `shared` item (>=2 `image` components on the\n * page reference the same `asset_id`) gets an explicit \"(shared by N image\n * slots, frame not attributable to one)\" header suffix — this asset_id's\n * frames are real but which specific component each one belongs to cannot be\n * determined from the render (`buildAssetBrief`'s own doc comment), so the\n * report says so instead of implying a pairing it can't back up.\n */\nfunction formatAssetBriefItem(item: AssetBriefItem): string {\n const idSuffix = item.page.id !== undefined ? `, ${item.page.id}` : \"\"\n const sharedSuffix = item.shared\n ? ` (shared by ${item.occurrenceCount} image slots, frame not attributable to one)`\n : \"\"\n const header = `page ${item.page.index + 1} (${item.page.type}${idSuffix}) — ${item.asset_id}${item.missing ? \" (missing)\" : \"\"}${item.rendered ? \"\" : \" (not rendered under the selected layout)\"}${sharedSuffix}`\n const lines = [header]\n if (item.frame && item.suggested_pixels) {\n lines.push(\n ` frame: ${item.frame.w}x${item.frame.h} @ (${item.frame.x},${item.frame.y}), aspect ${item.frame.aspect}, ${item.fit.mode}`,\n )\n lines.push(` suggested pixels: ${item.suggested_pixels.w}x${item.suggested_pixels.h}`)\n }\n lines.push(` fit: ${item.fit.note}`)\n lines.push(` palette: primary ${item.palette.primary}, accent ${item.palette.accent} (${item.palette.hexes.join(\", \")})`)\n lines.push(` mood: ${item.mood.description}`)\n lines.push(` prompt: ${item.suggested_prompt}`)\n return lines.join(\"\\n\")\n}\n\n/**\n * Human-readable `pptwise asset-brief` report (asset-brief plan, task 1):\n * one {@link formatAssetBriefItem} block per image slot, followed by a\n * trailing summary line in the same \"read just the last line\" spirit\n * {@link formatAuditReport} already established for `audit`.\n */\nfunction formatAssetBriefReport(brief: AssetBrief): string {\n if (brief.items.length === 0) return `no image components found for theme \"${brief.theme}\"`\n const missingCount = brief.items.filter((i) => i.missing).length\n const notRenderedCount = brief.items.filter((i) => !i.rendered).length\n const lines = brief.items.map(formatAssetBriefItem)\n lines.push(\n `${brief.items.length} image slot${brief.items.length === 1 ? \"\" : \"s\"}, ${missingCount} to generate, ${notRenderedCount} not rendered under their selected layout`,\n )\n return lines.join(\"\\n\\n\")\n}\n\nexport interface AssetBriefOptions {\n json?: boolean\n cwd?: string\n}\n\n/**\n * `pptwise asset-brief <target> [--json]` (asset-brief plan, task 1): resolve\n * `target` through the exact same `loadDeckTarget` path `audit`/`validate`/\n * `render`/`preview` already use, validate first (same error shape/exit-1\n * path as every other command in this file), then hand the validated IR to\n * `buildAssetBrief` (`../svg/asset-brief.ts`, pure, no I/O beyond the render\n * pass it runs internally).\n *\n * No exit-1 gating on `missing`/`rendered` the way `audit` gates on\n * `hasFindings` — a to-do list of images still needing art is not a defect\n * the way an audit finding is; this command is purely informational, the\n * same \"advisory\" posture `runValidate`'s `placeholderNote` already has for\n * unfilled pages.\n */\nexport async function runAssetBrief(target: string, opts: AssetBriefOptions = {}): Promise<string> {\n const cwd = opts.cwd ?? process.cwd()\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, workspaceAssetsDir } = await loadDeckTarget(target, cwd, projectHit, userHit)\n await applyDeckConfig(raw, { cwd, projectHit, userHit })\n const v = validateIr(raw)\n if (!v.ok) {\n throw new PptwiseError(\n `invalid IR (${v.errors.length} issue${v.errors.length === 1 ? \"\" : \"s\"}):\\n${formatIssues(v.errors)}`,\n )\n }\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n const brief = buildAssetBrief(v.ir!)\n return opts.json ? JSON.stringify(brief, null, 2) : formatAssetBriefReport(brief)\n}\n\n/**\n * Validate a deck spec JSON file (W5 task 2: `pptwise plan validate`, renamed\n * to `pptwise spec validate` — vocabulary-v4 rename, task 2, spec §8.2).\n * `loadIrFile` is a generic \"read + JSON-parse with a readable failure\n * message\" helper despite its IR-scoped name (`./load-ir.ts`) — reused as-is\n * rather than duplicated, same pattern `runValidate` above uses for IR.\n * Returns human-readable report. Throws PptwiseError when invalid (CLI exit 1).\n *\n * Appends the same {@link normalizedNote} `runValidate`/`runRender` print\n * (T0b fix 2 scope extension) whenever `validateSpec` rewrote a top-level\n * `narrative: {id: \"<preset>\"}` shape (`SpecValidateResult.normalized`,\n * `../spec/index.ts`) — the spec-validate channel gets the identical note\n * format the bare-IR path already has, not a second, differently-shaped one.\n */\nexport async function runSpecValidate(specPath: string): Promise<string> {\n const raw = await loadIrFile(specPath, \"spec\")\n // Brand-extract wave: a spec that names a custom theme id normally sits in\n // a deck project directory whose own theme.json defines it — auto-load it\n // from alongside the spec file, same zero-flag convention loadDeckTarget\n // applies for whole-directory targets, so `pptwise spec validate\n // deck-dir/deck.spec.json` doesn't hard-fail the theme gate a later\n // `pptwise validate deck-dir/` would pass.\n await registerDeckThemeFile(dirname(resolve(specPath)))\n const v = validateSpec(raw)\n if (!v.ok) {\n throw new PptwiseError(formatInvalidSpecError(v.errors))\n }\n const spec = v.spec!\n // Safe to call unguarded: validateSpec already resolved this same\n // expression successfully as part of its own hard-gate chain.\n const axes = resolveNarrative(spec.narrative as string | Partial<NarrativeProfile> | undefined)\n const ok = `OK — ${spec.pages.length} pages, narrative ${axes.strategy}/${axes.pacing}/${axes.audience}, theme \"${resolveSpecThemeId(spec)}\"`\n const aliasNote = normalizedNote(v.normalized)\n return aliasNote ? `${ok}\\n${aliasNote}` : ok\n}\n\n/** `mode` selects which JSON Schema to print (`pptwise schema [--style|--spec]`,\n * spec §8.2's `schema --plan`→`schema --spec` rename, task 2) — `\"plan\"` was\n * the pre-rename flag value, no longer accepted (`../cli.ts` hard-fails a\n * bare `--plan` before this function is ever called, see that file's own\n * comment). */\nexport function runSchema(mode?: \"style\" | \"spec\"): string {\n const schema = mode === \"style\" ? styleJsonSchema() : mode === \"spec\" ? specJsonSchema() : irJsonSchema()\n return JSON.stringify(schema, null, 2)\n}\n\nexport function runThemes(asJson: boolean): string {\n const themes = listThemes()\n if (asJson) return JSON.stringify(themes, null, 2)\n return themes.map((t) => `${t.id.padEnd(12)} ${t.label}`).join(\"\\n\")\n}\n\nexport interface BrandExtractOptions {\n output: string\n /** `--id` (裁定 4) — defaults to a slug of the output filename. */\n id?: string\n /** `--label` — defaults to the source theme's own color-scheme name. */\n label?: string\n}\n\n/** `basename(output)` minus a trailing `.theme.json`/`.json`, slugged — the\n * 裁定 4 default id (`my-brand.theme.json` → `my-brand`). */\nfunction defaultThemeIdFor(output: string): string {\n return slugify(basename(output).replace(/\\.theme\\.json$|\\.json$/i, \"\"))\n}\n\n/**\n * `pptwise brand extract <file> -o <out.theme.json> [--id] [--label]`\n * (brand-extract wave, roadmap §2.0.1): extract brand colors/fonts from a\n * user's own `.thmx`/`.potx`/`.pptx` **locally** — the file's bytes never\n * leave the machine; there is no network call anywhere in this path — into a\n * pptwise theme file (`extractBrandTheme`, `../themes/brand-extract.ts`).\n *\n * Two fail-fast checks beyond extraction itself, both mirroring what the\n * load path would reject later, surfaced here where the fix is cheapest:\n * - a builtin-id collision (`--id consulting`, or an output filename that\n * slugs to one) errors now, with the same message shape\n * `registerBrandThemeFile` uses — never writes a file that every load\n * attempt would refuse (裁定 4).\n * - a derived palette that would fail `registerTheme`'s contrast floor\n * (`assertContrastFloor` — a pathological source template whose text/\n * background tones are too close) still writes the file but appends the\n * would-be load error as a warning, so the user can hand-adjust the\n * written JSON's colors instead of discovering the problem at render time.\n */\nexport async function runBrandExtract(file: string, opts: BrandExtractOptions): Promise<string> {\n let bytes: Buffer\n try {\n bytes = await readFile(file)\n } catch {\n throw new PptwiseError(`cannot read template file: ${file}`)\n }\n const id = opts.id ?? defaultThemeIdFor(opts.output)\n if ((CANONICAL_THEME_IDS as readonly string[]).includes(id)) {\n throw new PptwiseError(\n `theme id \"${id}\" collides with a built-in pptwise theme — pick a different id with --id (or a different output filename)`,\n )\n }\n const theme = await extractBrandTheme(bytes, { id, label: opts.label })\n const outPath = resolve(opts.output)\n await mkdir(dirname(outPath), { recursive: true })\n await writeFile(outPath, JSON.stringify(theme, null, 2) + \"\\n\")\n const c = theme.style.colors\n const lines = [\n `wrote ${opts.output} (theme \"${theme.id}\", label \"${theme.label}\")`,\n ` colors: bg ${c.bg}, text ${c.text}, primary ${c.primary}, accent ${c.accent}, muted ${c.muted} (derived), ${c.chartPalette.length} chart colors`,\n ` fonts: heading \"${theme.style.fonts.heading[0]}\", body \"${theme.style.fonts.body[0]}\"`,\n `use it: pptwise render <deck> --theme-file ${opts.output} — or drop it into a deck project directory as ${THEME_FILENAME} and reference \"${theme.id}\" as the deck's theme`,\n ]\n try {\n assertContrastFloor(theme.id, theme.style)\n } catch (e) {\n lines.push(\n `warning: this theme will be refused at load time — ${e instanceof Error ? e.message : String(e)}. Edit the written file's colors (darker text, or a lighter bg) before using it`,\n )\n }\n return lines.join(\"\\n\")\n}\n\n/**\n * List the named narrative presets (spec §5): strategy/pacing/audience axes +\n * soft theme recommendations — never a hard constraint, see\n * `NarrativePreset.themeRecommendations`'s own doc comment in `narrative/index.ts`.\n * `--json` hands back the full machine-readable payload an agent would want\n * before picking a narrative: every preset, plus the raw strategy/pacing/audience\n * tables those presets are built from (`STRATEGY_DEFINITIONS`/`PACING_BUDGETS`\n * carry data this wave doesn't yet consume for selection — W4's job — but are\n * still useful for a caller inspecting what each axis value means).\n *\n * CLI surface renamed this task (spec §8.2's `scenarios`→`narratives`\n * rename, task 2): command name `narratives`, `--json` output field names\n * `strategies`/`pacings` (were `modes`/`deliveries`) — kept in step with the\n * command's own new name rather than leaving a `pptwise narratives --json`\n * caller staring at a `modes` key for what is now the `strategy` axis.\n */\nexport function runNarratives(asJson: boolean): string {\n if (asJson) {\n return JSON.stringify(\n {\n presets: NARRATIVE_PRESETS,\n strategies: STRATEGY_DEFINITIONS,\n pacings: PACING_BUDGETS,\n audiences: AUDIENCE_VALUES,\n },\n null,\n 2,\n )\n }\n const rows = Object.values(NARRATIVE_PRESETS).map((p) => ({\n id: p.id,\n axes: `${p.axes.strategy}/${p.axes.pacing}/${p.axes.audience}`,\n themes: p.themeRecommendations.join(\", \"),\n }))\n const idWidth = Math.max(...rows.map((r) => r.id.length))\n const axesWidth = Math.max(...rows.map((r) => r.axes.length))\n return rows\n .map((r) => `${r.id.padEnd(idWidth + 2)}${r.axes.padEnd(axesWidth + 2)}${r.themes}`)\n .join(\"\\n\")\n}\n\nconst CONFIG_TEMPLATE = {\n theme: \"consulting\",\n style: {\n colors: { primary: \"#0B5FFF\", accent: \"#FF6A00\" },\n },\n} as const\n\n/** Scaffold pptwise.config.json in cwd. Never overwrites. */\nexport async function runInit(cwd = process.cwd()): Promise<string> {\n const target = join(cwd, CONFIG_FILENAME)\n try {\n await writeFile(target, JSON.stringify(CONFIG_TEMPLATE, null, 2) + \"\\n\", { flag: \"wx\" })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EEXIST\") {\n throw new PptwiseError(`${target} already exists — edit it instead`)\n }\n throw e\n }\n return `wrote ${target} — themes: \\`pptwise themes\\`, style schema: \\`pptwise schema --style\\``\n}\n\nexport interface PreviewOptions {\n cwd?: string\n /** `--no-git-ignore` sets this false. No effect when `-o` is given. */\n gitIgnore?: boolean\n /** Injectable git runner for tests. Production leaves this unset. */\n runGit?: GitRunner\n /** `--theme-file <path>` — see `applyDeckConfig`'s own `themeFilePath` doc comment. */\n themeFilePath?: string\n /** `--html` (v0.3 W7 task 1, spec §7 workflow ⑤): also write a\n * self-contained `preview.html` alongside the per-slide SVG files —\n * every slide's already-rendered SVG inlined into one file (thumbnail\n * filmstrip + keyboard/click navigation, `buildPreviewHtml`,\n * `./preview-html.ts`) for a human (or an agent that can view HTML) to\n * flip through the whole deck at once instead of opening N separate SVG\n * files. Named `htmlOut` rather than `html` so `RenderOptions.draft`-style\n * option objects in this file all read as \"what to produce\", not\n * \"whether this is HTML\" (there is nothing else this bundle could be).\n * Known limitation (see `buildPreviewHtml`'s own doc comment,\n * `./preview-html.ts`): self-containment assumes every image asset is\n * local or already a `data:` URI — a remote `http(s):` asset src passes\n * through `resolveLocalAssets` untouched and lands in the bundle as a\n * live network reference, not an inlined file.\n *\n * Also gates the audit overlay (notes+preview wave, task 2): when set\n * and the deck has no placeholder page, `runPreview` runs `auditDeck`\n * (`../svg/audit/deck-audit.ts`) and embeds its findings and `checks`\n * into `preview.html` (per-page badges + a findings panel + a one-line\n * checks summary, `buildPreviewHtml`). A deck with any placeholder page\n * skips the audit entirely instead of running it partially — see\n * `runPreview`'s own doc comment for why. */\n htmlOut?: boolean\n}\n\n/**\n * Shared \"assemble/validate/render\" half of the preview build pipeline\n * (serve wave, task S1 extraction) — every step `runPreview` always\n * performed regardless of `--html`, factored out so {@link buildDeckPreview}\n * below (and transitively `createServeServer`, `./serve.ts`) can reuse it\n * without re-threading `loadDeckTarget`/`applyDeckConfig`/`validateIr`/\n * `resolveLocalAssets` a second time. Resolves `target` exactly like\n * `runRender`/`runValidate`/`runAudit` (single IR file, deck project\n * directory, or bare deck name — {@link loadDeckTarget} above), then renders\n * every slide to SVG once (`svgs`, index-aligned with `ir.slides`) — the same\n * strings both `runPreview`'s per-slide `.svg` files and\n * {@link buildDeckAuditAndHtml}'s embedded `preview.html` copies come from,\n * so the two stay byte-identical by construction, not just because the\n * renderer is deterministic (the same guarantee `runPreview` documented\n * before this extraction).\n */\ninterface DeckRenderResult {\n ir: PptxIR\n svgs: string[]\n /** The deck directory (`isDir: true`) or the single IR file (`isDir:\n * false`) `target` resolved to — see {@link loadDeckTarget}'s own doc\n * comment on `resolvedTarget` for why this is threaded back. */\n resolvedTarget: string\n isDir: boolean\n normalized?: string[]\n}\n\nasync function renderDeckSlides(\n target: string,\n opts: { cwd?: string; themeFilePath?: string } = {},\n): Promise<DeckRenderResult> {\n const cwd = opts.cwd ?? process.cwd()\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const { raw, baseDir, isDir, resolvedTarget, workspaceAssetsDir } = await loadDeckTarget(target, cwd, projectHit, userHit)\n await applyDeckConfig(raw, { themeFilePath: opts.themeFilePath, cwd, projectHit, userHit })\n const v = validateIr(raw)\n if (!v.ok) throw new PptwiseError(`invalid IR:\\n${formatIssues(v.errors)}`)\n await resolveLocalAssets(v.ir!, baseDir, workspaceAssetsDir)\n const ir = v.ir!\n const svgs = ir.slides.map((_, i) => renderSlideSvg(ir, i))\n return { ir, svgs, resolvedTarget, isDir, normalized: v.normalized }\n}\n\n/**\n * Audit + HTML-build half of the pipeline (serve wave, task S1 extraction —\n * this is `runPreview`'s pre-extraction `opts.htmlOut` branch body, moved\n * here with no behavior change so both `--html` output and\n * `createServeServer`'s cached page are the exact same bytes for the exact\n * same deck state). Runs `auditDeck` (notes+preview wave, task 2) — but only\n * when the deck has no placeholder page. `auditDeck` itself silently skips a\n * placeholder (`AuditReport.pagesSkipped`, nothing to audit on an unfilled\n * page) — running it over a deck that has some would produce a *partial*\n * report that still looks complete (zero findings reads as \"clean\", not\n * \"some pages were never checked\"), which is worse than not running it at\n * all. The plan's contract is the simpler \"any placeholder present → skip\n * the whole overlay, one-line notice instead\" — implemented here as\n * `hasPlaceholder`, and threaded into `buildPreviewHtml` as either\n * `findings` + `checks` (clean run) or `auditNote` (skipped), never both.\n * `checks` (`AuditReport.checks`, `../svg/audit/deck-audit.ts`) rides along\n * with `findings` on every clean run, not just a partial/findings-only one —\n * `buildPreviewHtml` renders it as its own one-line summary regardless of\n * `findings.length`, so a deck that audited clean because nothing was wrong\n * stays visually distinct from one that audited clean because the pixel\n * pass never ran.\n */\nfunction buildDeckAuditAndHtml(\n ir: PptxIR,\n svgs: string[],\n): { html: string; findings: AuditFinding[]; checks?: AuditChecks } {\n const hasPlaceholder = ir.slides.some((slide) => slide.placeholder)\n const auditReport = hasPlaceholder ? undefined : auditDeck(ir)\n const findings = auditReport?.findings ?? []\n const html = buildPreviewHtml({\n title: ir.filename,\n slides: ir.slides.map((slide, i) => ({\n index: i,\n id: slide.id,\n type: slide.type,\n svg: svgs[i]!,\n placeholder: slide.placeholder,\n })),\n findings: findings.map((f) => ({ page: f.page, slideId: f.slideId, code: f.code, message: f.message })),\n auditNote: hasPlaceholder\n ? \"audit overlay skipped — deck has unfilled placeholder pages; fill every page and re-run `pptwise preview --html` to see audit findings\"\n : undefined,\n checks: auditReport?.checks,\n })\n return { html, findings, checks: auditReport?.checks }\n}\n\n/**\n * {@link renderDeckSlides} + {@link buildDeckAuditAndHtml} combined — the\n * full \"target → {html, findings, ...}\" preview build pipeline (serve wave,\n * task S1; spec-plan.md `.issues/2026-07-25-serve/spec-plan.md` §3 design\n * ruling 5: \"buildPreviewHtml 复用现状 ... 禁止 fork 一份 preview 构建逻辑\").\n * Two consumers: `runPreview`'s `opts.htmlOut` branch below (byte-identical\n * output to before this extraction — see that function's own doc comment),\n * and `createServeServer` (`./serve.ts`), which calls this once at startup\n * and again on every debounced `fs.watch` rebuild, caching `.html` in memory\n * for `GET /` and pushing an SSE `reload` once it succeeds. A thrown\n * `PptwiseError` (invalid IR, a mid-edit malformed JSON save, ...) propagates\n * straight out of this function either way — it is `createServeServer`'s job\n * to catch the *rebuild* case and turn it into an SSE `error` event instead\n * of letting it kill the server; the *first* call (before serve starts\n * listening) is deliberately allowed to reject the whole command, same\n * \"throw `PptwiseError` → CLI exit 1\" contract every other `run*` command\n * already has, since there is no previous-good HTML yet to keep serving.\n */\nexport interface DeckPreviewResult extends DeckRenderResult {\n html: string\n findings: AuditFinding[]\n checks?: AuditChecks\n}\n\nexport async function buildDeckPreview(\n target: string,\n opts: { cwd?: string; themeFilePath?: string } = {},\n): Promise<DeckPreviewResult> {\n const rendered = await renderDeckSlides(target, opts)\n const { html, findings, checks } = buildDeckAuditAndHtml(rendered.ir, rendered.svgs)\n return { ...rendered, html, findings, checks }\n}\n\n/**\n * `irPath` accepts a single IR/spec JSON file, a deck project directory, or\n * a bare deck name (same `loadDeckTarget` resolution `runRender` uses).\n * Preview never gates on placeholder pages either way (single-file or\n * dir-mode) — `renderSlideSvg` itself never calls the draft gate, spec §7:\n * preview always lets everything through — an agent iterating on a\n * partially-filled deck needs to see whatever page it just wrote without\n * every other still-empty page blocking it.\n *\n * Appends the same field-alias {@link normalizedNote} `runValidate`/\n * `runRender` print (W5 whole-branch review finding 3).\n *\n * Delegates the assemble/render/audit/HTML-build work to\n * {@link renderDeckSlides}/{@link buildDeckAuditAndHtml} (serve wave, task S1\n * extraction — see {@link buildDeckPreview}'s own doc comment for why); this\n * function's own job is now purely the CLI-facing shell around them —\n * writing each rendered SVG to `outDir`, conditionally writing\n * `preview.html`, and assembling the human-readable summary line. `outDir`\n * is optional (workspace-artifacts wave): omitted, the files land in\n * `<anchor>/.pptwise/<slug>/`, and matching `NNN-<type>.svg` leftovers from\n * a previous run of the same deck are pruned first. An explicit `-o` is\n * resolved against `cwd` and is never pruned — that directory may be\n * anything. The directory is only created once assemble/validate/render has\n * already succeeded (`renderDeckSlides` runs first) — a target that fails\n * to resolve or validate never leaves behind an empty directory it was\n * never able to fill, the same \"don't create output for a call that's about\n * to fail\" posture `runDisassemble`'s own path-traversal guard already\n * established elsewhere in this file.\n */\nexport async function runPreview(irPath: string, outDir?: string, opts: PreviewOptions = {}): Promise<string> {\n const cwd = opts.cwd ?? process.cwd()\n const { ir, svgs, normalized, isDir, resolvedTarget } = await renderDeckSlides(irPath, {\n cwd,\n themeFilePath: opts.themeFilePath,\n })\n // After render, not before (S1 review carry) — see this function's own doc comment.\n const extraNotes: string[] = []\n let resolvedOut: string\n if (outDir !== undefined) {\n resolvedOut = resolve(cwd, outDir)\n await mkdir(resolvedOut, { recursive: true })\n } else {\n const projectHit = await findConfig(cwd)\n const location = resolveWorkspaceLocation({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n target: resolvedTarget,\n isDir,\n })\n extraNotes.push(...(await prepareWorkspaceDir(location, { gitIgnore: opts.gitIgnore, runGit: opts.runGit })))\n await pruneRenderedSvgs(location.dir)\n resolvedOut = location.dir\n }\n const svgNames: string[] = []\n for (let i = 0; i < ir.slides.length; i++) {\n const name = `${String(i + 1).padStart(3, \"0\")}-${ir.slides[i]!.type}.svg`\n svgNames.push(name)\n await writeFile(join(resolvedOut, name), svgs[i]!)\n }\n const ok = `wrote ${ir.slides.length} SVG files to ${resolvedOut}`\n const notes: string[] = [...extraNotes]\n const aliasNote = normalizedNote(normalized)\n if (aliasNote) notes.push(aliasNote)\n if (opts.htmlOut) {\n const { html, findings, checks } = buildDeckAuditAndHtml(ir, svgs)\n const htmlPath = join(resolvedOut, \"preview.html\")\n await writeFile(htmlPath, html)\n\n // The machine-readable half of the same bundle (`./preview-manifest.ts`).\n // A harness with its own UI reads this and draws the deck however it\n // likes; one without a UI opens the HTML sitting next to it. Neither has\n // to re-implement the renderer, which is the only way there stays exactly\n // one rendering path.\n const hasPlaceholder = ir.slides.some((s) => s.placeholder)\n const manifest = buildPreviewManifest({\n title: ir.filename,\n pptwiseVersion: VERSION,\n width: CANVAS_W_PX,\n height: CANVAS_H_PX,\n slides: ir.slides.map((slide, i) => ({\n index: i,\n type: slide.type ?? \"content\",\n id: slide.id,\n placeholder: slide.placeholder,\n file: svgNames[i]!,\n })),\n findings: findings.map((f) => ({ page: f.page, code: f.code, message: f.message })),\n checks,\n auditNote: hasPlaceholder\n ? \"audit skipped — deck has unfilled placeholder pages\"\n : undefined,\n })\n const manifestPath = join(resolvedOut, \"manifest.json\")\n await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`)\n\n notes.push(`note: wrote self-contained preview to ${htmlPath}`)\n notes.push(`note: wrote machine-readable page manifest to ${manifestPath}`)\n if (findings.length > 0) {\n notes.push(`note: audit found ${findings.length} finding${findings.length === 1 ? \"\" : \"s\"} — see preview.html`)\n }\n }\n return notes.length > 0 ? `${ok}\\n${notes.join(\"\\n\")}` : ok\n}\n\nexport interface AssembleOptions {\n output?: string\n cwd?: string\n}\n\n/**\n * Rewrites every local (non-`data:`/non-`http(s)`) asset src so it keeps\n * resolving correctly when the assembled IR is written to `outDir`, a\n * different directory than the `deckDir` it was assembled from (`-o`\n * pointing outside the deck project, `runAssemble` below). `readDeckDir`'s\n * asset scan always produces a `deckDir`-relative src (`./deck-dir.ts`'s\n * `scanAssets` — always `assets/<file>`), so writing the IR anywhere else\n * unchanged would leave that src resolving against the *wrong* base the\n * next time this file is loaded (`loadDeckTarget`'s single-file branch\n * resolves relative asset srcs against the IR file's own directory, not\n * where it happened to be assembled from). Rebuilds `assets.images` rather\n * than mutating entries in place — the same \"never mutate a live IR's asset\n * map\" caution `readDeckDir` itself documents (`./deck-dir.ts`).\n */\nfunction withRewrittenAssetPaths(ir: PptxIR, deckDir: string, outDir: string): PptxIR {\n const images = Object.fromEntries(\n Object.entries(ir.assets.images).map(([id, asset]) => {\n if (asset.src.startsWith(\"data:\") || /^https?:\\/\\//.test(asset.src)) return [id, asset] as const\n return [id, { ...asset, src: relative(outDir, join(deckDir, asset.src)) }] as const\n }),\n )\n return { ...ir, assets: { images } }\n}\n\n/**\n * `pptwise assemble <dir|name>` (W5 task 5): resolve `target` (path or bare\n * deck name, `resolveDeckTarget`) → `readDeckDir` (spec + pages/ + assets/ →\n * IR, `./deck-dir.ts`) → write the assembled IR as pretty-printed JSON,\n * default `<deckDir>/deck.json` when `-o` is omitted. Deliberately does\n * *not* call `applyDeckConfig` — `assemble` materializes exactly what the\n * spec says plus each page's own auto-selected `layout` where the page file\n * left it implicit (`assembleDeck`'s own doc comment, W4 design decision\n * 10) — a portable IR file, self-contained down to which layout each page\n * will render with. Theme/style overrides are `validate`/`render`/\n * `preview`'s job (each already applies the four-layer chain whether given\n * this same directory or the `deck.json` this command just wrote).\n *\n * `target` must resolve to an actual directory: a target that exists but\n * names a file gets a friendly `expected a deck project directory` error\n * right here rather than reaching `readDeckDir` and failing deeper, with a\n * confusing `ENOTDIR` message, trying to read `<file>/deck.spec.json`. A\n * target that does not exist *at all* is deliberately let through to\n * `readDeckDir` unchanged — its own missing-spec-file error already names\n * the expected layout, strictly more helpful than this shorter message.\n *\n * `-o` resolves against `cwd` (the same fix `resolveDeckTarget` already\n * needed — see that function's own doc comment) rather than the real\n * `process.cwd()`, so a caller that threads a custom `cwd` gets the output\n * where it actually asked for it. When the resolved output directory is not\n * `deckDir` itself, every local asset src is rewritten\n * ({@link withRewrittenAssetPaths}) to stay correct from the new location —\n * otherwise `assets/logo.png` (correct relative to `deckDir`) would silently\n * fail to resolve from wherever `-o` actually put the file.\n *\n * When the spec omitted `seed`, `readDeckDir` (via `assembleDeck`) generates\n * one deterministically and reports it as `generatedSeed` — surfaced here as\n * a suggestion to add it back to `deck.spec.json` for revision stability\n * (spec §5's seed-generation semantics). Never written automatically:\n * `assembleDeck` stays a pure function with no fs side effects, and silently\n * rewriting a file the user did not ask this command to touch would be a\n * worse surprise than asking them to paste one line in.\n *\n * `materializedLayoutCount` (also from `assembleDeck`, unset when every page\n * already named its own `layout` or landed on the image-cover bypass) gets\n * its own one-line note the same way, listed after the seed note when both\n * apply — purely informational, telling the caller how many pages just had\n * an auto-pick baked into `deck.json` rather than leaving them to notice by\n * diffing the file. The base summary line's `(N slides, M placeholders)`\n * parenthetical itself stays untouched by either note (`scripts/e2e.mts`\n * checks it by exact substring) — both notes are strictly additional lines.\n */\nexport async function runAssemble(target: string, opts: AssembleOptions = {}): Promise<string> {\n const cwd = opts.cwd ?? process.cwd()\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const dir = await resolveDeckTarget(target, resolveDecksDirSource(projectHit, userHit), cwd)\n if ((await pathExists(dir)) && !(await isDeckDirectory(dir))) {\n throw new PptwiseError(`expected a deck project directory: ${dir}`)\n }\n // Same deck-local theme.json auto-load `loadDeckTarget` performs (brand-\n // extract wave) — assemble bypasses that helper but hits the same\n // spec-level installed-theme gate inside readDeckDir's assemble step.\n if (await isDeckDirectory(dir)) await registerDeckThemeFile(dir)\n const { ir, generatedSeed, materializedLayoutCount, deckDir } = await readDeckDir(dir)\n const outPath = opts.output ? resolve(cwd, opts.output) : join(deckDir, \"deck.json\")\n const outDir = dirname(outPath)\n const outIr = outDir === deckDir ? ir : withRewrittenAssetPaths(ir, deckDir, outDir)\n await mkdir(outDir, { recursive: true })\n await writeFile(outPath, JSON.stringify(outIr, null, 2) + \"\\n\")\n const placeholderCount = outIr.slides.filter((s) => s.placeholder).length\n const summary = `wrote ${outPath} (${outIr.slides.length} slides, ${placeholderCount} placeholder${placeholderCount === 1 ? \"\" : \"s\"})`\n const notes: string[] = []\n if (generatedSeed !== undefined) {\n notes.push(`note: generated seed ${generatedSeed} — add \"seed\": ${generatedSeed} to deck.spec.json for revision stability`)\n }\n if (materializedLayoutCount !== undefined) {\n notes.push(\n `note: ${materializedLayoutCount} layout${materializedLayoutCount === 1 ? \"\" : \"s\"} auto-selected into deck.json — pin \"layout\" in a page file to lock one`,\n )\n }\n return [summary, ...notes].join(\"\\n\")\n}\n\n/**\n * `pptwise disassemble <deck.json> -o <dir>` (W5 task 5): the CLI shell for\n * `disassembleDeck` (`../spec/assemble.ts`) — read + validate an IR file the\n * same way `runRender`/`runValidate` do, then write `deck.spec.json` +\n * `pages/<id>.json` for every non-placeholder page. Pretty-printed. Key\n * order is already stable because `disassembleDeck` builds every object\n * with the same fixed field order on every call, not by iterating the\n * input, so there is no separate \"stable stringify\" step to write. Refuses\n * to overwrite an existing `deck.spec.json` — same `wx`-flag EEXIST guard as\n * `runInit`'s config scaffold — so re-running this command never silently\n * clobbers a deck project someone has since started filling in. Page files\n * are freely (re)written since they only exist because this same command\n * produced them, and written concurrently (`Promise.all`) since each is an\n * independent file.\n *\n * Also materializes `assets/` ({@link writeDeckAssets}, `./deck-dir.ts`) —\n * `disassembleDeck` itself never touches `ir.assets.images` (see that\n * function's own doc comment for the full accounting), so this is the step\n * that actually closes the loop: without it, an image deck disassembles\n * with every `asset_id` reference intact but no bytes behind it, then\n * re-assembles and renders with the image silently missing.\n *\n * The summary never claims to have written a directory it did not create:\n * `pagesDir`/`assetsDir` are only named when at least one page/asset file\n * actually landed there (a spec-only deck with every slide a placeholder,\n * or an assetless deck, leaves either directory unwritten).\n *\n * Every page id is checked with {@link assertSafeFileSegment} (`./deck-dir.ts`)\n * before *any* file is written — not just ahead of `pages/<id>.json` (W5\n * whole-branch review finding 1, CRITICAL — CWE-22), but ahead of\n * `deck.spec.json` too (post-v0.3 W8 fix round, backlog item 8,\n * `.issues/notes/engineering-history.md` #8 — the check originally\n * ran after the spec write): `slide.id` is an unrestricted string at the\n * schema layer, so a hand-authored IR could otherwise set one to\n * `\"../../../../escape\"` and write outside `outDir`. `writeDeckAssets` below\n * (`./deck-dir.ts`) carries the matching check for asset keys, inside\n * `writeOneAsset` — that check stays per-asset rather than also moving\n * ahead of the spec write, since an unsafe id is only one of several ways\n * `writeOneAsset` can fail (malformed data URI, URL asset, unreadable local\n * file) and the others can't be front-loaded without doing the write itself.\n *\n * Failure rollback (post-v0.3 W8 fix round, backlog item 8): once\n * `deck.spec.json` is written, this call is the sole owner of that file for\n * the rest of its own execution, so any failure in the page/asset writes\n * below deletes it before rethrowing — a failed run never leaves a\n * `deck.spec.json` behind that doesn't match what actually landed in\n * `pages/`/`assets/`. The `wx` no-overwrite guard above still runs first and\n * throws before this rollback scope is ever entered, so a pre-existing\n * `deck.spec.json` this call did not itself create is never at risk of\n * being deleted — deleting only ever targets the file this same invocation\n * just wrote.\n */\nexport async function runDisassemble(irPath: string, outDir: string): Promise<string> {\n const raw = await loadIrFile(irPath)\n const v = validateIr(raw)\n if (!v.ok) throw new PptwiseError(`invalid IR:\\n${formatIssues(v.errors)}`)\n const { spec, pages } = disassembleDeck(v.ir!)\n\n // W5 whole-branch review finding 1 (CRITICAL, CWE-22): `id` is `slide.id`\n // off the parsed input IR (`disassembleDeck` passes a bare `slide.id`\n // through unchanged when present, `../spec/assemble.ts`) — unrestricted at\n // the schema layer, so an id like `\"../../../../escape\"` would otherwise\n // write outside `outDir`. Post-v0.3 W8 fix round (backlog item 8): checked\n // here, ahead of every write including `deck.spec.json` itself, so a\n // single unsafe id fails the whole call with nothing written at all,\n // rather than leaving a `deck.spec.json` that then needs rolling back.\n const ids = Object.keys(pages)\n for (const id of ids) assertSafeFileSegment(id, \"slide id\")\n\n const specPath = join(outDir, \"deck.spec.json\")\n await mkdir(outDir, { recursive: true })\n try {\n await writeFile(specPath, JSON.stringify(spec, null, 2) + \"\\n\", { flag: \"wx\" })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EEXIST\") {\n throw new PptwiseError(`${specPath} already exists — refusing to overwrite an existing deck project`)\n }\n throw e\n }\n\n // From here on `specPath` is a file this call just created (the `wx` flag\n // above guarantees no pre-existing file survived to this point), so it is\n // safe to delete on any failure below — backlog item 8: a mid-way failure\n // used to leave `deck.spec.json` on disk with no matching pages/assets,\n // misrepresenting the deck project as already, successfully disassembled.\n const pagesDir = join(outDir, \"pages\")\n try {\n if (ids.length > 0) {\n await mkdir(pagesDir, { recursive: true })\n await Promise.all(\n ids.map((id) => {\n const content: PageContent = pages[id]!\n return writeFile(join(pagesDir, `${id}.json`), JSON.stringify(content, null, 2) + \"\\n\")\n }),\n )\n }\n\n const { count: assetCount, assetsDir } = await writeDeckAssets(\n v.ir!.assets.images,\n outDir,\n dirname(resolve(irPath)),\n )\n\n const pagesNote =\n ids.length > 0\n ? `${ids.length} page file${ids.length === 1 ? \"\" : \"s\"} to ${pagesDir}`\n : \"no pages (every slide was a placeholder)\"\n const assetsNote = assetCount > 0 ? `, and ${assetCount} asset file${assetCount === 1 ? \"\" : \"s\"} to ${assetsDir}` : \"\"\n return `wrote ${specPath}, ${pagesNote}${assetsNote}`\n } catch (e) {\n // Best-effort cleanup: a failure to delete the spec file must never mask\n // the real failure `e` below, so its own error is swallowed, not thrown.\n await rm(specPath, { force: true }).catch(() => {})\n throw e\n }\n}\n\n// ── migrate ──────────────────────────────────────────────────────────────\n\n/**\n * `pptwise migrate <input> -o <output>` (spec §9.1/§9.2/§9.3, vocabulary-v4\n * rename, task 2): the one deterministic conversion surface for both\n * artifacts this rename touches. Dispatches purely on whether `<input>`\n * resolves to a directory ({@link isDeckDirectory}) — the same signal every\n * other deck-accepting command already uses to branch between single-file\n * and deck-project-directory mode:\n *\n * - a directory containing `deck.plan.json` → {@link runMigrateDeckDir}:\n * rewrites it to `deck.spec.json` per spec §9.2's field mapping\n * ({@link migrateDeckPlanToSpec}, `../spec/migrate.ts`), written to\n * `<output>` (a directory — `<output>/deck.spec.json`).\n * - a file → {@link runMigrateIrFile}: an IR v3 document (`version: \"3\"`)\n * wraps {@link migrateIrV3ToV4} (`../ir/migrate.ts`). A v4 IR or\n * spec-shaped file that still carries the old `chrome` field is rewritten\n * via {@link migrateChromeToBranding}, a leftover `bloom` theme id is\n * relocated onto `classroom` via {@link migrateBloomToClassroom}, and a\n * leftover `logo_wall` component is rewritten to `image_grid` via\n * {@link migrateLogoWallToImageGrid}, and a leftover `banner-heading`\n * layout pin is rewritten to `two-column` via\n * {@link migrateBannerHeadingToTwoColumn}. IR v2\n * is explicitly not accepted here (spec §15.3: \"v2 无真实用户\" —\n * `pptwise migrate` does not convert v2, `validateIr`'s own v2\n * hard-reject message carries the full v2→v4 combined mapping for a\n * caller who needs to convert one by hand).\n *\n * Both branches never overwrite `<output>` — a pre-existing file at the\n * resolved output path is a hard `PptwiseError`, the same `wx`-flag EEXIST\n * guard `runDisassemble`/`runInit` already use elsewhere in this file (spec\n * §9.2: \"迁移工具必须默认写到新目标,不覆盖原文件\"). Neither branch runs a\n * model or reinterprets content — both are thin CLI shells over an\n * already-pure mapping function, per spec §9.3: \"只做已声明的结构映射,不\n * 运行模型,不重写内容,不重新选择 layout\".\n */\nexport async function runMigrate(input: string, output: string, cwd = process.cwd()): Promise<string> {\n const resolvedInput = resolve(cwd, input)\n if (await isDeckDirectory(resolvedInput)) {\n return runMigrateDeckDir(resolvedInput, output, cwd)\n }\n return runMigrateIrFile(resolvedInput, output, cwd)\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction needsChromeRewrite(raw: Record<string, unknown>): boolean {\n return Object.hasOwn(raw, \"chrome\")\n}\n\nfunction needsBloomRewrite(raw: Record<string, unknown>): boolean {\n const theme = raw.theme\n if (theme === \"bloom\") return true\n return isPlainRecord(theme) && theme.id === \"bloom\"\n}\n\nfunction componentListHasLogoWall(components: unknown): boolean {\n return Array.isArray(components) && components.some((component) => isPlainRecord(component) && component.type === \"logo_wall\")\n}\n\nfunction needsLogoWallRewrite(raw: Record<string, unknown>): boolean {\n if (componentListHasLogoWall(raw.components)) return true\n if (!Array.isArray(raw.slides)) return false\n return raw.slides.some((slide) => isPlainRecord(slide) && componentListHasLogoWall(slide.components))\n}\n\nfunction recordHasBannerHeadingPin(obj: Record<string, unknown>): boolean {\n return obj.layout === \"banner-heading\" || obj.focus === \"banner-heading\"\n}\n\nfunction needsBannerHeadingRewrite(raw: Record<string, unknown>): boolean {\n if (recordHasBannerHeadingPin(raw)) return true\n if (Array.isArray(raw.slides) && raw.slides.some((slide) => isPlainRecord(slide) && recordHasBannerHeadingPin(slide))) {\n return true\n }\n if (Array.isArray(raw.pages) && raw.pages.some((page) => isPlainRecord(page) && recordHasBannerHeadingPin(page))) {\n return true\n }\n return false\n}\n\nfunction migrateRewriteNote(chrome: boolean, bloom: boolean, logoWall = false, bannerHeading = false): string {\n const parts: string[] = []\n if (chrome) parts.push(\"renamed chrome → branding\")\n if (bloom) parts.push(\"relocated bloom → classroom\")\n if (logoWall) parts.push(\"rewrote logo_wall → image_grid\")\n if (bannerHeading) parts.push(\"rewrote banner-heading → two-column\")\n return parts.join(\", \")\n}\n\nfunction applyV4LeftoverRewrites(raw: Record<string, unknown>): unknown {\n return migrateBannerHeadingToTwoColumn(\n migrateLogoWallToImageGrid(migrateBloomToClassroom(migrateChromeToBranding(raw))),\n )\n}\n\nasync function listPageJsonNames(dir: string): Promise<string[]> {\n const pagesDir = join(dir, PAGES_DIRNAME)\n try {\n const entries = await readdir(pagesDir)\n return entries.filter((name) => name.endsWith(\".json\")).sort()\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return []\n throw e\n }\n}\n\nasync function rewriteLeftoverPages(\n dir: string,\n outDir: string,\n): Promise<{ paths: string[]; logoWall: boolean; bannerHeading: boolean }> {\n const names = await listPageJsonNames(dir)\n const written: string[] = []\n let logoWall = false\n let bannerHeading = false\n for (const name of names) {\n const src = join(dir, PAGES_DIRNAME, name)\n const raw = await loadIrFile(src, \"page\")\n if (!isPlainRecord(raw)) continue\n const hasLogo = needsLogoWallRewrite(raw)\n const hasBanner = needsBannerHeadingRewrite(raw)\n if (!hasLogo && !hasBanner) continue\n if (hasLogo) logoWall = true\n if (hasBanner) bannerHeading = true\n const dest = join(outDir, PAGES_DIRNAME, name)\n await writeMigratedJson(dest, migrateBannerHeadingToTwoColumn(migrateLogoWallToImageGrid(raw)))\n written.push(dest)\n }\n return { paths: written, logoWall, bannerHeading }\n}\n\n/** Write JSON with the existing `wx` never-overwrite rule shared by migrate legs. */\nasync function writeMigratedJson(outPath: string, data: unknown): Promise<void> {\n await mkdir(dirname(outPath), { recursive: true })\n try {\n await writeFile(outPath, JSON.stringify(data, null, 2) + \"\\n\", { flag: \"wx\" })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EEXIST\") {\n throw new PptwiseError(`${outPath} already exists — refusing to overwrite, delete it first or choose a different -o`)\n }\n throw e\n }\n}\n\n/**\n * Deck-project-directory leg of {@link runMigrate}: reads `deck.plan.json`\n * out of `dir` (`loadIrFile`'s generic read-plus-parse, `./load-ir.ts` —\n * same helper `runSpecValidate` above uses, \"plan\" naming its own failure\n * messages), maps it through {@link migrateDeckPlanToSpec}\n * (`../spec/migrate.ts`, spec §9.2's field mapping), and writes the result\n * to `<output>/deck.spec.json`. `assets/*` are untouched. Leftover\n * `logo_wall` components in `pages/*.json` are rewritten to `image_grid`,\n * leftover `banner-heading` pins to `two-column`, at\n * `<output>/pages/<filename>` (source pages stay put). Spec §9.2's\n * field mapping still only touches `deck.plan.json`'s own top-level\n * `scenario` field and each page's `rhythm` field.\n *\n * Deliberately does not delete or rename the source `deck.plan.json` —\n * spec §9.2: \"不覆盖原文件\" applies to the migration direction generally,\n * and leaving the old file in place is what lets {@link readSpecFile}-style\n * dual-file detection (`../cli/deck-dir.ts`) catch a half-finished migration\n * (both files present) instead of one command silently deciding the old\n * file is now garbage. The success message tells the caller to delete it\n * once they have confirmed the new file is correct.\n *\n * Checks for `deck.plan.json` up front (task 3, routed from task 2's\n * review) instead of letting a missing file fall through to\n * `loadIrFile`'s generic \"cannot read plan file\" — a directory that has\n * already been migrated (a `deck.spec.json` sitting there with no\n * `deck.plan.json` left to convert, the plan file having since been\n * deleted per this function's own success message) gets a dedicated\n * \"already migrated\" error instead of a message that reads like the\n * directory was never a deck project at all. A directory with neither file\n * still reaches `loadIrFile`'s generic error — this function has no more\n * specific diagnosis to offer than that one already gives.\n *\n * A directory that has only `deck.spec.json` (no plan) still carrying the\n * old `chrome` field is rewritten via {@link migrateChromeToBranding}, a\n * leftover `bloom` theme id is relocated onto `classroom` via\n * {@link migrateBloomToClassroom}, and leftover `logo_wall` components in\n * `pages/*.json` are rewritten to `image_grid` via\n * {@link migrateLogoWallToImageGrid}. Spec rewrites land at\n * `<output>/deck.spec.json`. Page rewrites land at\n * `<output>/pages/<filename>`. Same-dir write keeps the `wx`\n * never-overwrite rule. Dual-source hard-errors. Neither chrome-to-rename\n * nor bloom nor leftover logo_wall left means already migrated.\n */\nasync function runMigrateDeckDir(dir: string, output: string, cwd: string): Promise<string> {\n const planPath = join(dir, PLAN_FILENAME)\n const sourceSpecPath = join(dir, SPEC_FILENAME)\n const outDir = resolve(cwd, output)\n const specPath = join(outDir, SPEC_FILENAME)\n if (!(await pathExists(planPath)) && (await pathExists(sourceSpecPath))) {\n const raw = await loadIrFile(sourceSpecPath, \"spec\")\n const specNeeds =\n isPlainRecord(raw) &&\n (needsChromeRewrite(raw) || needsBloomRewrite(raw) || needsBannerHeadingRewrite(raw))\n const pages = await rewriteLeftoverPages(dir, outDir)\n if (specNeeds && isPlainRecord(raw)) {\n const chrome = needsChromeRewrite(raw)\n const bloom = needsBloomRewrite(raw)\n const bannerHeading = needsBannerHeadingRewrite(raw)\n const migrated = applyV4LeftoverRewrites(raw)\n await writeMigratedJson(specPath, migrated)\n const specNote = `wrote ${specPath} (${migrateRewriteNote(chrome, bloom, false, bannerHeading)})`\n if (pages.paths.length === 0) return specNote\n return `${specNote}, wrote ${pages.paths.length === 1 ? pages.paths[0] : join(outDir, PAGES_DIRNAME)} (${migrateRewriteNote(false, false, pages.logoWall, pages.bannerHeading)})`\n }\n if (pages.paths.length > 0) {\n const target = pages.paths.length === 1 ? pages.paths[0] : join(outDir, PAGES_DIRNAME)\n return `wrote ${target} (${migrateRewriteNote(false, false, pages.logoWall, pages.bannerHeading)})`\n }\n throw new PptwiseError(\n `${dir} has ${SPEC_FILENAME} but no ${PLAN_FILENAME} — this deck project is already migrated, nothing to do`,\n )\n }\n const raw = await loadIrFile(planPath, \"plan\")\n const migrated = migrateDeckPlanToSpec(raw)\n await writeMigratedJson(specPath, migrated)\n const pages = await rewriteLeftoverPages(dir, outDir)\n const specNote = `wrote ${specPath} — run \\`pptwise spec validate ${specPath}\\` to confirm it, then delete ${planPath} (a directory with both files present is rejected)`\n if (pages.paths.length === 0) return specNote\n return `${specNote}, wrote ${pages.paths.length === 1 ? pages.paths[0] : join(outDir, PAGES_DIRNAME)} (${migrateRewriteNote(false, false, pages.logoWall, pages.bannerHeading)})`\n}\n\n/**\n * Single-file leg of {@link runMigrate}: an explicit `version: \"3\"` is the\n * IR v3 → v4 path (spec §9.3). `version: \"2\"` gets its own message pointing\n * at `validateIr`'s existing combined v2→v4 mapping rather than silently\n * routing it through the v3 vocabulary as a stepping stone (spec §15.3:\n * \"v2 无真实用户\", \"`pptwise migrate` 只支持 v3→v4,不接 v2\"). A v4 IR or\n * spec-shaped file that still carries the old `chrome` field is rewritten\n * via {@link migrateChromeToBranding}, a leftover `bloom` theme id is\n * relocated onto `classroom` via {@link migrateBloomToClassroom}, and a\n * leftover `logo_wall` component is rewritten to `image_grid` via\n * {@link migrateLogoWallToImageGrid}, and a leftover `banner-heading`\n * layout pin is rewritten to `two-column` via\n * {@link migrateBannerHeadingToTwoColumn}. Anything else is rejected with a\n * message naming what this command does accept.\n */\nasync function runMigrateIrFile(filePath: string, output: string, cwd: string): Promise<string> {\n const raw = await loadIrFile(filePath)\n const version = typeof raw === \"object\" && raw !== null ? (raw as Record<string, unknown>).version : undefined\n if (version === \"2\") {\n throw new PptwiseError(\n \"pptwise migrate does not support IR v2 (spec §15.3: v2 has no real users) — run `pptwise validate` on the v2 file to see the full v2→v4 combined field mapping and rewrite it by hand\",\n )\n }\n const outPath = resolve(cwd, output)\n if (version === \"3\") {\n // PptxIRV3Schema reuses v4 SlideSchema, so a leftover logo_wall would\n // fail parse after the union drops the type. Rewrite it on the raw\n // object first, then parse, then the v3→v4 field map (which also\n // relocates leftover banner-heading pins).\n const pre = isPlainRecord(raw) ? migrateLogoWallToImageGrid(raw) : raw\n const parsed = PptxIRV3Schema.safeParse(pre)\n if (!parsed.success) {\n const detail = parsed.error.issues.map((issue) => `${issue.path.join(\".\") || \"(root)\"}: ${issue.message}`).join(\"\\n\")\n throw new PptwiseError(`invalid IR v3 file ${filePath}:\\n${detail}`)\n }\n const migrated = migrateIrV3ToV4(parsed.data)\n await writeMigratedJson(outPath, migrated)\n return `wrote ${outPath} (migrated IR v3 → v4)`\n }\n if (\n isPlainRecord(raw) &&\n (needsChromeRewrite(raw) || needsBloomRewrite(raw) || needsLogoWallRewrite(raw) || needsBannerHeadingRewrite(raw))\n ) {\n const chrome = needsChromeRewrite(raw)\n const bloom = needsBloomRewrite(raw)\n const logoWall = needsLogoWallRewrite(raw)\n const bannerHeading = needsBannerHeadingRewrite(raw)\n const migrated = applyV4LeftoverRewrites(raw)\n await writeMigratedJson(outPath, migrated)\n return `wrote ${outPath} (${migrateRewriteNote(chrome, bloom, logoWall, bannerHeading)})`\n }\n throw new PptwiseError(\n `pptwise migrate converts an IR v3 file (version: \"3\"), a v4 IR or deck spec still carrying the old chrome field (renamed to branding), the removed bloom theme id (relocated to classroom), a leftover logo_wall component (rewritten to image_grid), or a leftover banner-heading layout pin (rewritten to two-column), or a deck project directory containing ${PLAN_FILENAME} — got version ${JSON.stringify(version)} in ${filePath} with nothing to migrate`,\n )\n}\n","import { readFile } from \"node:fs/promises\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { z } from \"zod\"\nimport { PptwiseError } from \"../errors\"\nimport { StyleOverrideSchema } from \"../ir\"\nimport { userConfigPath } from \"./home\"\nimport { ImagesConfigSchema } from \"./image-config\"\n\n/**\n * Project-level deck defaults. Precedence (spec §7's four-layer chain, W5\n * task 5): CLI flag > project config (this schema, cwd walk-up) > user\n * config (`UserConfigSchema` below) > whatever the artifact itself already\n * carries (an authored IR's own `theme`, or the schema's own \"consulting\"\n * default when nothing anywhere sets one) — see `commands.ts`'s\n * `applyDeckConfig` for where all four layers actually get merged. `theme`\n * is kept an open string at this schema layer (mirrors `ThemeSchema` in\n * `ir/index.ts`) on purpose: `readConfigFile` below no longer checks it\n * against the installed set at read time — a config file's theme value\n * might sit behind a CLI flag or another layer that never actually gets\n * used, so rejecting it here would hard-fail a command over a value that\n * was never going to apply. `applyDeckConfig` runs that check once, at\n * resolution time, against whichever layer's value actually wins the chain\n * — the same \"unknown → PptwiseError with the available list\" UX as\n * `validateIr`, just applied to the resolved value instead of unconditionally\n * to every layer.\n *\n * `decksDir` (W5 task 6, spec §7: a team that wants deck project\n * directories checked into the repo instead of living under\n * `~/.pptwise/decks` declares it here): a relative value resolves against\n * *this config file's own directory* (wherever `findConfig`'s cwd walk-up\n * found it) — never the CLI's cwd, and never `pptwiseHome()`. Wins over the\n * user config's own `decksDir` (`UserConfigSchema` below) when both are\n * set, same project-beats-user precedence as `theme`/`style` above. The two\n * layers resolve against different bases, so this schema alone can't\n * express the final answer — `commands.ts`'s `resolveDecksDirSource`\n * computes the already-resolved absolute path before handing it down to\n * `./deck-dir.ts`'s `resolveDeckTarget` / `./home.ts`'s `decksRoot`, neither\n * of which knows there are two possible bases, only the final one.\n *\n * `outDir` (workspace-artifacts wave): where `render`/`preview` write when\n * the caller passes no `-o`. Default `.pptwise` under this config file's own\n * directory (`../cli/workspace.ts`'s `WORKSPACE_DIRNAME`); a relative value\n * here resolves against that same directory, an absolute one passes through.\n * Setting it at all is also the opt-out from the automatic git-exclude line\n * — a project that names its own artifact directory has already decided how\n * that directory is tracked (`prepareWorkspaceDir`). Project layer only, on\n * purpose: an artifact root is a property of *this project's* working tree,\n * not of the user's identity, so it deliberately has no counterpart in\n * {@link UserConfigSchema} below — a user-level `outDir` would collapse every\n * project's artifacts into one directory.\n */\nconst ConfigSchema = z\n .object({\n theme: z.string().optional(),\n style: StyleOverrideSchema.optional(),\n decksDir: z.string().optional(),\n outDir: z.string().optional(),\n })\n .strict()\n\nexport type PptwiseConfig = z.infer<typeof ConfigSchema>\n\n/**\n * User-level config schema (spec §7's four-layer chain — the layer between\n * project config and the artifact's own value): the same three deck-default\n * fields as {@link ConfigSchema} (`theme`/`style`/`decksDir`), plus optional\n * `images` (Pexels/Pixabay keys and Openverse OAuth for stock-photo search). `outDir` is\n * deliberately absent — an artifact root belongs to this working tree, not\n * to the user's identity (see {@link ConfigSchema}'s own `outDir` comment).\n * `images` is user-layer only: project {@link ConfigSchema} rejects it so a\n * repo file cannot carry API keys.\n * `decksDir` is no longer project-config-free as of W5 task 6 (see\n * {@link ConfigSchema}'s own doc comment on that field), but the two layers\n * still resolve it against different bases: this user layer always resolves\n * against `pptwiseHome()` (`./home.ts`'s `decksRoot`, this layer's one fixed\n * location), the project layer against the project config file's own\n * directory. Declared as its own flat object literal rather than\n * `ConfigSchema.extend(...)` — a shape this small is not worth taking on\n * zod's extend-then-restrict chaining, and it keeps both schemas readable\n * independently.\n *\n * `decksDir`: a relative value resolves against this config file's own\n * directory (`./home.ts`'s `pptwiseHome()` — the only directory a user\n * config can ever live in, see `decksRoot`), never the CLI's cwd. No tilde\n * expansion — a literal `~/decks` is the literal relative path segment\n * `~/decks` under that base, not the home directory. The resulting (almost\n * certainly missing) directory surfaces through whatever downstream error\n * reads it, same as any other bad path.\n */\nconst UserConfigSchema = z\n .object({\n theme: z.string().optional(),\n style: StyleOverrideSchema.optional(),\n decksDir: z.string().optional(),\n images: ImagesConfigSchema.optional(),\n })\n .strict()\n\nexport type UserPptwiseConfig = z.infer<typeof UserConfigSchema>\n\nexport const CONFIG_FILENAME = \"pptwise.config.json\"\nexport const LEGACY_CONFIG_FILENAMES = [\"pptpress.config.json\", \"pptfast.config.json\"] as const\n\n/**\n * Shared read+parse+validate body for both config layers (project and user)\n * — same failure posture either way: a missing file is `null` (\"fine, no\n * config at this level\"), invalid JSON or a failed schema parse is a hard\n * {@link PptwiseError} naming `path`. Deliberately does *not* check `theme`\n * against the installed set here — see {@link ConfigSchema}'s own doc\n * comment for why that moved to `applyDeckConfig` (`../cli/commands.ts`) at\n * resolution time instead, applied only to whichever layer's value actually\n * wins the four-layer chain.\n */\nasync function readConfigFile<T>(\n path: string,\n schema: z.ZodType<T>,\n): Promise<{ path: string; config: T } | null> {\n let text: string\n try {\n text = await readFile(path, \"utf8\")\n } catch {\n return null // no config at this level\n }\n let raw: unknown\n try {\n raw = JSON.parse(text) as unknown\n } catch (e) {\n throw new PptwiseError(`${path} is not valid JSON: ${(e as Error).message}`)\n }\n const r = schema.safeParse(raw)\n if (!r.success) {\n const detail = r.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"\\n\")\n throw new PptwiseError(`invalid ${path}:\\n${detail}`)\n }\n return { path, config: r.data }\n}\n\n/** Walk from startDir up to the filesystem root looking for pptwise.config.json,\n * then pptpress.config.json, then pptfast.config.json in the same directory.\n * New name wins when more than one exists. Invalid config is a hard error\n * (with the file path in the message), never silently ignored. */\nexport async function findConfig(\n startDir: string,\n): Promise<{ path: string; config: PptwiseConfig } | null> {\n let dir = resolve(startDir)\n for (;;) {\n const hit = await readConfigFile(join(dir, CONFIG_FILENAME), ConfigSchema)\n if (hit) return hit\n for (const name of LEGACY_CONFIG_FILENAMES) {\n const legacy = await readConfigFile(join(dir, name), ConfigSchema)\n if (legacy) return legacy\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * User-level config (spec §7's four-layer chain, the layer below project\n * config): a single fixed path (`userConfigPath()`, `./home.ts` —\n * `$PPTWISE_HOME` or `~/.pptwise`), no cwd walk-up — there is exactly one\n * user config, unlike project config which can live at any ancestor of cwd.\n * Same missing/invalid posture as {@link findConfig}: missing file is fine\n * (`null`), invalid JSON or schema is a hard {@link PptwiseError} with the\n * path.\n */\nexport async function findUserConfig(): Promise<{ path: string; config: UserPptwiseConfig } | null> {\n return readConfigFile(userConfigPath(), UserConfigSchema)\n}\n","import { cpSync, existsSync, realpathSync, renameSync, rmSync } from \"node:fs\"\nimport { homedir as osHomedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { resolveProductEnv } from \"./product-env\"\n\nexport const HOME_DIRNAME = \".pptwise\"\nexport const LEGACY_HOME_DIRNAMES = [\".pptpress\", \".pptfast\"] as const\n\nexport interface PptwiseHomeOpts {\n /** Injectable so tests never touch the real `~/.pptwise` or leftover homes. */\n homedir?: () => string\n env?: NodeJS.ProcessEnv\n}\n\n/**\n * Root directory for pptwise's user-level state — deck project defaults\n * (`decksRoot`) and the user config file (`userConfigPath`), spec §7's\n * storage-policy decision. `PPTWISE_HOME` overrides it wholesale (CI /\n * containers). `PPTPRESS_HOME` and `PPTFAST_HOME` remain legacy aliases\n * (warn once when one actually supplies the value). Empty string counts as\n * unset.\n *\n * Otherwise a single predictable dotdir under the user's home, the same\n * posture as `.ssh`/`.npmrc`/`.aws`/`~/.claude` — deliberately *not* the\n * per-OS XDG/AppData split an `env-paths`-style helper would give: deck\n * project directories are large working files an agent produces, not\n * roaming-synced app config, and this tool's users (developers and agents)\n * benefit more from one predictable path than from OS-idiomatic placement.\n * Read fresh on every call (never cached) — `PPTWISE_HOME` is meant to be\n * redirectable per-process (tests set it via `process.env` before calling).\n *\n * When no env is set, the default is `~/.pptwise`. If that directory does\n * not exist, copy from `~/.pptpress` when present, otherwise `~/.pptfast`,\n * via a temp sibling then `rename`, and leave the old directory in place.\n * The DSH plugin resolves its own preview root by the same rules\n * (`previewRoot`, dsh/preview-tool.js). Keep those copies in sync.\n */\nexport function pptwiseHome(opts: PptwiseHomeOpts = {}): string {\n const env = opts.env ?? process.env\n const fromEnv = resolveProductEnv(\"HOME\", env)\n if (fromEnv !== undefined) return fromEnv\n const home = (opts.homedir ?? osHomedir)()\n const next = join(home, HOME_DIRNAME)\n migrateLegacyHomes(home, next)\n return next\n}\n\nfunction migrateLegacyHomes(home: string, nextDir: string): void {\n if (existsSync(nextDir)) return\n for (const dirname of LEGACY_HOME_DIRNAMES) {\n const legacy = join(home, dirname)\n if (existsSync(legacy)) {\n copyLegacyHome(legacy, nextDir)\n return\n }\n }\n}\n\nfunction copyLegacyHome(legacyDir: string, nextDir: string): void {\n // realpath so a directory symlink is copied as a real tree. Default\n // `cpSync` would copy the link itself, and the two homes would share one\n // payload. Leave the old path (symlink or dir) in place.\n const source = realpathSync(legacyDir)\n const tmpDir = `${nextDir}.migrating`\n rmSync(tmpDir, { recursive: true, force: true })\n try {\n cpSync(source, tmpDir, { recursive: true })\n renameSync(tmpDir, nextDir)\n } catch (error) {\n try {\n rmSync(tmpDir, { recursive: true, force: true })\n } catch {\n // still throw the original copy/rename failure\n }\n throw error\n }\n}\n\n/**\n * Default parent directory for bare-name deck resolution\n * (`$PPTWISE_HOME/decks/<name>/`, `./deck-dir.ts`'s `resolveDeckTarget`).\n * `config` is deliberately a minimal structural shape (`{ decksDir?: string\n * }`), not `UserPptwiseConfig` itself — `./config.ts` already imports\n * `userConfigPath` from this module, so importing its type back here would\n * be circular. Redirecting `decksDir` is a user-identity concern (spec §7:\n * user-identity-class config belongs to the user layer) — a team that wants\n * deck projects tracked inside a repo instead reaches for project-level\n * `pptwise.config.json`, a separate, unrelated mechanism.\n *\n * A relative `decksDir` resolves against `pptwiseHome()` itself — the only\n * directory a user config file can ever live in (see `userConfigPath`\n * below) — never the CLI's cwd. An absolute value passes through unchanged\n * (`path.resolve`'s own semantics handle both in one call, no separate\n * `isAbsolute` branch needed). No tilde expansion: a literal `~/decks` is\n * one relative path segment, not shorthand for the home directory — see\n * `./config.ts`'s `UserConfigSchema` doc comment.\n */\nexport function decksRoot(config?: { decksDir?: string }, opts?: PptwiseHomeOpts): string {\n return resolve(pptwiseHome(opts), config?.decksDir ?? \"decks\")\n}\n\n/** Path to the user-level config file (theme/style defaults + `decksDir` redirect, spec §7's four-layer chain). */\nexport function userConfigPath(opts?: PptwiseHomeOpts): string {\n return join(pptwiseHome(opts), \"config.json\")\n}\n","/**\n * `PPTWISE_*` environment variables, with `PPTPRESS_*` and `PPTFAST_*` as\n * per-process aliases. The new name wins, then pptpress, then pptfast.\n * Empty string counts as unset. When an old name actually supplies the\n * value, one stderr warning per key per process.\n *\n * Never abbreviate the product to PPTP or PPTW.\n */\n\nexport const PRODUCT_ENV_PREFIX = \"PPTWISE_\"\nexport const LEGACY_ENV_PREFIXES = [\"PPTPRESS_\", \"PPTFAST_\"] as const\n\nconst warnedLegacyKeys = new Set<string>()\n\n/** Test-only: clear the per-process \"warned once\" set. */\nexport function resetProductEnvWarningsForTests(): void {\n warnedLegacyKeys.clear()\n}\n\nexport function productEnvName(suffix: string): string {\n return `${PRODUCT_ENV_PREFIX}${suffix}`\n}\n\nexport function legacyEnvNames(suffix: string): string[] {\n return LEGACY_ENV_PREFIXES.map((prefix) => `${prefix}${suffix}`)\n}\n\nfunction nonempty(value: string | undefined): string | undefined {\n return value === undefined || value === \"\" ? undefined : value\n}\n\nfunction warnLegacy(legacyKey: string, currentKey: string): void {\n if (warnedLegacyKeys.has(legacyKey)) return\n warnedLegacyKeys.add(legacyKey)\n process.stderr.write(`${legacyKey} is deprecated. Use ${currentKey} instead.\\n`)\n}\n\n/**\n * Look up `PPTWISE_<suffix>`, then `PPTPRESS_<suffix>`, then `PPTFAST_<suffix>`.\n * Consults the **passed** `env` object (tests pass a fake env).\n */\nexport function resolveProductEnv(suffix: string, env: NodeJS.ProcessEnv = process.env): string | undefined {\n const currentKey = productEnvName(suffix)\n const current = nonempty(env[currentKey])\n if (current !== undefined) return current\n for (const legacyKey of legacyEnvNames(suffix)) {\n const legacy = nonempty(env[legacyKey])\n if (legacy !== undefined) {\n warnLegacy(legacyKey, currentKey)\n return legacy\n }\n }\n return undefined\n}\n","/**\n * User-level stock-image credentials. Stored in `$PPTWISE_HOME/config.json`\n * under `images`, never in a project `pptwise.config.json`. Whole-source\n * per provider: if the file names `images.pexels` (even as `{}`), the env\n * var is ignored for Pexels. Same for Pixabay and Openverse. Never mix\n * env + file for one provider.\n */\nimport { chmodSync, lstatSync } from \"node:fs\"\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { z } from \"zod\"\nimport { PptwiseError } from \"../errors\"\nimport { pptwiseHome, userConfigPath } from \"./home\"\nimport { resolveProductEnv } from \"./product-env\"\n\nexport const PEXELS_ENV = \"PPTWISE_PEXELS_API_KEY\"\nexport const PIXABAY_ENV = \"PPTWISE_PIXABAY_API_KEY\"\nexport const OPENVERSE_CLIENT_ID_ENV = \"PPTWISE_OPENVERSE_CLIENT_ID\"\nexport const OPENVERSE_CLIENT_SECRET_ENV = \"PPTWISE_OPENVERSE_CLIENT_SECRET\"\n\nexport const ImageProviderConfigSchema = z\n .object({\n apiKey: z.string().optional(),\n })\n .strict()\n\nexport const OpenverseConfigSchema = z\n .object({\n clientId: z.string().optional(),\n clientSecret: z.string().optional(),\n })\n .strict()\n\nexport const GENERATOR_IDS = [\"grok\", \"codex\", \"antigravity\"] as const\nexport type GeneratorId = (typeof GENERATOR_IDS)[number]\nexport const DEFAULT_GENERATOR_ORDER: GeneratorId[] = [\"grok\", \"codex\", \"antigravity\"]\nexport const DEFAULT_GENERATOR_TIMEOUT_MS = 180000\n\nexport const GeneratorFlagsSchema = z.object({ enabled: z.boolean().optional() }).strict()\nexport const GeneratorsConfigSchema = z\n .object({\n grok: GeneratorFlagsSchema.optional(),\n codex: GeneratorFlagsSchema.optional(),\n antigravity: GeneratorFlagsSchema.optional(),\n order: z.array(z.enum(GENERATOR_IDS)).optional(),\n timeoutMs: z.number().int().positive().optional(),\n })\n .strict()\n\nexport const ImagesConfigSchema = z\n .object({\n pexels: ImageProviderConfigSchema.optional(),\n pixabay: ImageProviderConfigSchema.optional(),\n openverse: OpenverseConfigSchema.optional(),\n generators: GeneratorsConfigSchema.optional(),\n })\n .strict()\n\nexport type ImageApiKeyProviderId = \"pexels\" | \"pixabay\"\nexport type ImageProviderId = ImageApiKeyProviderId | \"openverse\" | GeneratorId\nexport type KeySource = \"file\" | \"env\"\n\nexport interface ImageUserConfig {\n images?: {\n pexels?: { apiKey?: string }\n pixabay?: { apiKey?: string }\n openverse?: { clientId?: string; clientSecret?: string }\n generators?: {\n grok?: { enabled?: boolean }\n codex?: { enabled?: boolean }\n antigravity?: { enabled?: boolean }\n order?: GeneratorId[]\n timeoutMs?: number\n }\n }\n}\n\nexport interface ResolvedImageKey {\n apiKey: string | undefined\n source: KeySource | null\n namedInFile: boolean\n}\n\nexport interface ResolvedOpenverse {\n clientId: string | undefined\n clientSecret: string | undefined\n source: KeySource | null\n namedInFile: boolean\n /** Both clientId and clientSecret resolved from the same source. */\n ready: boolean\n}\n\nexport interface ResolvedImageKeys {\n pexels: ResolvedImageKey\n pixabay: ResolvedImageKey\n openverse: ResolvedOpenverse\n}\n\nexport type PersistableConfigValue = string | boolean | number | string[]\n\nconst API_KEY_PROVIDERS: ImageApiKeyProviderId[] = [\"pexels\", \"pixabay\"]\nconst ENV_SUFFIX_BY_PROVIDER: Record<ImageApiKeyProviderId, string> = {\n pexels: \"PEXELS_API_KEY\",\n pixabay: \"PIXABAY_API_KEY\",\n}\n\nconst FORBIDDEN_SEGMENTS = new Set([\"__proto__\", \"constructor\", \"prototype\"])\n\nexport type CliValueKind = \"string\" | \"boolean\" | \"order\" | \"timeoutMs\"\n\nexport interface CliConfigKey {\n cliKey: string\n path: string[]\n omitValue: boolean\n secret: boolean\n kind: CliValueKind\n}\n\nconst CLI_KEYS: Record<string, CliConfigKey> = {\n \"pexels.apiKey\": {\n cliKey: \"pexels.apiKey\",\n path: [\"images\", \"pexels\", \"apiKey\"],\n omitValue: true,\n secret: true,\n kind: \"string\",\n },\n \"pixabay.apiKey\": {\n cliKey: \"pixabay.apiKey\",\n path: [\"images\", \"pixabay\", \"apiKey\"],\n omitValue: true,\n secret: true,\n kind: \"string\",\n },\n \"openverse.clientId\": {\n cliKey: \"openverse.clientId\",\n path: [\"images\", \"openverse\", \"clientId\"],\n omitValue: false,\n secret: true,\n kind: \"string\",\n },\n \"openverse.clientSecret\": {\n cliKey: \"openverse.clientSecret\",\n path: [\"images\", \"openverse\", \"clientSecret\"],\n omitValue: true,\n secret: true,\n kind: \"string\",\n },\n \"images.generators.grok.enabled\": {\n cliKey: \"images.generators.grok.enabled\",\n path: [\"images\", \"generators\", \"grok\", \"enabled\"],\n omitValue: false,\n secret: false,\n kind: \"boolean\",\n },\n \"images.generators.codex.enabled\": {\n cliKey: \"images.generators.codex.enabled\",\n path: [\"images\", \"generators\", \"codex\", \"enabled\"],\n omitValue: false,\n secret: false,\n kind: \"boolean\",\n },\n \"images.generators.antigravity.enabled\": {\n cliKey: \"images.generators.antigravity.enabled\",\n path: [\"images\", \"generators\", \"antigravity\", \"enabled\"],\n omitValue: false,\n secret: false,\n kind: \"boolean\",\n },\n \"images.generators.order\": {\n cliKey: \"images.generators.order\",\n path: [\"images\", \"generators\", \"order\"],\n omitValue: false,\n secret: false,\n kind: \"order\",\n },\n \"images.generators.timeoutMs\": {\n cliKey: \"images.generators.timeoutMs\",\n path: [\"images\", \"generators\", \"timeoutMs\"],\n omitValue: false,\n secret: false,\n kind: \"timeoutMs\",\n },\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return \"****\"\n return `${value.slice(0, 6)}...${value.slice(-2)}`\n}\n\nexport function assertSafeConfigKeyPath(key: string): void {\n for (const segment of key.split(\".\")) {\n if (FORBIDDEN_SEGMENTS.has(segment)) {\n throw new PptwiseError(`refusing to set \"${key}\": \"${segment}\" is not a valid config key`)\n }\n }\n}\n\nexport function parseCliConfigKey(key: string): CliConfigKey {\n assertSafeConfigKeyPath(key)\n const hit = CLI_KEYS[key]\n if (!hit) {\n throw new PptwiseError(\n `unknown config key \"${key}\" — expected pexels.apiKey, pixabay.apiKey, openverse.clientId, openverse.clientSecret, or images.generators.*`,\n )\n }\n return hit\n}\n\nexport function providerNamedInFile(\n file: ImageUserConfig | null | undefined,\n provider: ImageApiKeyProviderId | \"openverse\",\n): boolean {\n return file?.images?.[provider] !== undefined\n}\n\nfunction nonempty(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined\n}\n\nfunction resolveOne(\n file: ImageUserConfig | null | undefined,\n env: NodeJS.ProcessEnv,\n provider: ImageApiKeyProviderId,\n): ResolvedImageKey {\n const namedInFile = providerNamedInFile(file, provider)\n if (namedInFile) {\n const apiKey = nonempty(file?.images?.[provider]?.apiKey)\n return { apiKey, source: apiKey ? \"file\" : null, namedInFile: true }\n }\n const apiKey = nonempty(resolveProductEnv(ENV_SUFFIX_BY_PROVIDER[provider], env))\n return { apiKey, source: apiKey ? \"env\" : null, namedInFile: false }\n}\n\nfunction resolveOpenverse(file: ImageUserConfig | null | undefined, env: NodeJS.ProcessEnv): ResolvedOpenverse {\n const namedInFile = providerNamedInFile(file, \"openverse\")\n if (namedInFile) {\n const clientId = nonempty(file?.images?.openverse?.clientId)\n const clientSecret = nonempty(file?.images?.openverse?.clientSecret)\n const ready = Boolean(clientId && clientSecret)\n return { clientId, clientSecret, source: ready ? \"file\" : null, namedInFile: true, ready }\n }\n const clientId = nonempty(resolveProductEnv(\"OPENVERSE_CLIENT_ID\", env))\n const clientSecret = nonempty(resolveProductEnv(\"OPENVERSE_CLIENT_SECRET\", env))\n const ready = Boolean(clientId && clientSecret)\n return { clientId, clientSecret, source: ready ? \"env\" : null, namedInFile: false, ready }\n}\n\nexport function resolveImageKeys(opts: { file?: ImageUserConfig | null; env?: NodeJS.ProcessEnv } = {}): ResolvedImageKeys {\n const file = opts.file ?? null\n const env = opts.env ?? process.env\n return {\n pexels: resolveOne(file, env, \"pexels\"),\n pixabay: resolveOne(file, env, \"pixabay\"),\n openverse: resolveOpenverse(file, env),\n }\n}\n\nexport interface ResolvedGenerators {\n enabled: Record<GeneratorId, boolean>\n order: GeneratorId[]\n timeoutMs: number\n}\n\nexport function resolveGenerators(opts: { file?: ImageUserConfig | null } = {}): ResolvedGenerators {\n const g = opts.file?.images?.generators\n const order = g?.order && g.order.length > 0 ? g.order : DEFAULT_GENERATOR_ORDER\n return {\n enabled: {\n grok: g?.grok?.enabled === true,\n codex: g?.codex?.enabled === true,\n antigravity: g?.antigravity?.enabled === true,\n },\n order,\n timeoutMs: typeof g?.timeoutMs === \"number\" && g.timeoutMs > 0 ? g.timeoutMs : DEFAULT_GENERATOR_TIMEOUT_MS,\n }\n}\n\nexport function parseCliConfigValue(parsed: CliConfigKey, raw: string): PersistableConfigValue {\n if (parsed.kind === \"boolean\") {\n const v = raw.trim().toLowerCase()\n if (v !== \"true\" && v !== \"false\") {\n throw new PptwiseError(`${parsed.cliKey} must be true or false`)\n }\n return v === \"true\"\n }\n if (parsed.kind === \"order\") {\n const names = raw\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\")\n const unknown = names.find((n) => !(GENERATOR_IDS as readonly string[]).includes(n))\n if (unknown) {\n throw new PptwiseError(`unknown generator \"${unknown}\" — expected grok, codex, or antigravity`)\n }\n if (names.length === 0) {\n throw new PptwiseError(\"images.generators.order must not be empty\")\n }\n return names\n }\n if (parsed.kind === \"timeoutMs\") {\n if (!/^[0-9]+$/.test(raw.trim()) || Number(raw) <= 0) {\n throw new PptwiseError(`${parsed.cliKey} must be a positive integer`)\n }\n return Number(raw)\n }\n return raw\n}\n\nexport function knownSecretsFrom(keys: ResolvedImageKeys): string[] {\n const secrets: string[] = []\n for (const provider of API_KEY_PROVIDERS) {\n const apiKey = keys[provider].apiKey\n if (apiKey && apiKey.length >= 6) secrets.push(apiKey)\n }\n const { clientId, clientSecret } = keys.openverse\n if (clientSecret && clientSecret.length >= 6) secrets.push(clientSecret)\n if (clientId && clientId.length >= 6) secrets.push(clientId)\n return secrets\n}\n\nfunction assertNotSymlink(path: string): void {\n let st: ReturnType<typeof lstatSync>\n try {\n st = lstatSync(path)\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw e\n }\n if (st.isSymbolicLink()) {\n throw new PptwiseError(`refusing to write ${path}: it is a symlink`)\n }\n}\n\nfunction asPlainObject(value: unknown): Record<string, unknown> {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return { ...(value as Record<string, unknown>) }\n }\n return {}\n}\n\nasync function readRawUserConfig(): Promise<Record<string, unknown>> {\n const path = userConfigPath()\n let text: string\n try {\n text = await readFile(path, \"utf8\")\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return {}\n throw e\n }\n let raw: unknown\n try {\n raw = JSON.parse(text) as unknown\n } catch (e) {\n throw new PptwiseError(`${path} is not valid JSON: ${(e as Error).message}`)\n }\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new PptwiseError(`${path} must be a JSON object`)\n }\n return raw as Record<string, unknown>\n}\n\nexport async function persistUserConfigValue(path: string[], value: PersistableConfigValue | \"\"): Promise<string> {\n for (const segment of path) {\n if (FORBIDDEN_SEGMENTS.has(segment) || segment === \"\") {\n throw new PptwiseError(`refusing to set \"${path.join(\".\")}\": \"${segment}\" is not a valid config key`)\n }\n }\n if (path.length === 0) {\n throw new PptwiseError(\"refusing to set an empty config path\")\n }\n const filePath = userConfigPath()\n assertNotSymlink(filePath)\n const raw = await readRawUserConfig()\n let cursor: Record<string, unknown> = raw\n for (let i = 0; i < path.length - 1; i++) {\n const segment = path[i]!\n const next = asPlainObject(cursor[segment])\n cursor[segment] = next\n cursor = next\n }\n const leaf = path[path.length - 1]!\n if (value === \"\") {\n delete cursor[leaf]\n } else {\n cursor[leaf] = value\n }\n await mkdir(pptwiseHome(), { recursive: true })\n const text = JSON.stringify(raw, null, 2) + \"\\n\"\n await writeFile(filePath, text, { encoding: \"utf8\", mode: 0o600 })\n try {\n chmodSync(filePath, 0o600)\n } catch {\n // platforms without POSIX permission bits\n }\n return filePath\n}\n\nexport async function persistImageApiKey(provider: ImageApiKeyProviderId, apiKey: string): Promise<string> {\n return persistUserConfigValue([\"images\", provider, \"apiKey\"], apiKey)\n}\n\nexport function pexelsApplyUrl(): string {\n return \"https://www.pexels.com/api/\"\n}\n\nexport function pixabayApplyUrl(): string {\n return \"https://pixabay.com/api/docs/\"\n}\n\n/** Hard-fail copy when fetch needs a Pexels or Pixabay key that is missing. */\nexport function missingKeysError(kind: \"pexels\" | \"pixabay\"): PptwiseError {\n if (kind === \"pixabay\") {\n return new PptwiseError(\n `Pixabay is not configured. Apply at ${pixabayApplyUrl()}, then run \\`pptwise config set pixabay.apiKey\\`.`,\n )\n }\n return new PptwiseError(\n `Pexels is not configured. Apply at ${pexelsApplyUrl()}, then run \\`pptwise config set pexels.apiKey\\`.`,\n )\n}\n","/**\n * Deck project directory fs shell (spec §7's \"deck project directory\"\n * scheme, W5 task 5). Everything here touches disk — the pure half\n * (locked-field injection, placeholder/orphan semantics) lives in\n * `../spec/assemble.ts`'s `assembleDeck`, zero-fs by design (`AGENTS.md`'s\n * layout rule: this module is the *only* place that reads `deck.spec.json`\n * / `pages/*.json` / `assets/*` off disk and calls straight through to it,\n * the same posture `./load-ir.ts` already holds for a single IR file). It\n * is also the only place that *writes* `assets/*`, on the disassemble side\n * ({@link writeDeckAssets}) — the mirror image of {@link scanAssets} below,\n * and the CLI-shell half of `disassembleDeck`'s otherwise-lossy asset\n * handling (see that function's own doc comment in `../spec/assemble.ts`).\n *\n * Directory layout (spec §6/§7 — the locked artifact renamed from\n * `deck.plan.json` to `deck.spec.json`, vocabulary-v4 rename, task 2):\n * ```\n * my-deck/\n * deck.spec.json the locked spec — page order's sole source of truth\n * pages/<page-id>.json one file per filled page, content only (no type/heading)\n * assets/ local images, auto-registered by filename\n * ```\n *\n * A directory carrying the pre-rename `deck.plan.json` only (no\n * `deck.spec.json` yet) is no longer read directly — `pptwise migrate\n * <dir> -o <dir>` (`./commands.ts`'s `runMigrate`) converts it in place per\n * spec §9.2's field mapping. A directory carrying *both* files at once is a\n * hard error ({@link readSpecFile} below) — spec §9.2: \"目录中同时出现\n * `deck.plan.json` 和 `deck.spec.json` 时应硬报错,不能猜测优先级\".\n */\nimport { copyFile, mkdir, readFile, readdir, stat, writeFile } from \"node:fs/promises\"\nimport { basename, extname, isAbsolute, join, relative, resolve } from \"node:path\"\nimport { PptwiseError } from \"../errors\"\nimport { assembleDeck, type AssembleResult, type PageContent } from \"../spec/assemble\"\nimport { decksRoot } from \"./home\"\nimport { EXT_BY_MIME, loadIrFile } from \"./load-ir\"\n\n/** The pre-rename artifact name (vocabulary-v4 rename, spec §6/§9.2) — no\n * longer read directly by {@link readSpecFile}, but still needed to (a)\n * detect the dual-file hard-error case and (b) as the migrate command's own\n * read source (`./commands.ts`'s `runMigrate`). Both exported for that\n * second reason — `runMigrate` needs the exact same two filenames, and\n * duplicating the literal strings there would risk the two modules drifting\n * on spelling. */\nexport const PLAN_FILENAME = \"deck.plan.json\"\nexport const SPEC_FILENAME = \"deck.spec.json\"\n// Exported (serve wave, task S1) so `./serve.ts` can build its fs.watch\n// roots from the exact same directory names this module already treats as\n// the deck-project layout's source of truth, instead of a second hardcoded\n// \"pages\"/\"assets\" literal that could drift from these.\nexport const PAGES_DIRNAME = \"pages\"\nexport const ASSETS_DIRNAME = \"assets\"\n/** Optional deck-local brand theme file (brand-extract wave, 裁定 3's\n * zero-flag convention): a `theme.json` sitting in the deck project\n * directory — typically `pptwise brand extract`'s output — is auto-loaded\n * (registered through `registerTheme`) before the deck is assembled, so the\n * spec/IR can reference its `id` with no `--theme-file` flag. Loading\n * happens in `./commands.ts` (`loadDeckTarget`/`runAssemble`), not here —\n * this module stays a pure fs shell. */\nexport const THEME_FILENAME = \"theme.json\"\n\n// ── path-traversal safety (CWE-22 defense) ──────────────────────────────\n\n/**\n * Rejects an `id` that is unsafe to join into a page/asset file path (W5\n * whole-branch review finding 1, CRITICAL — reproduced by the reviewer\n * against both call sites below). `slide.id` and `assets.images` keys are\n * both open, unrestricted `z.string()` at the schema layer (`../ir/index.ts`\n * — no format rule there by design, cross-slide/id rules are `validateIr`'s\n * job, see `SlideSchema.id`'s own doc comment), so a hand-authored IR can set\n * either to anything, including `\"../../../../escape\"` — and both\n * {@link writeOneAsset} below and `runDisassemble`'s page write\n * (`./commands.ts`) join that value straight into a write path with no\n * check of their own before this task. Call this before building any path\n * from an id sourced off a parsed IR.\n *\n * A value `join()`'d as (a possibly-suffixed) single trailing path segment\n * can only ever escape `base` if it is itself absolute, contains a `/` or\n * `\\` separator (smuggling in extra segments, e.g. `\"../../../escape\"`), or\n * is exactly `\"..\"` (the one separator-free value that is still a traversal\n * on its own, e.g. when a sink appends an empty suffix) — those lexical\n * checks alone already make every call site in this file safe regardless of\n * what it joins `id` under. `relative(base, resolve(base, id))` escaping\n * `base` (starts with `\"..\"`, or is itself absolute) is checked too, as\n * defense-in-depth on top of the lexical checks, not a substitute for them —\n * `base` here is a fixed stand-in directory rather than either real sink's\n * actual `assetsDir`/`pagesDir`: the property under test (\"can this id ever\n * resolve outside whatever directory it's joined under\") is a function of\n * `id` alone once the lexical checks above hold, true for any base, so a\n * real caller-supplied base would add no extra precision — see this\n * function's own test suite for the two attack shapes this closes.\n *\n * `context` names the offending id's role (`\"slide id\"`, `\"asset id\"`) so\n * the thrown message points at which field was unsafe.\n */\nexport function assertSafeFileSegment(id: string, context: string): void {\n const safeBase = resolve(\"/pptwise-safe-base\")\n const rel = relative(safeBase, resolve(safeBase, id))\n const safe =\n !isAbsolute(id) && !id.includes(\"/\") && !id.includes(\"\\\\\") && id !== \"..\" && !rel.startsWith(\"..\") && !isAbsolute(rel)\n if (!safe) {\n throw new PptwiseError(\n `${context} \"${id}\" is not a safe file name — ids used as page/asset file names must not contain path separators or \"..\"`,\n )\n }\n}\n\n// ── bare-name / path resolution ─────────────────────────────────────────\n\n/**\n * Returns true when `stat(path)` succeeds and names a directory — the\n * single source of truth every deck-accepting CLI command (`assemble`,\n * `disassemble`'s input is always a file so it never calls this, `validate`/\n * `render`/`preview`) uses to branch between single-file IR and deck-project\n * directory mode. A missing path (`ENOENT`) reads as \"not a directory\"\n * rather than propagating — the caller's next step (`loadIrFile` for the\n * single-file branch) already has its own readable \"cannot read\" error for\n * a path that turns out not to exist at all, and re-deriving that\n * distinction here would just duplicate it. Any *other* `stat` failure\n * (`EACCES`, `ENOTDIR` via a non-directory path segment, ...) rethrows\n * wrapped in {@link PptwiseError} instead — silently reading a real\n * permission or filesystem problem as \"not a directory, try it as a single\n * IR file\" produces a strictly more confusing downstream error than\n * surfacing the actual failure here.\n */\nexport async function isDeckDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory()\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return false\n throw new PptwiseError(`cannot check ${path}: ${(e as Error).message}`)\n }\n}\n\n/**\n * true when `stat(path)` succeeds (file or directory, no distinction) — the\n * same ENOENT-vs-everything-else posture as {@link isDeckDirectory} just\n * above, factored out because `resolveDeckTarget` below needs plain\n * existence (a candidate can legitimately be a file *or* a directory) at\n * two different points, and `runAssemble` (`../cli/commands.ts`) needs it\n * once more, to tell \"target does not exist at all\" (the existing, detailed\n * `readDeckDir` error, expected-layout hint included) apart from \"target\n * exists but is not a directory\" (a friendlier, immediate error — see that\n * function).\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path)\n return true\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return false\n throw new PptwiseError(`cannot check path ${path}: ${(e as Error).message}`)\n }\n}\n\n/**\n * Path-vs-bare-name resolution (spec §7's CLI bare-name resolution): an\n * `arg` that contains a path separator, or that exists locally relative to\n * `cwd` (file *or* directory — a same-directory `deck.json` has no\n * separator but must still resolve as the obviously-intended local file,\n * not get redirected to the deck home), resolves against `cwd` and comes\n * back as a fully-resolved path in both cases — an explicit or\n * locally-resolvable path always wins over the bare-name interpretation,\n * but is never handed back unresolved: every downstream fs call (Node's\n * `readFile` et al.) always resolves a relative path against the process's\n * *real* `process.cwd()`, which only coincides with this function's `cwd`\n * parameter in production (`commands.ts` never passes one explicitly, so it\n * defaults to the real thing) — a test that exercises a different `cwd`\n * without an actual `process.chdir()` needs the already-resolved path back,\n * not the bare `arg`, or the caller's next fs call would silently resolve\n * against a completely different, real cwd. An absolute `arg` is unaffected\n * either way — `path.resolve` returns an absolute later segment as-is.\n *\n * Otherwise `arg` is treated as a deck name under `decksRoot(config)`\n * (`./home.ts` — `$PPTWISE_HOME/decks/<name>`, or a `decksDir` override) —\n * but only when that candidate actually exists. When *neither* the local\n * path nor the deck-home candidate exists, this returns the local\n * (cwd-resolved) path rather than the deck-home guess: a bare arg that was\n * actually a typo'd local filename (`pptwise validate typo.json`) should\n * have its eventual \"cannot read\" error name the file the user typed, not\n * an unrelated `~/.pptwise/decks/typo.json` path they never meant. `config`\n * is not either config layer's raw shape — it is the already-resolved\n * effective `decksDir` source (project `pptwise.config.json`'s own value,\n * spec §7's project-level escape hatch, W5 task 6, when it sets one, else\n * the user config's, `./config.ts`'s `UserPptwiseConfig`) computed once by\n * the caller (`commands.ts`'s `resolveDecksDirSource`) and passed in here\n * already resolved to an absolute path when it came from the project layer\n * — this function itself has no reason to know there were ever two\n * possible layers or bases, only the final answer. The caller fetches both\n * config files at most once per command and passes them in rather than\n * this function reaching for either itself, so a command that already\n * needs one of them for other reasons (theme/style resolution) never reads\n * the same file twice.\n *\n * An empty or whitespace-only `arg` (W5 whole-branch review finding 4) is\n * rejected up front rather than silently resolving to `cwd` itself — without\n * this guard, `resolve(cwd, \"\")` returns `cwd` unchanged and `pathExists`\n * always finds it (a directory always exists), so the empty string would\n * otherwise quietly pass through as \"the target is cwd\", surfacing later as\n * a confusing missing-`deck.spec.json` error instead of naming the actual\n * problem (an empty target argument) up front.\n */\nexport async function resolveDeckTarget(\n arg: string,\n config?: { decksDir?: string },\n cwd: string = process.cwd(),\n): Promise<string> {\n if (arg.trim() === \"\") throw new PptwiseError(\"deck target must not be empty\")\n if (arg.includes(\"/\") || arg.includes(\"\\\\\")) return resolve(cwd, arg)\n const local = resolve(cwd, arg)\n if (await pathExists(local)) return local\n const fallback = join(decksRoot(config), arg)\n return (await pathExists(fallback)) ? fallback : local\n}\n\n// ── deck.spec.json ──────────────────────────────────────────────────────\n\n/** The expected-layout block of {@link readSpecFile}'s missing-file error —\n * `padEnd`-aligned programmatically (not hand-counted spaces in a template\n * literal) so the three column widths can't silently drift out of line\n * when one of the three filename/`*_DIRNAME` constants above changes. */\nfunction expectedLayoutHint(): string {\n const rows: [string, string][] = [\n [SPEC_FILENAME, \"the locked spec (see `pptwise spec validate`)\"],\n [`${PAGES_DIRNAME}/<page-id>.json`, \"one file per filled page (missing pages become placeholders)\"],\n [`${ASSETS_DIRNAME}/`, \"optional local images\"],\n ]\n const width = Math.max(...rows.map(([name]) => name.length)) + 2\n return rows.map(([name, desc]) => ` ${name.padEnd(width)}${desc}`).join(\"\\n\")\n}\n\n/**\n * Reads `deck.spec.json` out of `dir` (vocabulary-v4 rename, task 2 —\n * this function used to read the pre-rename `deck.plan.json` directly; it\n * no longer does). Three failure shapes, each with its own message:\n *\n * - both `deck.plan.json` and `deck.spec.json` present — a hard error, spec\n * §9.2: \"目录中同时出现 `deck.plan.json` 和 `deck.spec.json` 时应硬报错,\n * 不能猜测优先级\" (\"hard error, never guess which one wins\"). Checked\n * before the missing-file branch below so a caller that just ran\n * `pptwise migrate <dir> -o <dir>` (which writes `deck.spec.json`\n * *alongside* the pre-existing `deck.plan.json`, never deleting it) gets\n * pointed at deleting the old file, not a generic \"not a deck project\"\n * message.\n * - only `deck.plan.json` present (no `deck.spec.json` yet) — this\n * directory predates the rename and is no longer read directly; the\n * message points at `pptwise migrate` instead of the generic missing-file\n * hint, since the fix here is a one-command conversion, not authoring a\n * fresh file from scratch.\n * - neither file present — the pre-existing \"friendlier message over\n * `loadIrFile`'s generic \"cannot read\"\" this function has always had: the\n * one failure a deck-directory caller is most likely to hit by typo or by\n * pointing at a directory that was never a deck project in the first\n * place, so the error spells out the expected layout and points at\n * `pptwise spec validate` rather than leaving the caller to guess.\n */\nasync function readSpecFile(dir: string): Promise<unknown> {\n const specPath = join(dir, SPEC_FILENAME)\n const planPath = join(dir, PLAN_FILENAME)\n const [specExists, planExists] = await Promise.all([pathExists(specPath), pathExists(planPath)])\n if (specExists && planExists) {\n throw new PptwiseError(\n `both ${SPEC_FILENAME} and ${PLAN_FILENAME} exist in ${dir} — ambiguous, refusing to guess which one wins. Delete ${PLAN_FILENAME} once you have confirmed ${SPEC_FILENAME} is correct (\\`pptwise migrate\\` never deletes the source file it read)`,\n )\n }\n if (!specExists && planExists) {\n throw new PptwiseError(\n `${dir} has ${PLAN_FILENAME} but no ${SPEC_FILENAME} — deck project directories now use ${SPEC_FILENAME}. Run \\`pptwise migrate ${dir} -o ${dir}\\` to convert it`,\n )\n }\n let text: string\n try {\n text = await readFile(specPath, \"utf8\")\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new PptwiseError(\n `no ${SPEC_FILENAME} in ${dir} — expected a deck project directory:\\n${expectedLayoutHint()}`,\n )\n }\n throw new PptwiseError(`cannot read spec file: ${specPath}`)\n }\n try {\n return JSON.parse(text) as unknown\n } catch (e) {\n throw new PptwiseError(`spec file ${specPath} is not valid JSON: ${(e as Error).message}`)\n }\n}\n\n// ── pages/<id>.json ──────────────────────────────────────────────────────\n\n/**\n * Reads every `pages/<id>.json` file into a `{ id: parsedContent }` record —\n * `id` is the filename sans `.json` (spec §7: one file per page, named by a\n * stable id). A missing `pages/` directory (`ENOENT`) is not an error, just\n * an empty record (a brand-new deck project with a spec and no filled pages\n * yet is exactly `assembleDeck`'s \"every page becomes a placeholder\" case)\n * — but `pages/` existing as something that cannot be read as a directory\n * (e.g. a file sitting where a directory was expected, `ENOTDIR`) is a real\n * problem and throws {@link PptwiseError} naming the path, not silently\n * \"zero pages\" (same ENOENT-vs-everything-else posture as\n * {@link isDeckDirectory} above). Non-`.json` entries (a stray `.DS_Store`,\n * an editor swap file, a subdirectory) are silently skipped rather than fed\n * to `JSON.parse` — `.json` is the only declared file shape for this\n * directory, so anything else was never a page file to begin with, not a\n * malformed one. `pages` is deliberately typed `Record<string, unknown>`\n * here (not `Record<string, PageContent>`) — each value's actual shape is\n * checked by `assembleDeck` itself, the same `unknown`-until-validated\n * boundary its own doc comment describes. Entries are read concurrently\n * (`Promise.all`) — independent files, each writing its own `pages[id]` key,\n * nothing to race on.\n */\nasync function readPages(dir: string): Promise<Record<string, unknown>> {\n const pagesDir = join(dir, PAGES_DIRNAME)\n let entries: string[]\n try {\n entries = (await readdir(pagesDir, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && extname(entry.name) === \".json\")\n .map((entry) => entry.name)\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return {}\n throw new PptwiseError(`cannot read ${PAGES_DIRNAME}/ directory ${pagesDir}: ${(e as Error).message}`)\n }\n const pages: Record<string, unknown> = {}\n await Promise.all(\n entries.map(async (entry) => {\n const id = basename(entry, \".json\")\n pages[id] = await loadIrFile(join(pagesDir, entry), `page \"${id}\"`)\n }),\n )\n return pages\n}\n\n// ── assets/ ──────────────────────────────────────────────────────────────\n\n/**\n * Scans `assets/` and maps each file to an `assets.images` entry (spec §7's\n * assets-mapping rule): `id` is the filename sans extension, `src` is the\n * `assets/<filename>` path *relative to the deck directory* — resolved to\n * actual bytes later by the existing `resolveLocalAssets` (`./load-ir.ts`),\n * called with the deck directory as its base (see `commands.ts`). A missing\n * `assets/` directory (`ENOENT`) is zero assets, same as a missing `pages/`\n * above — anything else (`ENOTDIR`, a permission error, ...) throws\n * {@link PptwiseError} naming the path rather than silently reading as \"no\n * assets here\" (see {@link readPages}'s own note on this). Dotfiles\n * (`.DS_Store` and friends — `extname` returns `\"\"` for these, so their\n * \"id\" would otherwise be the whole filename) are skipped: they are never a\n * legitimate image, and `resolveLocalAssets` inlines *every* registered\n * entry unconditionally, so a stray metadata file left registered would\n * fail the whole render with a confusing \"unsupported image format\" error\n * for an asset nothing in the deck ever references. Two files that\n * normalize to the same id (`logo.png` and `logo.jpg`) is a genuine\n * authoring ambiguity — same \"structural mismatch always errors\" posture as\n * `assembleDeck`'s orphan-page check — reported with both filenames so the\n * fix (rename one) is obvious.\n */\nasync function scanAssets(dir: string): Promise<Record<string, { src: string }>> {\n const assetsDir = join(dir, ASSETS_DIRNAME)\n let entries: string[]\n try {\n entries = (await readdir(assetsDir, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && !entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return {}\n throw new PptwiseError(`cannot read ${ASSETS_DIRNAME}/ directory ${assetsDir}: ${(e as Error).message}`)\n }\n const images: Record<string, { src: string }> = {}\n const sourceFile = new Map<string, string>()\n for (const entry of entries) {\n const id = basename(entry, extname(entry))\n const previous = sourceFile.get(id)\n if (previous !== undefined) {\n throw new PptwiseError(\n `${ASSETS_DIRNAME}/${previous} and ${ASSETS_DIRNAME}/${entry} both register image id \"${id}\" — rename one of the files`,\n )\n }\n sourceFile.set(id, entry)\n images[id] = { src: `${ASSETS_DIRNAME}/${entry}` }\n }\n return images\n}\n\n// ── readDeckDir ──────────────────────────────────────────────────────────\n\nexport interface DeckDirResult extends AssembleResult {\n /** Absolute path to the deck directory — the base `resolveLocalAssets`\n * should resolve this IR's (relative) asset paths against. */\n deckDir: string\n}\n\n/**\n * Reads a deck project directory end to end: spec + pages → `assembleDeck`\n * (locked-field injection, placeholder/orphan semantics — see that\n * function's own doc comment) → assets/ scan merged into the assembled IR's\n * `assets.images` (assemble first, then inject — a deck spec has no `assets`\n * field of its own, so there is never a pre-existing id for a scanned asset\n * to collide with, only the intra-`assets/`-directory collision\n * {@link scanAssets} itself guards against).\n *\n * The merge rebuilds `ir.assets` as a fresh object (`{ images: { ...,\n * ...images } }`) rather than assigning into `ir.assets.images` in place —\n * deliberately, not just style: `PptxIRSchema`'s `assets` field defaults to\n * a *static* object literal (`AssetsSchema.default({ images: {} })`,\n * `../ir/index.ts`), and a deck spec never sets its own `assets` (assembleDeck's\n * raw object omits the key entirely, same as every other field it lets the\n * schema default), so *every* assembled deck's `ir.assets.images` starts out\n * as that one schema-default object — zod does not deep-clone a static\n * default per parse, only the immediately-defaulted field itself, so nested\n * defaults below it (`images: {}` inside `AssetsSchema`'s own default) keep\n * one shared identity across unrelated parses. Mutating that shared object\n * in place (`ir.assets.images[id] = asset`, the first version of this\n * function) would silently register one deck project's local images onto\n * every other deck assembled in the same process — confirmed with a\n * standalone repro against this exact schema shape before writing this\n * comment. Rebuilding the object sidesteps the shared reference without\n * needing to touch the schema itself.\n *\n * The merged result is spliced in via a shallow clone of the whole `ir`\n * object (`{ ...ir, assets: ... }`), not a `ir.assets = ...` reassignment\n * onto the object `assembleDeck` returned (post-v0.3 W8 fix round, backlog\n * item 4, `.issues/notes/engineering-history.md` #4): the earlier\n * version mutated `assembleDeck`'s own return value in place, which is\n * harmless *today* only because `variety.ts`'s `deckSeedCache` and\n * `layout-selection.ts`'s `deckEffectiveLayoutIdsCache` — the only two\n * consumers that key a `WeakMap` off an `ir` object's identity — never read\n * `.assets` (confirmed by reading both cache-populating functions: they only\n * touch `seed`/`filename`/`theme.id`/`theme.style`/`narrative`/\n * `slides[].heading`/`.id`/`.type`/`.layout`/`.background`). Cloning\n * instead of mutating means the object\n * `assembleDeck` returned is never touched, and this function's own return\n * value is a distinct identity no earlier reference could have already\n * cached against — correct-by-construction regardless of what a future\n * cache keys on, not just correct because of what today's two caches happen\n * to skip.\n */\nexport async function readDeckDir(dir: string): Promise<DeckDirResult> {\n const deckDir = resolve(dir)\n const spec = await readSpecFile(deckDir)\n const pages = await readPages(deckDir)\n const { ir, generatedSeed, materializedLayoutCount } = assembleDeck(spec, pages as Record<string, PageContent>)\n const images = await scanAssets(deckDir)\n const merged = { ...ir, assets: { images: { ...ir.assets.images, ...images } } }\n return { ir: merged, generatedSeed, materializedLayoutCount, deckDir }\n}\n\n// ── assets/ (write direction — disassemble) ─────────────────────────────\n\nexport interface WriteDeckAssetsResult {\n /** Number of `ir.assets.images` entries materialized into `assets/`. */\n count: number\n /** Absolute path to the `assets/` directory written into — never created\n * (and this path never exists) when `count` is 0. */\n assetsDir: string\n}\n\n/**\n * Write direction of the assets/ concept — the mirror image of\n * {@link scanAssets} above, and the CLI-shell half of `disassembleDeck`'s\n * documented-lossy `assets` handling (`../spec/assemble.ts`'s own doc\n * comment on that function): that pure function never touches\n * `ir.assets.images` at all (its `{ spec, pages }` return has no `assets`\n * field), so without this step a disassembled directory would carry\n * `asset_id` references inside `pages/*.json` with nothing under `assets/`\n * backing them — exactly the \"image deck round-trips to a missing image\"\n * bug this function exists to close. Called by `runDisassemble`\n * (`../cli/commands.ts`) with the source IR's own `assets.images` map and\n * `sourceBaseDir` (the *input* IR file's own directory — the same base\n * `resolveLocalAssets`, `./load-ir.ts`, would resolve a relative local src\n * against at render time).\n *\n * Three source shapes, three outcomes (per entry, all independent —\n * written concurrently via `Promise.all`):\n * - `data:<mime>;base64,<payload>` → decoded and written to\n * `assets/<id><ext>`, `ext` looked up from `mime` via `EXT_BY_MIME`\n * (`./load-ir.ts`). An unrecognized mime or a non-base64 data URI is a\n * hard {@link PptwiseError} naming the asset, not a silent skip.\n * - a local file path (relative resolves against `sourceBaseDir`, the same\n * `isAbsolute(src) ? src : resolve(base, src)` rule `resolveLocalAssets`\n * itself uses) → copied byte-for-byte into `assets/<id><origExt>`. An\n * unreadable source (moved/deleted/permission-denied since the IR was\n * generated) is a hard {@link PptwiseError} naming the asset and the path\n * that could not be read.\n * - `http(s)://` → always a hard {@link PptwiseError} — a URL asset has no\n * local bytes to write at all. The fix is on the deck author's side\n * (inline it as a data URI, or download it first), not something this\n * function can paper over.\n *\n * Written entries need no spec or page record of their own: `readDeckDir`'s\n * own {@link scanAssets} re-registers every file under `assets/` purely by\n * scanning the directory, the same way it would for a hand-added image —\n * this function's only job is making sure the bytes are there.\n */\nexport async function writeDeckAssets(\n images: Record<string, { src: string }>,\n outDir: string,\n sourceBaseDir: string,\n): Promise<WriteDeckAssetsResult> {\n const entries = Object.entries(images)\n const assetsDir = join(outDir, ASSETS_DIRNAME)\n if (entries.length === 0) return { count: 0, assetsDir }\n await mkdir(assetsDir, { recursive: true })\n await Promise.all(entries.map(([id, asset]) => writeOneAsset(id, asset.src, assetsDir, sourceBaseDir)))\n return { count: entries.length, assetsDir }\n}\n\n/** `data:<mime>;base64,<payload>` — the only data-URI shape any producer in\n * this codebase ever writes (`resolveLocalAssets`, `./load-ir.ts`, and the\n * sharp/canvas recode paths in `../platform/`) — matched strictly rather\n * than handling arbitrary charset params or non-base64 payloads nothing\n * here produces. */\nconst DATA_URI_RE = /^data:([^;,]+);base64,(.*)$/s\n\nasync function writeOneAsset(id: string, src: string, assetsDir: string, sourceBaseDir: string): Promise<void> {\n // W5 whole-branch review finding 1: one guard at the top covers both\n // write branches below (data-URI and local-file-copy) — `id` is the same\n // value regardless of which branch runs, so there is nothing branch-\n // specific about the check itself, only about what gets appended after it.\n assertSafeFileSegment(id, \"asset id\")\n if (src.startsWith(\"data:\")) {\n const match = DATA_URI_RE.exec(src)\n if (!match) {\n throw new PptwiseError(`asset \"${id}\": only base64-encoded data URIs can be disassembled (malformed data URI)`)\n }\n const mime = match[1]\n const payload = match[2]\n const ext = EXT_BY_MIME[mime]\n if (!ext) {\n throw new PptwiseError(\n `asset \"${id}\": cannot disassemble a data URI with mime \"${mime}\" — expected one of ${Object.keys(EXT_BY_MIME).join(\", \")}`,\n )\n }\n await writeFile(join(assetsDir, `${id}${ext}`), Buffer.from(payload, \"base64\"))\n return\n }\n if (/^https?:\\/\\//.test(src)) {\n throw new PptwiseError(\n `asset \"${id}\": URL assets cannot be disassembled into a deck directory — inline it as a data URI or download it first`,\n )\n }\n const abs = isAbsolute(src) ? src : resolve(sourceBaseDir, src)\n try {\n await copyFile(abs, join(assetsDir, `${id}${extname(abs)}`))\n } catch {\n throw new PptwiseError(`asset \"${id}\": cannot read source image ${abs} (from src \"${src}\") — cannot disassemble`)\n }\n}\n","import { readFile } from \"node:fs/promises\"\nimport { basename, extname, isAbsolute, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { PptwiseError } from \"../errors\"\nimport type { PptxIR } from \"../ir\"\nimport { FORMAT_BY_MIME, MIME_BY_SNIFFED_FORMAT, sniffImageFormat } from \"../ir/asset-sniff\"\nimport { getPlatform } from \"../platform/registry\"\n\nconst MIME_BY_EXT: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n}\n\n/**\n * Mime → extension (with leading dot), one canonical extension per mime\n * (\"image/jpeg\" → \".jpg\", not \".jpeg\") — the reverse direction of\n * {@link MIME_BY_EXT} above, plus `image/webp`, which that table\n * deliberately omits: a local `.webp` file must keep taking the sharp\n * recode-to-png path in {@link resolveLocalAssets} below (see the e2e\n * \"webp asset regression leg\", `scripts/e2e.mts` — adding webp to\n * `MIME_BY_EXT` would silently skip that path for local files). This table\n * only serves the opposite direction — naming a file already decoded from a\n * `data:` URI (`writeDeckAssets`, `./deck-dir.ts`'s disassemble asset\n * materialization) — where a webp *payload* is a real possibility (e.g. an\n * upload-derived asset already embedded as webp) with no local file or\n * recode step involved at all.\n */\nexport const EXT_BY_MIME: Record<string, string> = {\n \"image/png\": \".png\",\n \"image/jpeg\": \".jpg\",\n \"image/gif\": \".gif\",\n \"image/webp\": \".webp\",\n}\n\n/** Decode a `file:` asset src with `fileURLToPath` (or the Windows mapping\n * when `platform` is injected so Darwin tests can pin a drive letter). */\nexport function unwrapFileSrc(src: string, platform: NodeJS.Platform = process.platform): string {\n if (platform === \"win32\") {\n const url = new URL(src)\n let pathname = decodeURIComponent(url.pathname.replace(/\\//g, \"\\\\\"))\n if (url.hostname) return `\\\\\\\\${url.hostname}${pathname}`\n if (pathname.startsWith(\"\\\\\") && pathname.length >= 3 && pathname[2] === \":\") {\n return pathname.slice(1)\n }\n return pathname\n }\n return fileURLToPath(src)\n}\n\n/** Read and JSON-parse a file with readable failure messages. `kind` names\n * what the file is expected to hold (e.g. \"spec\") for both failure\n * messages — defaults to \"IR\" for this function's original, still most\n * common caller (`runRender`/`runValidate`/`runPreview`, `./commands.ts`);\n * `runSpecValidate` passes \"spec\" so its own errors read correctly instead\n * of borrowing IR's wording for a file that was never one. */\nexport async function loadIrFile(irPath: string, kind = \"IR\"): Promise<unknown> {\n let text: string\n try {\n text = await readFile(irPath, \"utf8\")\n } catch {\n throw new PptwiseError(`cannot read ${kind} file: ${irPath}`)\n }\n try {\n return JSON.parse(text) as unknown\n } catch (e) {\n throw new PptwiseError(`${kind} file ${irPath} is not valid JSON: ${(e as Error).message}`)\n }\n}\n\n/**\n * Rewrite local file paths in assets.images to data URIs (CLI-only concern).\n * data: and http(s): sources pass through — the export pipeline inlines URLs\n * itself.\n *\n * Byte-level validation (borrow wave, Task 2 — D3, the local-file half of\n * `api.ts`'s `checkAssetBytes` doc comment — read that one for the full\n * rationale, this is its Node-only counterpart for the ingestion form\n * `validateIr` itself can't reach): every file's bytes are read once here\n * regardless of extension, then a zero-byte file is rejected loud\n * immediately (`dr/d-robustness.md`'s zero-byte-PNG probe — previously this\n * silently produced a 0-byte media part in the exported .pptx). For the four\n * extensions {@link MIME_BY_EXT} recognizes, the bytes are additionally\n * magic-byte-sniffed ({@link sniffImageFormat}) and checked against what the\n * extension claims: a corrupt/unrecognized header is rejected\n * (garbage-bytes-PNG probe), and so is a mismatch — a real PNG saved as\n * `.jpg` (the third D3 probe) — same reject-not-silently-relabel disposition\n * `checkAssetBytes` documents, for the same reason: an extension/content\n * mismatch would otherwise land in the exported package as a media part\n * whose declared type and actual bytes disagree, which `package-audit.ts`'s\n * structural rules never check. A file whose extension isn't one of those\n * four (webp and friends) skips the sniff/mismatch check and keeps taking\n * the `recodeImageToPng` path below unconditionally — sharp decodes by\n * content, not extension, so a mislabeled-but-decodable file there is\n * already harmless, and a genuinely corrupt one surfaces as sharp's own\n * decode error.\n */\nasync function readFromWorkspace(src: string, workspaceAssetsDir: string): Promise<Buffer | null> {\n const candidates = [resolve(workspaceAssetsDir, src), resolve(workspaceAssetsDir, basename(src))]\n const seen = new Set<string>()\n for (const candidate of candidates) {\n if (seen.has(candidate)) continue\n seen.add(candidate)\n try {\n return await readFile(candidate)\n } catch {\n continue\n }\n }\n return null\n}\n\nexport async function resolveLocalAssets(ir: PptxIR, baseDir: string, workspaceAssetsDir?: string): Promise<void> {\n for (const [name, asset] of Object.entries(ir.assets.images)) {\n const src = asset.src\n if (src.startsWith(\"data:\") || /^https?:\\/\\//.test(src)) continue\n const abs = src.startsWith(\"file:\")\n ? unwrapFileSrc(src)\n : isAbsolute(src)\n ? src\n : resolve(baseDir, src)\n let bytes: Buffer\n try {\n bytes = await readFile(abs)\n } catch {\n const fallback = workspaceAssetsDir && !isAbsolute(src) ? await readFromWorkspace(src, workspaceAssetsDir) : null\n if (!fallback) {\n throw new PptwiseError(`asset \"${name}\": cannot read image file ${abs} (from src \"${src}\")`)\n }\n bytes = fallback\n }\n if (bytes.length === 0) {\n throw new PptwiseError(`asset \"${name}\": image file ${abs} is zero bytes — re-export or re-select the file`)\n }\n const ext = extname(abs).toLowerCase()\n const mime = MIME_BY_EXT[ext]\n if (mime) {\n const sniffed = sniffImageFormat(bytes)\n if (sniffed === null) {\n throw new PptwiseError(\n `asset \"${name}\": image file ${abs} has a corrupt or unrecognized header (extension claims ${mime}) — re-export or re-select the file`,\n )\n }\n const expected = FORMAT_BY_MIME[mime]\n if (expected && sniffed !== expected) {\n throw new PptwiseError(\n `asset \"${name}\": image file ${abs} is named \"${ext}\" but its bytes are actually ${MIME_BY_SNIFFED_FORMAT[sniffed]} — rename the file to match its real format, or re-export/re-save it as a genuine ${mime}`,\n )\n }\n asset.src = `data:${mime};base64,${bytes.toString(\"base64\")}`\n continue\n }\n const recode = getPlatform().recodeImageToPng\n if (!recode) {\n throw new PptwiseError(\n `asset \"${name}\": unsupported image format \"${extname(abs)}\" — install sharp or convert to png/jpeg/gif`\n )\n }\n asset.src = await recode(`data:application/octet-stream;base64,${bytes.toString(\"base64\")}`)\n }\n}\n","/**\n * The paint a preview surface must put *behind* a mounted slide.\n *\n * Every surface that shows a slide in HTML — `../cli/preview-html.ts`, the\n * review gallery, the DSH panel — drops the standalone `<svg viewBox=\"0 0\n * 1280 720\">` into a 16:9 box and stretches it to fill (`width:100%;\n * height:100%`). That box almost never lands on whole device pixels: its\n * width comes out of a grid track, a `min()` against the viewport, or a\n * container query, so the slide's own left and right edges routinely sit at,\n * say, x=53.33 and x=1386.67.\n *\n * A browser paints that boundary column twice. First the box's own\n * background, at partial coverage; then the SVG on top of it, at the same\n * partial coverage. What survives is `(1-a)*a` of the box's background —\n * roughly a fifth to a quarter of it — in a one-to-two pixel strip down the\n * slide's edge. When the box is painted in a neutral light grey and the\n * slide is dark, that strip reads as a pale vertical line hugging the page,\n * which is exactly what the 2026-08-20 review reported on campaign p01/p02,\n * ink p01/p03 and insight p07. Measured, not reasoned about: setting the\n * gallery's stage colour to magenta turned the line magenta, and setting it\n * to the slide's own background made the line disappear.\n *\n * There is no way to stop the double-paint from the slide's side. The SVG\n * cannot paint outside its own viewBox, an outset plus `overflow:hidden`\n * just moves the same antialiased boundary onto the clip, and forcing a\n * compositing layer makes it worse. What does work is giving that boundary\n * nothing foreign to blend with: paint the box in the slide's own edge\n * colour, and the surviving fraction is the colour that was already there.\n *\n * The exported PPTX has no equivalent defect — the slide is 13.33in wide and\n * the background shape converts to 13.3333in, so it overhangs rather than\n * falls short.\n */\n\nimport { CANVAS_H_PX, CANVAS_W_PX } from \"../constants\"\n\n/** Runs of `<g …>`, `</g>` and `<rect …>`, in paint order. */\nconst TAG = /<(\\/?)(g|rect)\\b([^>]*?)(\\/?)>/g\n\nconst ATTR_CACHE = new Map<string, RegExp>()\n\nfunction attr(attrs: string, name: string): string | undefined {\n let re = ATTR_CACHE.get(name)\n if (!re) {\n re = new RegExp(`\\\\b${name}=\"([^\"]*)\"`)\n ATTR_CACHE.set(name, re)\n }\n const m = re.exec(attrs)\n return m ? m[1] : undefined\n}\n\nfunction num(attrs: string, name: string, fallback: number): number {\n const raw = attr(attrs, name)\n if (raw === undefined) return fallback\n const n = Number(raw)\n return Number.isFinite(n) ? n : fallback\n}\n\n/**\n * `\\bopacity=\"` also matches the tail of `fill-opacity=\"…\"`, which is\n * harmless here: both attributes disqualify a rect the same way, and the\n * worst a mixed-up read can do is skip a rect that was in fact opaque.\n */\nfunction isOpaque(attrs: string): boolean {\n for (const name of [\"opacity\", \"fill-opacity\"]) {\n const raw = attr(attrs, name)\n if (raw !== undefined && Number(raw) < 1) return false\n }\n return true\n}\n\nconst HEX = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/\n\n/**\n * The CSS `background` value to paint behind this slide, or `null` when the\n * slide's edge has no single answer (a photo background, most obviously) and\n * the caller should keep whatever neutral it already uses.\n *\n * Reads the markup rather than the IR on purpose: what the reader sees at the\n * page edge is whatever was painted last there, which is not always the\n * theme's background token. `ink`'s cover paints a full-bleed near-black\n * masthead over a cream page, and its `split-band` content pages paint a\n * 224px-tall dark band over the same cream — the token would be wrong for\n * both, the markup is right for both.\n *\n * Only full-width, fully opaque `<rect>`s outside any transformed group\n * count, so the result is a top-to-bottom profile of the page edge: one\n * colour when the page is one colour, and a hard-stop `linear-gradient` when\n * it is not (a gradient background's bands, or a band layout's header).\n */\nexport function slideEdgeFill(svg: string): string | null {\n // One entry per canvas row, so overpainting is just assignment in paint\n // order — no interval arithmetic, and a later band wins exactly the rows it\n // actually covers.\n const rows: (string | undefined)[] = new Array<string | undefined>(CANVAS_H_PX)\n // Depth of `<g>` nesting that moves its children, so their rect\n // coordinates are no longer canvas coordinates and must be ignored.\n let moved = 0\n const open: boolean[] = []\n let painted = false\n\n TAG.lastIndex = 0\n for (let m = TAG.exec(svg); m; m = TAG.exec(svg)) {\n const [, closing, tag, attrs, selfClosing] = m\n if (tag === \"g\") {\n if (closing) {\n if (open.pop()) moved--\n continue\n }\n const movesChildren = /\\btransform=\"/.test(attrs!)\n if (selfClosing) continue\n open.push(movesChildren)\n if (movesChildren) moved++\n continue\n }\n if (closing || moved > 0) continue\n\n const fill = attr(attrs!, \"fill\")\n if (!fill || !HEX.test(fill) || !isOpaque(attrs!)) continue\n const x = num(attrs!, \"x\", 0)\n const width = num(attrs!, \"width\", 0)\n if (x > 0 || x + width < CANVAS_W_PX) continue\n const y = num(attrs!, \"y\", 0)\n const height = num(attrs!, \"height\", 0)\n const top = Math.max(0, Math.round(y))\n const bottom = Math.min(CANVAS_H_PX, Math.round(y + height))\n for (let r = top; r < bottom; r++) rows[r] = fill\n if (bottom > top) painted = true\n }\n\n if (!painted) return null\n\n // Rows nothing covered take the nearest painted colour, so a background\n // that stops a pixel short of the canvas cannot punch a hole in the answer.\n let last: string | undefined\n for (let r = 0; r < CANVAS_H_PX; r++) {\n if (rows[r]) last = rows[r]\n else rows[r] = last\n }\n for (let r = CANVAS_H_PX - 1; r >= 0; r--) {\n if (rows[r]) last = rows[r]\n else rows[r] = last\n }\n\n const stops: { fill: string; top: number; bottom: number }[] = []\n for (let r = 0; r < CANVAS_H_PX; r++) {\n const fill = rows[r]!\n const run = stops[stops.length - 1]\n if (run && run.fill === fill) run.bottom = r + 1\n else stops.push({ fill, top: r, bottom: r + 1 })\n }\n\n if (stops.length === 1) return stops[0]!.fill\n const pct = (row: number) => `${((row / CANVAS_H_PX) * 100).toFixed(3).replace(/\\.?0+$/, \"\")}%`\n const body = stops.map((s) => `${s.fill} ${pct(s.top)} ${pct(s.bottom)}`).join(\",\")\n return `linear-gradient(180deg,${body})`\n}\n","/**\n * Namespacing for SVG-internal ids, for the case where several standalone\n * slide SVGs are inlined into one HTML document.\n *\n * Each slide is rendered as its own standalone document, so ids only have to\n * be unique *within* a slide — and they are. Put two of those documents in\n * one page, though, and their id spaces merge: a `url(#decor-tech-field)` on\n * slide 6 resolves against slide 3's definition, because that one came\n * first. The paint the reader sees is whichever slide happened to be earlier\n * in the document, which is not a rule anybody intended.\n *\n * Today the collisions that actually occur are between byte-identical\n * definitions (a theme's decor gradient repeated per slide), so nothing\n * renders wrong yet — this is a latent defect, found by diffing the ids\n * across a real deck's slides rather than by anything going visibly wrong.\n * The moment a definition varies per slide (a decor gradient tinted by slide\n * type, a chart gradient keyed on its own series) the later slide silently\n * takes the earlier one's paint, and the failure looks like a theme bug\n * rather than an id bug.\n *\n * Applied at build time, in the two places that inline slides into one\n * document: `../cli/preview-html.ts` and the review gallery.\n */\n\n/**\n * Rewrite every id definition and reference inside one SVG document so it\n * cannot collide with another document inlined beside it.\n *\n * Deliberately narrow: this only touches `id=\"…\"`, `url(#…)` and\n * `href`/`xlink:href=\"#…\"`, the three forms this renderer actually emits.\n * It is a string transform over self-produced markup, not a general SVG\n * rewriter — an arbitrary document could reference an id in ways this misses\n * (`begin=\"a.click\"`, `style=\"fill:url(#a)\"` inside a CSS string), and none\n * of those forms appear in anything `renderSlideSvg` produces.\n */\nexport function namespaceSvgIds(svg: string, prefix: string): string {\n if (!prefix) return svg\n return svg\n .replace(/\\bid=\"([^\"]*)\"/g, (_m, id: string) => `id=\"${prefix}${id}\"`)\n .replace(/url\\(#([^)\"']*)\\)/g, (_m, id: string) => `url(#${prefix}${id})`)\n .replace(/\\b(xlink:href|href)=\"#([^\"]*)\"/g, (_m, attr: string, id: string) => `${attr}=\"#${prefix}${id}\"`)\n}\n\n/**\n * A short, stable, collision-free prefix for the slide at `index`.\n *\n * Positional rather than content-derived on purpose: two slides that render\n * byte-identical markup still need distinct id spaces, and a content hash\n * would hand them the same one.\n */\nexport function svgIdPrefix(index: number): string {\n return `s${index}-`\n}\n","/**\n * Pure string builder for `pptwise preview --html`'s self-contained review\n * bundle (v0.3 W7 task 1, spec §7 workflow ⑤): one `preview.html` with every\n * slide's already-rendered SVG (`renderSlideSvg`, `../api.ts`) inlined\n * directly into the markup, a bottom thumbnail filmstrip, keyboard (←/→) and\n * click navigation, and a page counter. No `fs` here on purpose — this\n * module only assembles a string; `runPreview` (`./commands.ts`) is the only\n * caller and the only place that touches disk, which keeps this file\n * trivially unit-testable (feed it slide data, assert on the returned\n * string) despite living under the Node-only `src/cli*` tree (AGENTS.md's\n * \"no Node-only deps\" layout rule is about `src/index.ts`'s own dependency\n * closure, not every file under `src/cli` — this one just happens to need\n * none anyway).\n *\n * Self-containment (the plan's hard requirement): every slide's SVG is\n * embedded as raw markup — never `<img src>`, never any other reference to\n * an external file — and the only CSS/JS in the document is inlined in\n * `<style>`/`<script>`. Local image assets are already `data:` URIs by the\n * time `runPreview` calls `renderSlideSvg` (`resolveLocalAssets`'s job,\n * `./load-ir.ts` — this module never touches assets itself) — *assuming*\n * every image asset the deck references is local or already a `data:` URI.\n * Known limitation: `resolveLocalAssets` deliberately passes a remote\n * `http(s):` asset `src` through untouched (the export pipeline inlines\n * those itself), so that src is left un-inlined and lands verbatim in this\n * bundle's embedded SVG as a live network reference, not a namespace URI —\n * breaking the zero-network-request guarantee for that one slide. Barring\n * that case, the only `http(s)` substrings that can appear anywhere in the\n * output are SVG namespace URIs (`xmlns=\"http://www.w3.org/2000/svg\"`,\n * emitted by `../svg/serialize.ts` on every slide) — XML namespace\n * identifiers, not network requests.\n *\n * Embed strategy (one `<svg>` per slide, not two): the thumbnail filmstrip\n * and the large \"stage\" view share the exact same DOM node per slide rather\n * than each holding its own copy — duplicating every slide's SVG (including\n * any inlined `data:` image payloads) would double the file's byte size for\n * an image-heavy deck for zero benefit, since only one size is ever on\n * screen for a given slide at a time. The one node that exists for a slide\n * lives in exactly one of two homes: `#pf-stage` (the slide currently being\n * viewed large) or its own `.pf-thumb-slot` (every other slide, shown small\n * in the filmstrip) — `<script>`'s `activate()` moves it between the two\n * with a plain `appendChild` (which detaches a node from its previous parent\n * automatically) when the viewer clicks a thumbnail or presses ←/→. Because\n * an inactive slide's badge travels with its node, and the active slide's\n * thumbnail button carries its own always-present badge, a placeholder page\n * still shows its \"unfilled\" mark in both places even though only one copy\n * of the slide's markup ever exists.\n *\n * Audit overlay + annotations (notes+preview wave, task 2): `buildPreviewHtml`\n * is still a pure renderer — `findings` (`../svg/audit/deck-audit.ts`'s\n * `AuditFinding`, reshaped locally as {@link PreviewHtmlFinding} so this file\n * still has no `../ir`/`../svg` import) and the placeholder-skip\n * {@link PreviewHtmlInput.auditNote} both arrive as plain input, the same way\n * `slides` already does; the caller (`runPreview`, `./commands.ts`) decides\n * *whether* to run `auditDeck` at all (skipped whenever the deck has any\n * placeholder page — a placeholder has nothing to audit and `auditDeck`\n * itself silently skips it, so surfacing a half-audited deck as if it were\n * clean would be misleading; the plan's contract is \"any placeholder present\n * → skip the whole overlay, one-line notice instead\"). Per-page finding\n * counts (thumbnail/stage badges) and the findings panel are rendered as\n * static markup at build time, not computed by client-side JS from the\n * embedded JSON — `findings` is known up front here, so there is nothing for\n * the browser to compute; the embedded `<script type=\"application/json\"\n * id=\"pf-audit-findings\">` blob exists only so a saved `preview.html` still\n * carries the structured findings for later tooling, not to drive the UI.\n * User content still flows through {@link escapeHtml} everywhere it lands in\n * HTML (a finding's `message` embeds a truncated quote of the offending\n * slide's own text) — the JSON blob instead goes through {@link embedJson},\n * which additionally neutralizes any literal `</script` sequence a slide's\n * text could contain (escaping every `<` to its unicode escape — valid\n * inside a JSON string, and the only character the HTML tokenizer would\n * otherwise use to end the `<script>` element early), the standard technique\n * for safely inlining untrusted JSON into a script tag.\n *\n * Annotations were removed on 2026-08-16, along with the \"Export revision\n * requests\" download and the `window.__pptwiseBuildExportBlob` seam that\n * `pptwise serve` used to POST the same payload to disk. The page shows the\n * deck and nothing else: a reviewer who wants something changed says so in\n * the conversation, usually with a screenshot, which reaches the agent\n * faster than typing into a panel whose output then has to be exported and\n * read back. `preview-html.test.ts` pins the absence so it cannot return as\n * a half-feature.\n */\n\nexport interface PreviewHtmlSlideInput {\n /** Authoritative page number (1-based labels derive from this, not array\n * position, so a caller that filters/reorders `slides` still gets\n * correct output). */\n index: number\n /** Stable slide id (`slide.id`, `../ir/index.ts`'s `SlideSchema`) when the\n * deck sets one. User content — HTML-escaped wherever it is shown. */\n id?: string\n /** `slide.type` — `cover`/`chapter`/`content`/`ending` in practice, kept\n * as a plain `string` here so this module has no dependency on `../ir`. */\n type: string\n /** Already-rendered standalone SVG markup for this slide (`renderSlideSvg`,\n * `../api.ts`) — embedded verbatim: trusted, self-produced markup, never\n * escaped (escaping it would corrupt the SVG/XML syntax itself). */\n svg: string\n /** `slide.placeholder` — an unfilled page (assemble's stand-in for content\n * nobody has written yet, W5 task 1). Renders a visible \"unfilled\" badge:\n * this bundle exists for a human/agent visual review, so an unfilled page\n * must never look indistinguishable from a finished one. */\n placeholder?: boolean\n}\n\n/**\n * One `auditDeck` finding (`AuditFinding`, `../svg/audit/deck-audit.ts`),\n * reshaped to this module's own minimal fields only — dropping `detail`\n * (never shown) keeps this file dependency-free of `../svg` the same way it\n * is already dependency-free of `../ir` (see the module doc comment). `page`\n * is 1-based, matching `PreviewHtmlSlideInput.index + 1` for the slide it\n * belongs to (both ultimately trace back to the same `ir.slides` array\n * position in `runPreview`, `./commands.ts`). User content — `message`\n * embeds a truncated quote of the offending slide's own text — HTML-escaped\n * wherever it is shown, same as every other user-content field in this file.\n */\nexport interface PreviewHtmlFinding {\n page: number\n slideId?: string\n code: string\n message: string\n}\n\n/**\n * `AuditChecks` (`../svg/audit/deck-audit.ts`), reshaped locally the same\n * way `findings` is reshaped to {@link PreviewHtmlFinding} — keeps this file\n * free of a `../svg` import (see the module doc comment). Literal state\n * words only, mirroring the source type exactly: this wave's soul\n * constraint is \"not checked must never read as passed\", so `pixels` being\n * `\"not-requested\"` has to survive unchanged all the way into the rendered\n * line — no checkmark, no \"passed\"/\"ok\" substitute that could be misread as\n * a completed pixel pass.\n */\nexport interface PreviewHtmlChecks {\n svg: \"completed\"\n pixels: \"not-requested\" | \"completed\"\n}\n\nexport interface PreviewHtmlInput {\n /** Deck title (`ir.filename`) — shown in the `<title>` tag and the header.\n * User content — HTML-escaped wherever it is shown. */\n title: string\n slides: PreviewHtmlSlideInput[]\n /** `auditDeck(ir).findings` (`../svg/audit/deck-audit.ts`), reshaped to\n * {@link PreviewHtmlFinding} — omit or pass `[]` when the caller skipped\n * the audit (no findings to show at all, e.g. the deck has a placeholder\n * page, see {@link auditNote}) or the deck audited clean. Drives the\n * thumbnail/stage finding-count badges and the findings panel — see the\n * module doc comment for why those are rendered as static markup here\n * rather than computed by client-side JS from the embedded JSON blob. */\n findings?: PreviewHtmlFinding[]\n /** One-line notice shown in the header in place of any findings UI — the\n * plan's placeholder-skip contract: `runPreview` sets this (and passes no\n * `findings`) whenever the deck has any placeholder page, since\n * `auditDeck` itself silently skips a placeholder (nothing to audit) and\n * showing a placeholder-heavy deck as audit-clean would be misleading.\n * User content only in the sense that it is caller-supplied prose, not\n * deck content — HTML-escaped like everything else in this file\n * regardless. */\n auditNote?: string\n /** `auditDeck(...).checks` (`../svg/audit/deck-audit.ts`), reshaped to\n * {@link PreviewHtmlChecks} — omit whenever the caller skipped the audit\n * (the same condition {@link auditNote}/{@link findings} already use: a\n * deck with a placeholder page never calls `auditDeck` at all, so there\n * is nothing to report here either). Rendered as one line, independent of\n * `findings.length` — a clean 0-finding report and a report where\n * `pixels` never ran would otherwise look identical once the findings\n * panel is empty in both cases, and the whole point of surfacing `checks`\n * here is to keep that distinction visible even then. */\n checks?: PreviewHtmlChecks\n}\n\nimport { slideEdgeFill } from \"../lib/slide-edge\"\nimport { namespaceSvgIds, svgIdPrefix } from \"../lib/svg-ids\"\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n}\n\n/**\n * `JSON.stringify(value)`, with every `<` escaped to `<` — safe to\n * inline inside a `<script>` element's text content (this file's\n * `#pf-audit-findings` data blob): the HTML tokenizer looks only for the\n * literal byte sequence `</script` to end a script element, regardless of\n * the script's `type` or of what its content actually parses as, so a\n * finding `message` that happens to contain that substring (it embeds a\n * truncated quote of the offending slide's own text — user content) could\n * otherwise truncate the document early. `<` never appears in JSON outside a\n * string value (the syntax has no structural use for it), so this blanket\n * replace only ever touches characters that were already inside string\n * content — the standard technique for embedding untrusted JSON in a page.\n */\nfunction embedJson(value: unknown): string {\n return JSON.stringify(value).replace(/</g, \"\\\\u003c\")\n}\n\n/** `\"3\"` (a finding-count badge) or `\"\"` when the slide has no findings —\n * shared by {@link slideNode} (the moving node's own badge) and\n * {@link thumbButton} (the thumbnail's always-present one), same\n * \"one class for the node's own copy, one for the thumbnail button's\"\n * split the existing `pf-badge`/`pf-thumb-badge` pair already uses. In\n * practice a slide never carries both this badge and the \"unfilled\" one —\n * `runPreview` only ever passes non-empty `findings` when *no* slide in the\n * deck is a placeholder (the plan's skip-the-whole-overlay contract, see\n * `PreviewHtmlInput.auditNote`'s doc comment) — but nothing here assumes\n * that invariant: the two badges use different classes and corners (this\n * one top-left, \"unfilled\" top-right) specifically so a caller that did\n * pass both for one slide would still render two distinct, non-overlapping\n * marks rather than a garbled stack. */\nfunction findingBadge(count: number, className: string): string {\n if (count === 0) return \"\"\n return `<div class=\"${className}\" aria-hidden=\"true\">${count}</div>`\n}\n\n/** The one `.pf-slide` node this slide will ever have — moved between\n * `#pf-stage` and its `.pf-thumb-slot` by `<script>` at runtime, never\n * duplicated (see this module's own doc comment). Carries its own\n * \"unfilled\" badge so the badge travels with it wherever it currently is,\n * plus (independently) a finding-count badge when `findingCount > 0`. */\nfunction slideNode(slide: PreviewHtmlSlideInput, findingCount: number): string {\n const idAttr = slide.id !== undefined ? ` data-id=\"${escapeHtml(slide.id)}\"` : \"\"\n const badge = slide.placeholder ? `<div class=\"pf-badge\" aria-hidden=\"true\">unfilled</div>` : \"\"\n const fBadge = findingBadge(findingCount, \"pf-finding-badge\")\n // Every slide's SVG lands in one shared document here, so its internal\n // ids get a per-slide namespace first — otherwise a `url(#…)` on a later\n // slide resolves against an earlier slide's definition of the same id.\n // See `../lib/svg-ids.ts` for the defect this prevents.\n const svg = namespaceSvgIds(slide.svg, svgIdPrefix(slide.index))\n // What the box under this slide must be painted with while the slide sits\n // on it. `<script>` reads it back on every stage change; the thumbnail slot\n // gets the same value as a static inline style. Leaving the box its own\n // neutral grey leaves a pale hairline down the page edge — see\n // `../lib/slide-edge.ts` for the measurement.\n const edgeAttr = edgeAttribute(slide, \"data-edge\")\n return `<div class=\"pf-slide\" id=\"pf-slide-${slide.index}\" data-index=\"${slide.index}\"${idAttr}${edgeAttr}>${badge}${fBadge}${svg}</div>`\n}\n\n/** ` data-edge=\"#1F1C18\"` or ` style=\"background:#1F1C18\"`, and `\"\"` when the\n * slide's edge has no single answer (a photo background) and the box should\n * keep whatever neutral it already had. */\nfunction edgeAttribute(slide: PreviewHtmlSlideInput, name: \"data-edge\" | \"style\"): string {\n const edge = slideEdgeFill(slide.svg)\n if (!edge) return \"\"\n return ` ${name}=\"${escapeHtml(name === \"style\" ? `background:${edge}` : edge)}\"`\n}\n\n/** `\"slide 3 · content · p-body · unfilled\"` — shared by the thumbnail\n * button's `title`/`aria-label` (raw pieces joined, then escaped once —\n * escaping each piece separately and joining after would be equally\n * correct, but this reads simpler and is exactly as safe). */\nfunction thumbDescription(slide: PreviewHtmlSlideInput): string {\n const parts = [`slide ${slide.index + 1}`, slide.type]\n if (slide.id !== undefined) parts.push(slide.id)\n if (slide.placeholder) parts.push(\"unfilled\")\n return escapeHtml(parts.join(\" · \"))\n}\n\n/** `\"3 · p-body\"` (or just `\"3\"` without an id) — the thumbnail's small\n * printed label, and (via {@link counterText}) the page counter's format. */\nfunction positionLabel(slide: PreviewHtmlSlideInput): string {\n const idPart = slide.id !== undefined ? ` · ${slide.id}` : \"\"\n return escapeHtml(`${slide.index + 1}${idPart}`)\n}\n\n/** `\"1 / 8 · p-cover\"` — the page counter's initial text (rendered directly\n * into the static markup so it is correct even before `<script>` runs;\n * `<script>`'s own `updateCounter()` keeps it in sync after that). */\nfunction counterText(slide: PreviewHtmlSlideInput, total: number): string {\n const idPart = slide.id !== undefined ? ` · ${slide.id}` : \"\"\n return escapeHtml(`${slide.index + 1} / ${total}${idPart}`)\n}\n\n/** One always-present thumbnail button. `slotContent` is the slide's own\n * {@link slideNode} markup when this slide starts inactive (every slide but\n * the first), or `\"\"` when it starts active (the first slide — its node\n * lives in `#pf-stage` instead, see {@link buildPreviewHtml}). Either way\n * the button itself, its label, its own \"unfilled\" badge (if the slide is a\n * placeholder), and its own finding-count badge (if `findingCount > 0`) are\n * always rendered — only the slot's content moves. */\nfunction thumbButton(slide: PreviewHtmlSlideInput, isActive: boolean, slotContent: string, findingCount: number): string {\n const description = thumbDescription(slide)\n const badge = slide.placeholder ? `<span class=\"pf-thumb-badge\" aria-hidden=\"true\">unfilled</span>` : \"\"\n const fBadge = findingBadge(findingCount, \"pf-thumb-finding-badge\")\n return (\n `<button type=\"button\" class=\"pf-thumb${isActive ? \" pf-thumb-active\" : \"\"}\" id=\"pf-thumb-${slide.index}\" ` +\n `data-index=\"${slide.index}\" title=\"${description}\" aria-label=\"${description}\">` +\n `<span class=\"pf-thumb-slot\" id=\"pf-slot-${slide.index}\"${edgeAttribute(slide, \"style\")}>${slotContent}</span>` +\n `<span class=\"pf-thumb-label\">${positionLabel(slide)}</span>` +\n `${badge}${fBadge}</button>`\n )\n}\n\n/** One row in the audit findings panel — `data-page-index` is the finding's\n * owning slide's 0-based array index (`f.page - 1`, `PreviewHtmlFinding.page`\n * is 1-based), the same identity `<script>`'s existing `activate(i)` already\n * navigates by, so a click just calls the same function every thumbnail\n * click already does. */\nfunction findingPanelEntry(f: PreviewHtmlFinding): string {\n const idPart = f.slideId !== undefined ? ` · ${escapeHtml(f.slideId)}` : \"\"\n return (\n `<button type=\"button\" class=\"pf-finding\" data-page-index=\"${f.page - 1}\">` +\n `<span class=\"pf-finding-loc\">page ${f.page}${idPart}</span>` +\n `<span class=\"pf-finding-code\">[${escapeHtml(f.code)}]</span> ` +\n `<span class=\"pf-finding-msg\">${escapeHtml(f.message)}</span>` +\n `</button>`\n )\n}\n\nconst CSS = `\n:root{\n --bg:#f4f4f2;--panel:#fff;--line:#d9d9d4;--ink:#1b1b19;--ink-dim:#6d6d66;\n --stage:#e8e8e4;--warn:#9a6b16;--bad:#a8342d;--radius:10px;\n color-scheme:light\n}\nbody[data-surround=\"dark\"]{\n --bg:#17181a;--panel:#1f2124;--line:#34373c;--ink:#e9eaec;--ink-dim:#9195a0;--stage:#101113;\n color-scheme:dark\n}\n*{box-sizing:border-box}\nhtml,body{height:100%;margin:0}\nbody{display:flex;flex-direction:column;background:var(--bg);color:var(--ink);\n font:14px/1.55 -apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",sans-serif;\n -webkit-font-smoothing:antialiased}\n\nheader{display:flex;align-items:center;gap:14px;padding:11px 18px;background:var(--panel);\n border-bottom:1px solid var(--line);flex:0 0 auto}\n#pf-title{font-weight:650;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#pf-counter{font-variant-numeric:tabular-nums;color:var(--ink-dim);white-space:nowrap;font-size:13px}\n.pf-spacer{flex:1 1 auto}\n#pf-audit-note{color:var(--warn);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:46ch}\n#pf-audit-checks{color:var(--ink-dim);font-size:12px;white-space:nowrap}\n\n.pf-seg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;flex:0 0 auto}\n.pf-seg button{appearance:none;border:0;border-right:1px solid var(--line);background:transparent;color:var(--ink);\n font:inherit;font-size:13px;padding:4px 10px;cursor:pointer}\n.pf-seg button:last-child{border-right:0}\n.pf-seg button[aria-pressed=\"true\"]{background:var(--ink);color:var(--panel)}\n.pf-seg button:hover:not([aria-pressed=\"true\"]){background:var(--stage)}\n\n/* container-type:size so the stage below can ask this box how tall it is\n instead of guessing. Legal here because the box's own size never depends on\n its contents: it is a flex item stretched to the body's width and given the\n leftover height by flex:1 1 auto. */\n#pf-stage-wrap{container-type:size;flex:1 1 auto;min-height:0;display:flex;align-items:center;\n justify-content:center;gap:16px;padding:18px}\n/* The stage has to fit both ways: as wide as its height allows, never wider\n than the room left beside the findings panel. Sizing it off its height and\n capping the width is the only arrangement that holds in both directions.\n Measured, not reasoned about: height-driven rules (height:100%) overflow\n when width is the tighter axis, and width-driven ones\n (width:100%;max-height:100%) stop being 16:9 when height is.\n 100cqh is the wrap's real content height. It used to be 100vh - 210px, a\n guess at what the header and filmstrip take. Guess high and the slide comes\n out smaller than the window allows; guess low and the box comes out wider\n than 16:9, which aspect-ratio cannot correct once both width and max-height\n are set -- the slide then letterboxes inside its own stage and paints a grey\n bar down each side. The first declaration is that old guess, left in as the\n fallback a browser without container query units will land on. */\n#pf-stage{position:relative;background:var(--stage);box-shadow:0 10px 40px rgba(0,0,0,.18);\n aspect-ratio:16/9;max-height:100%;\n width:min(100%,calc((100vh - 210px) * 16 / 9));\n width:min(100%,calc(100cqh * 16 / 9))}\n#pf-stage,.pf-thumb-slot{position:relative}\n.pf-slide{position:absolute;inset:0}\n.pf-slide svg{display:block;width:100%;height:100%}\n\n#pf-side{flex:0 0 250px;align-self:stretch;overflow-y:auto;background:var(--panel);\n border:1px solid var(--line);border-radius:var(--radius);padding:12px 13px;font-size:13px}\n#pf-side h2{margin:0 0 8px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-dim)}\n.pf-finding{display:block;width:100%;text-align:left;background:transparent;border:1px solid var(--line);\n border-radius:8px;padding:7px 9px;margin-bottom:7px;cursor:pointer;font:inherit;color:var(--ink)}\n.pf-finding:hover{background:var(--stage)}\n.pf-finding-loc{display:block;font-size:11px;color:var(--ink-dim)}\n.pf-finding-code{display:inline-block;font-size:11px;font-weight:700;color:var(--bad)}\n.pf-finding-msg{display:block;font-size:12px;color:var(--ink-dim);margin-top:2px}\n\n/* The strip is cut off by its own right edge when it scrolls, which reads as\n a clipping bug rather than as \"there is more this way\" -- the last thumbnail\n is simply sliced, and inside an embedder's rounded frame it is sliced on a\n curve. The two widths are driven from script (see fadeStrip) and are 0 until\n there is really something hidden on that side, so a strip that fits is\n untouched: at 0 the gradient is opaque from edge to edge. */\n#pf-filmstrip{display:flex;gap:10px;padding:11px 18px;overflow-x:auto;background:var(--panel);\n border-top:1px solid var(--line);flex:0 0 auto;--pf-fade-l:0px;--pf-fade-r:0px;\n -webkit-mask-image:linear-gradient(to right,transparent 0,#000 var(--pf-fade-l),\n #000 calc(100% - var(--pf-fade-r)),transparent 100%);\n mask-image:linear-gradient(to right,transparent 0,#000 var(--pf-fade-l),\n #000 calc(100% - var(--pf-fade-r)),transparent 100%)}\n.pf-thumb{flex:0 0 auto;width:154px;padding:0;margin:0;border:1px solid var(--line);background:var(--panel);\n cursor:pointer;border-radius:8px;overflow:hidden;position:relative;font:inherit;text-align:left}\n.pf-thumb:hover{border-color:var(--ink-dim)}\n.pf-thumb-active,.pf-thumb-active:hover{border-color:var(--ink);box-shadow:0 0 0 1px var(--ink)}\n.pf-thumb-slot{display:block;width:100%;aspect-ratio:16/9;background:var(--stage)}\n/* The active slide's SVG lives in the stage, not here — one node per slide,\n moved between the two homes. Without this the vacated slot reads as a\n broken empty box, so it states what it is instead. */\n.pf-thumb-active .pf-thumb-slot::after{content:\"on stage\";position:absolute;inset:0;display:flex;\n align-items:center;justify-content:center;font-size:10px;letter-spacing:.08em;text-transform:uppercase;\n color:var(--ink-dim)}\n.pf-thumb-label{display:block;font-size:11px;line-height:1.5;padding:4px 7px;color:var(--ink-dim);\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.pf-badge,.pf-thumb-badge{position:absolute;top:5px;right:5px;background:var(--warn);color:#fff;font-size:10px;\n font-weight:700;letter-spacing:.03em;padding:2px 6px;border-radius:999px;text-transform:uppercase;z-index:2;pointer-events:none}\n.pf-finding-badge,.pf-thumb-finding-badge{position:absolute;top:5px;left:5px;background:var(--bad);color:#fff;\n font-size:10px;font-weight:700;padding:2px 6px;border-radius:999px;z-index:2;pointer-events:none}\n`.trim()\n\nconst JS = `\n(function () {\n var stage = document.getElementById('pf-stage')\n var counter = document.getElementById('pf-counter')\n var thumbs = Array.prototype.slice.call(document.querySelectorAll('.pf-thumb'))\n var total = thumbs.length\n if (total === 0) return\n var current = parseInt(thumbs[0].getAttribute('data-index'), 10)\n\n function slideEl(i) { return document.getElementById('pf-slide-' + i) }\n function slotEl(i) { return document.getElementById('pf-slot-' + i) }\n function thumbEl(i) { return document.getElementById('pf-thumb-' + i) }\n function thumbPos(i) {\n for (var t = 0; t < thumbs.length; t++) {\n if (parseInt(thumbs[t].getAttribute('data-index'), 10) === i) return t\n }\n return -1\n }\n\n function updateCounter(i) {\n var el = slideEl(i)\n if (!el) return\n var text = (thumbPos(i) + 1) + ' / ' + total\n var id = el.getAttribute('data-id')\n if (id) text += ' · ' + id\n counter.textContent = text\n }\n\n function activate(i) {\n if (i === current) return\n var nextSlide = slideEl(i)\n var nextThumb = thumbEl(i)\n if (!nextSlide || !nextThumb) return\n var prevSlide = slideEl(current)\n var prevSlot = slotEl(current)\n if (prevSlide && prevSlot) prevSlot.appendChild(prevSlide)\n var prevThumb = thumbEl(current)\n if (prevThumb) prevThumb.classList.remove('pf-thumb-active')\n stage.appendChild(nextSlide)\n stage.style.background = nextSlide.getAttribute('data-edge') || ''\n nextThumb.classList.add('pf-thumb-active')\n nextThumb.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n current = i\n updateCounter(i)\n }\n\n thumbs.forEach(function (t) {\n t.addEventListener('click', function () {\n activate(parseInt(t.getAttribute('data-index'), 10))\n })\n })\n\n document.addEventListener('keydown', function (e) {\n var pos = thumbPos(current)\n if (e.key === 'ArrowRight' && pos < total - 1) {\n activate(parseInt(thumbs[pos + 1].getAttribute('data-index'), 10))\n } else if (e.key === 'ArrowLeft' && pos > 0) {\n activate(parseInt(thumbs[pos - 1].getAttribute('data-index'), 10))\n }\n })\n\n // Click a finding, jump to its page — the same activate() a thumbnail uses.\n Array.prototype.slice.call(document.querySelectorAll('.pf-finding')).forEach(function (b) {\n b.addEventListener('click', function () {\n activate(parseInt(b.getAttribute('data-page-index'), 10))\n })\n })\n\n // Fade whichever end of the strip has more thumbnails behind it. Driven\n // from here rather than left to CSS because no CSS rule can ask whether a\n // box currently overflows. See the #pf-filmstrip rule for why it matters.\n var strip = document.getElementById('pf-filmstrip')\n function fadeStrip() {\n if (!strip || thumbs.length === 0) return\n // Measured off the thumbnails, not off scrollWidth. The strip's own\n // 18px of padding counts as scrollable width, so scrollWidth says there\n // is more to the right while a reader is already looking at the last\n // thumbnail — and activate()'s scrollIntoView stops exactly there,\n // leaving the padding unscrolled. The fade would then claim there is\n // more to see and dim the selected thumbnail's own ring to do it. Asking\n // whether a thumbnail is actually cut off is what the fade means anyway.\n var box = strip.getBoundingClientRect()\n var hiddenLeft = box.left - thumbs[0].getBoundingClientRect().left\n var hiddenRight = thumbs[thumbs.length - 1].getBoundingClientRect().right - box.right\n // A pixel of slack: sub-pixel layout leaves a fraction of overflow on\n // strips that visibly fit, and fading those is the bug in reverse.\n strip.style.setProperty('--pf-fade-l', (hiddenLeft > 1 ? 28 : 0) + 'px')\n strip.style.setProperty('--pf-fade-r', (hiddenRight > 1 ? 28 : 0) + 'px')\n }\n if (strip) {\n fadeStrip()\n strip.addEventListener('scroll', fadeStrip, { passive: true })\n // The strip also stops and starts overflowing as the window changes width,\n // and scrollIntoView moves it without a resize.\n window.addEventListener('resize', fadeStrip)\n if (window.ResizeObserver) new ResizeObserver(fadeStrip).observe(strip)\n }\n\n // Open on a given page: #page=3 is the third thumbnail, not slide index 3.\n // A reader who clicks page 3 in a harness that embeds this file expects to\n // land on page 3, and the embedder has no other way to say so — it holds a\n // URL, not a handle on this script. Reading the position rather than the\n // index keeps that promise honest for a deck whose slides are not numbered\n // 0..n-1. Silently ignored when it names a page this deck does not have.\n function fromHash() {\n var m = /(?:^|[#&])page=(\\\\d+)/.exec(location.hash || '')\n if (!m) return\n var pos = parseInt(m[1], 10) - 1\n // >= 0, not > 0. Page 1 is a no-op on first load, but not afterwards: a\n // reader who pages forward and then hits Back gets #page=1 in the URL,\n // and skipping it leaves the address bar and the deck disagreeing.\n // activate() already returns early when it is handed the current page.\n if (pos >= 0 && pos < total) activate(parseInt(thumbs[pos].getAttribute('data-index'), 10))\n }\n fromHash()\n window.addEventListener('hashchange', fromHash)\n\n // Light/dark surround. A deck is judged on color and weight, and the\n // surround it sits on changes both — a dark theme reads muddy on a light\n // page and vice versa, so the reviewer picks rather than the page deciding.\n var seg = document.getElementById('pf-surround')\n if (seg) {\n seg.addEventListener('click', function (e) {\n var btn = e.target.closest('button')\n if (!btn) return\n document.body.setAttribute('data-surround', btn.getAttribute('data-surround'))\n Array.prototype.slice.call(seg.children).forEach(function (b) {\n b.setAttribute('aria-pressed', String(b === btn))\n })\n })\n }\n})()\n`.trim()\n\n\n/**\n * Build the self-contained `preview.html` bundle. Pure — no `fs`, safe to\n * unit-test directly (`./preview-html.test.ts`). See this module's own doc\n * comment for the self-containment and single-embed-per-slide design notes.\n */\nexport function buildPreviewHtml(input: PreviewHtmlInput): string {\n const { title, slides, findings = [], auditNote, checks } = input\n const total = slides.length\n const escapedTitle = escapeHtml(title)\n\n // Group findings by the 1-based page number they belong to, so each\n // slide's badge count is a single map lookup rather than an O(findings)\n // scan per slide.\n const findingsByPage = new Map<number, PreviewHtmlFinding[]>()\n for (const f of findings) {\n const list = findingsByPage.get(f.page)\n if (list) list.push(f)\n else findingsByPage.set(f.page, [f])\n }\n const countFor = (slide: PreviewHtmlSlideInput) => findingsByPage.get(slide.index + 1)?.length ?? 0\n\n const stageSlide = total > 0 ? slideNode(slides[0]!, countFor(slides[0]!)) : \"\"\n // The first slide starts on the stage, so its edge paint has to be in the\n // static markup — `<script>` only repaints from the next change onward.\n const stageEdge = total > 0 ? edgeAttribute(slides[0]!, \"style\") : \"\"\n const thumbs = slides\n .map((s, i) => thumbButton(s, i === 0, i === 0 ? \"\" : slideNode(s, countFor(s)), countFor(s)))\n .join(\"\")\n const initialCounter = total > 0 ? counterText(slides[0]!, total) : \"0 / 0\"\n\n // Findings panel + embedded JSON blob (see this module's own doc comment\n // for why the panel is static markup, not client-computed from the blob)\n // — both entirely omitted when there is nothing to show, so a clean or\n // audit-skipped deck's preview.html carries no trace of either (matches\n // the pre-existing \"never shows an 'unfilled' badge when no slide is a\n // placeholder\" precedent for the badge markup itself).\n const auditPanel =\n findings.length > 0\n ? `<section id=\"pf-audit-panel\"><h2>Audit findings (${findings.length})</h2><div id=\"pf-audit-list\">${findings.map(findingPanelEntry).join(\"\")}</div></section>`\n : \"\"\n const findingsDataScript =\n findings.length > 0\n ? `<script type=\"application/json\" id=\"pf-audit-findings\">${embedJson(findings)}</script>`\n : \"\"\n const auditNoteHtml = auditNote !== undefined ? `<span id=\"pf-audit-note\">${escapeHtml(auditNote)}</span>` : \"\"\n\n // One-line \"which check families actually ran\" summary (fix round,\n // Important-1: the task brief's own scope for this wave, missed in the\n // first pass) — independent of `auditPanel`'s `findings.length > 0` gate\n // on purpose, see `PreviewHtmlInput.checks`'s own doc comment for why.\n // Renders the literal state words straight out of `PreviewHtmlChecks`\n // (`\"completed\"` / `\"not-requested\"`) with no checkmark/tick substitute —\n // the soul constraint this whole wave is built on is \"not checked must\n // never read as passed\", and a glyph here would be exactly that misread\n // for a `pixels: \"not-requested\"` report.\n const checksLine =\n checks !== undefined\n ? `<span id=\"pf-audit-checks\">audit: svg ${checks.svg} · pixels ${checks.pixels}</span>`\n : \"\"\n\n // The findings rail only exists when there is something in it. An empty\n // panel used to sit there taking a quarter of the width on every clean\n // deck, which is most of them.\n const sideHtml = auditPanel ? `<aside id=\"pf-side\">${auditPanel}</aside>` : \"\"\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapedTitle} — pptwise preview</title>\n<style>${CSS}</style>\n</head>\n<body data-surround=\"light\">\n<header>\n<span id=\"pf-title\">${escapedTitle}</span>\n<span id=\"pf-counter\">${initialCounter}</span>\n<span class=\"pf-spacer\"></span>\n${auditNoteHtml}\n${checksLine}\n<div class=\"pf-seg\" id=\"pf-surround\" role=\"group\" aria-label=\"surround\">\n<button type=\"button\" data-surround=\"light\" aria-pressed=\"true\">Light</button>\n<button type=\"button\" data-surround=\"dark\" aria-pressed=\"false\">Dark</button>\n</div>\n</header>\n<div id=\"pf-stage-wrap\"><div id=\"pf-stage\"${stageEdge}>${stageSlide}</div>${sideHtml}</div>\n<nav id=\"pf-filmstrip\" aria-label=\"slides\">${thumbs}</nav>\n${findingsDataScript}\n<script>${JS}</script>\n</body>\n</html>\n`\n}\n","/**\n * The machine-readable half of a preview bundle.\n *\n * `pptwise preview` has always written per-slide SVG files and a\n * self-contained `preview.html`. Both are for eyes: one needs a file browser\n * to make sense of, the other needs a browser window. Neither tells a\n * *program* what it is looking at — which page is which, what the slide\n * dimensions are, which pages the audit flagged — so every consumer that\n * wanted to show a deck had to either re-render it or scrape the HTML.\n *\n * `manifest.json` is that missing piece, and it is deliberately small: a\n * flat page list with stable ids, the file each page lives in, and the\n * audit findings already computed for it. A harness with its own UI reads\n * this and draws whatever it likes; a harness without one opens the HTML\n * next to it; nothing has to re-implement the renderer to do either. That\n * split — one producer, several consumers, no second rendering path — is\n * the whole point, and it is why this file describes files on disk rather\n * than embedding anything itself.\n *\n * Pure data assembly, no `fs`: `runPreview` (`./commands.ts`) writes it, the\n * same division of labour `./preview-html.ts` already keeps.\n */\n\n/** Schema identifier, bumped when a consumer would need to change. */\nexport const PREVIEW_MANIFEST_VERSION = 1 as const\n\nexport interface PreviewManifestPage {\n /**\n * Stable, filename-safe page id.\n *\n * Derived from the deck's own slide id when it has one, else from the\n * page number — never from array position alone, so a consumer holding a\n * reference to a page (a selection, a comment, a scroll position) keeps\n * it across a re-render that did not change that page.\n */\n readonly id: string\n /** 1-based page number, matching the label the preview UI shows. */\n readonly page: number\n /** `slide.type` — cover / chapter / content / ending. */\n readonly type: string\n /** Path to this page's SVG, relative to the manifest. */\n readonly file: string\n /** `slide.id` when the deck sets one. */\n readonly slideId?: string\n /** True for an unfilled page — never let one pass for finished work. */\n readonly placeholder?: boolean\n /** What the deterministic auditor found on this page, if it ran. */\n readonly findings?: readonly { readonly code: string; readonly message: string }[]\n}\n\nexport interface PreviewManifest {\n readonly manifestVersion: typeof PREVIEW_MANIFEST_VERSION\n readonly generator: \"pptwise preview\"\n readonly pptwiseVersion: string\n readonly title: string\n /** Render canvas in px — every page shares it. */\n readonly slide: { readonly width: number; readonly height: number }\n /** Present only when the audit actually ran; absent is not \"clean\". */\n readonly checks?: { readonly svg: string; readonly pixels: string }\n /**\n * Why the audit was skipped, when it was. A consumer must be able to tell\n * \"audited, found nothing\" from \"never audited\" — showing the second as\n * the first is the misread this whole surface is built to avoid.\n */\n readonly auditNote?: string\n readonly pages: readonly PreviewManifestPage[]\n}\n\nexport interface PreviewManifestSlideInput {\n readonly index: number\n readonly type: string\n readonly id?: string\n readonly placeholder?: boolean\n readonly file: string\n}\n\nexport interface PreviewManifestInput {\n readonly title: string\n readonly pptwiseVersion: string\n readonly width: number\n readonly height: number\n readonly slides: readonly PreviewManifestSlideInput[]\n readonly findings?: readonly { page: number; code: string; message: string }[]\n readonly checks?: { svg: string; pixels: string }\n readonly auditNote?: string\n}\n\n/** Filename-safe id from a slide id, falling back to the page number. */\nfunction pageSlug(slide: PreviewManifestSlideInput): string {\n const raw = slide.id?.trim()\n if (raw) {\n const safe = raw\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n if (safe) return safe\n }\n return `page-${String(slide.index + 1).padStart(3, \"0\")}`\n}\n\n/**\n * Page ids for a whole deck, guaranteed distinct.\n *\n * Slugging is lossy — `\"Q2 Cover\"` and `\"q2-cover\"` are two legal, distinct\n * slide ids that reduce to the same slug — and these ids are exactly what a\n * consumer anchors a selection, a comment or a scroll position to. Two pages\n * sharing one would send those to the wrong page, so a repeat falls back to\n * the page-number form, which cannot collide with anything.\n */\nfunction pageIds(slides: readonly PreviewManifestSlideInput[]): string[] {\n const taken = new Set<string>()\n const claim = (candidate: string): string => {\n if (!taken.has(candidate)) {\n taken.add(candidate)\n return candidate\n }\n // The fallback can itself be taken: a deck whose page 1 carries the\n // literal slide id \"page-002\" owns the very name page 2 would fall back\n // to. The first version of this stopped at one fallback and shipped a\n // duplicate anyway, with a test that only covered the easy collision.\n // Probing until free is the only version that can promise uniqueness.\n for (let n = 2; ; n++) {\n const suffixed = `${candidate}-${n}`\n if (!taken.has(suffixed)) {\n taken.add(suffixed)\n return suffixed\n }\n }\n }\n return slides.map((slide) => {\n const slug = pageSlug(slide)\n if (!taken.has(slug)) return claim(slug)\n return claim(`page-${String(slide.index + 1).padStart(3, \"0\")}`)\n })\n}\n\nexport function buildPreviewManifest(input: PreviewManifestInput): PreviewManifest {\n const byPage = new Map<number, { code: string; message: string }[]>()\n for (const f of input.findings ?? []) {\n const list = byPage.get(f.page) ?? []\n list.push({ code: f.code, message: f.message })\n byPage.set(f.page, list)\n }\n\n const ids = pageIds(input.slides)\n const pages: PreviewManifestPage[] = input.slides.map((slide, i) => {\n const findings = byPage.get(slide.index + 1) ?? []\n return {\n id: ids[i]!,\n page: slide.index + 1,\n type: slide.type,\n file: slide.file,\n ...(slide.id !== undefined ? { slideId: slide.id } : {}),\n ...(slide.placeholder ? { placeholder: true } : {}),\n ...(findings.length > 0 ? { findings } : {}),\n }\n })\n\n return {\n manifestVersion: PREVIEW_MANIFEST_VERSION,\n generator: \"pptwise preview\",\n pptwiseVersion: input.pptwiseVersion,\n title: input.title,\n slide: { width: input.width, height: input.height },\n ...(input.checks !== undefined ? { checks: input.checks } : {}),\n ...(input.auditNote !== undefined ? { auditNote: input.auditNote } : {}),\n pages,\n }\n}\n","/**\n * Workspace artifact root — where `pptwise render` / `pptwise preview` put\n * their output when the caller passes no `-o`.\n *\n * ```\n * <anchor>/.pptwise/\n * <deck-slug>/\n * preview.html preview --html (regenerable)\n * manifest.json preview --html (regenerable)\n * 001-cover.svg preview (regenerable)\n * 002-content.svg\n * <deck-slug>.pptx render (regenerable)\n * assets/ pinned stock photos (not regenerable)\n * hero.jpg\n * hero.json sidecar\n * ```\n *\n * Three properties this module exists to hold:\n *\n * 1. **One anchor rule, two lines long.** The anchor is the directory of the\n * nearest `pptwise.config.json` (`./config.ts`'s `findConfig` cwd walk-up\n * — the project root is already a concept this CLI has), else cwd. No git\n * root probing: a git root and a project root are not the same thing in a\n * monorepo, and a third rule would only be a third thing to remember.\n * 2. **Two zones live here.** Render output (pptx, preview.html, `NNN-*.svg`,\n * manifest.json) is regenerable: delete those files and re-run, they grow\n * back. Stock-photo assets (`.pptwise/<deck>/assets/` plus sidecars) are\n * pinned downloads, not garbage. Deleting the whole `.pptwise/` directory\n * drops those photos. Deck sources (`deck.spec.json`, `pages/`, project\n * `assets/`, `theme.json`) stay where the user put them.\n * 3. **The directory ignores itself, once.** The first time this CLI creates\n * `.pptwise/` it appends the entry to the repository's *local* exclude\n * file — never the shared `.gitignore`, which is the user's to write. See\n * {@link ensureGitIgnored} for the four ways that can go sideways and what\n * each one does instead.\n *\n * Everything here is fs- and process-facing, so it lives under `src/cli/`\n * (AGENTS.md's layout rule: Node-only code never enters `src/index.ts`'s\n * dependency closure).\n */\nimport { existsSync } from \"node:fs\"\nimport { appendFile, mkdir, readFile, readdir, stat, unlink } from \"node:fs/promises\"\nimport { basename, dirname, extname, join, resolve } from \"node:path\"\nimport { PptwiseError } from \"../errors\"\nimport { slugify } from \"../themes/brand-extract\"\nimport { runChild } from \"./child\"\nimport { ASSETS_DIRNAME, assertSafeFileSegment } from \"./deck-dir\"\n\n/** The default artifact root's directory name, relative to the anchor. A\n * project config's `outDir` (`./config.ts`) replaces it wholesale. */\nexport const WORKSPACE_DIRNAME = \".pptwise\"\nexport const LEGACY_WORKSPACE_DIRNAMES = [\".pptpress\", \".pptfast\"] as const\n\n/** The line appended to the local exclude file — a trailing slash so it only\n * ever matches a directory, and no leading slash so it matches at whatever\n * depth below the repository root the anchor happens to sit (a monorepo's\n * per-package project root is not the repository root). Fresh projects\n * write `.pptwise/`. A leftover `.pptpress/` or `.pptfast/` keeps that name. */\nexport const WORKSPACE_IGNORE_ENTRY = `${WORKSPACE_DIRNAME}/`\n\nfunction defaultWorkspaceDirname(anchor: string): string {\n if (existsSync(join(anchor, WORKSPACE_DIRNAME))) return WORKSPACE_DIRNAME\n for (const name of LEGACY_WORKSPACE_DIRNAMES) {\n if (existsSync(join(anchor, name))) return name\n }\n return WORKSPACE_DIRNAME\n}\n\nfunction ignoreEntryFor(dirname: string): string {\n return `${dirname}/`\n}\n\n/**\n * `preview`'s own per-slide SVG filenames: `NNN-<slide type>.svg`\n * (`runPreview` in `./commands.ts`). Slide *type* is what the name carries,\n * not slide *id*, so inserting, deleting, or retyping a page renames files —\n * an 8-page deck cut to 5 leaves `006-*.svg`..`008-*.svg` behind. That was\n * invisible while `-o` was mandatory and every run got a fresh directory;\n * with a fixed default directory it would accumulate silently, so\n * {@link pruneRenderedSvgs} clears them before each default-path write.\n * Deliberately narrow: it matches only names this CLI itself produces, so a\n * file a human dropped in the same directory is never touched.\n */\nexport const RENDERED_SVG_PATTERN = /^\\d{3}-[a-z-]+\\.svg$/\n\n/** Where a deck's artifacts go, fully resolved. {@link resolveWorkspaceLocation}\n * is pure — nothing here has touched the filesystem yet. */\nexport interface WorkspaceLocation {\n /** The project root the artifact root hangs off: the nearest\n * `pptwise.config.json`'s directory, else cwd. */\n anchor: string\n /** `<anchor>/.pptwise`, or the project config's `outDir` resolved against\n * the config file's own directory. */\n root: string\n /** `<root>/<slug>` — this deck's own subdirectory. */\n dir: string\n /** The deck's directory/file name, slugified. */\n slug: string\n /** True when {@link root} came from a project config's `outDir`. An\n * explicit `outDir` is the user having already said where artifacts go,\n * so the git-ignore step stays out of it (see {@link prepareWorkspaceDir}). */\n configured: boolean\n}\n\n/**\n * `<deck target> → <directory name>`. A deck project directory contributes\n * its directory name, a single IR file its filename without the extension,\n * and a bare deck name resolves to one or the other before it ever gets here\n * (`resolveDeckTarget`, `./deck-dir.ts`), so this only ever sees a real path.\n *\n * `slugify` already strips every character that could mean anything to a path\n * (it keeps `[a-z0-9-]` and nothing else), which makes an escape impossible by\n * construction; {@link assertSafeFileSegment} runs anyway, as the same\n * belt-and-braces posture `./deck-dir.ts` applies to every other id it joins\n * into a write path — a future change to either function then fails loudly\n * here instead of quietly writing outside the workspace.\n */\nexport function deckSlug(target: string, isDir: boolean): string {\n const base = basename(target)\n const name = isDir ? base : base.slice(0, base.length - extname(base).length)\n const slug = slugify(name, \"deck\")\n assertSafeFileSegment(slug, \"deck slug\")\n return slug\n}\n\n/** The artifact root hanging off an anchor, before a deck slug is known.\n * {@link inspectWorkspace} (doctor) uses this, because it reports the root\n * rather than any one deck's subdirectory. */\nexport function resolveWorkspaceRoot(opts: {\n cwd: string\n /** The nearest `pptwise.config.json`'s path (`findConfig`'s hit), or null. */\n projectConfigPath?: string | null\n /** That config's `outDir`, if it set one. Relative values resolve against\n * the config file's own directory — the same base `decksDir` already uses\n * (`./config.ts`), never the CLI's cwd. */\n outDir?: string\n}): Pick<WorkspaceLocation, \"anchor\" | \"root\" | \"configured\"> {\n const anchor = opts.projectConfigPath ? dirname(resolve(opts.projectConfigPath)) : resolve(opts.cwd)\n const configured = opts.outDir !== undefined\n const root =\n opts.outDir !== undefined ? resolve(anchor, opts.outDir) : join(anchor, defaultWorkspaceDirname(anchor))\n return { anchor, root, configured }\n}\n\nexport function resolveWorkspaceLocation(opts: {\n cwd: string\n projectConfigPath?: string | null\n outDir?: string\n /** The deck's resolved path (`loadDeckTarget`'s `resolvedTarget`). */\n target: string\n isDir: boolean\n}): WorkspaceLocation {\n const { anchor, root, configured } = resolveWorkspaceRoot(opts)\n const slug = deckSlug(opts.target, opts.isDir)\n return { anchor, root, dir: join(root, slug), slug, configured }\n}\n\n// ── git ignore ──────────────────────────────────────────────────────────\n\n/** One `git` invocation's result, or `null` when git could not be spawned at\n * all (no binary on PATH — a real possibility in a slim container). */\nexport interface GitResult {\n code: number\n stdout: string\n}\n\nexport type GitRunner = (args: string[], cwd: string) => Promise<GitResult | null>\n\nconst runGitDefault: GitRunner = async (args, cwd) => {\n try {\n const { code, stdout } = await runChild(\"git\", args, { cwd })\n return { code, stdout }\n } catch (error) {\n // A spawn failure's errno string (`\"ENOENT\"`) is \"there is no git here\",\n // which is not a failure — it is one of the four outcomes below.\n const code = (error as NodeJS.ErrnoException).code\n if (typeof code !== \"number\") return null\n throw error\n }\n}\n\n/**\n * What {@link ensureGitIgnored} did. Every variant is a normal outcome —\n * none of them ever stops a render.\n */\nexport type IgnoreOutcome =\n | { kind: \"already-ignored\" }\n /** Not a git repository (`check-ignore` exit 128), or no git binary. */\n | { kind: \"no-repo\" }\n | { kind: \"appended\"; path: string }\n /** The repository is real and the entry is not ignored, but the exclude\n * file could not be written (read-only `.git`, permissions, ...). */\n | { kind: \"failed\"; path: string; reason: string }\n\n/** What `git check-ignore` said, collapsed into the three outcomes doctor\n * and the exclude writer both need. `\"skipped\"` is doctor-only: a project\n * that set `outDir` opted out of this whole path, so we do not even ask. */\nexport type GitIgnoreStatus = \"ignored\" | \"not-ignored\" | \"not-a-repo\" | \"skipped\"\n\nexport async function gitIgnoreStatus(\n dir: string,\n entry: string,\n runGit: GitRunner = runGitDefault,\n): Promise<Exclude<GitIgnoreStatus, \"skipped\">> {\n // Keep a trailing slash if the caller passed one. A directory-only\n // gitignore rule (`.pptwise/`) does not match a *non-existent* path\n // without the slash — git cannot know that name would be a directory —\n // so stripping it made the first-create probe always look unignored.\n const check = await runGit([\"check-ignore\", \"-q\", \"--\", entry], dir)\n if (check === null) return \"not-a-repo\"\n if (check.code === 0) return \"ignored\"\n if (check.code === 1) return \"not-ignored\"\n return \"not-a-repo\"\n}\n\n/**\n * The three facts `pptwise doctor` prints about the workspace: the anchor,\n * the resolved artifact root, and whether git already ignores it. Read-only\n * — never creates a directory, never writes an exclude line.\n */\nexport async function inspectWorkspace(\n opts: {\n cwd: string\n projectConfigPath?: string | null\n outDir?: string\n runGit?: GitRunner\n },\n): Promise<{\n anchor: string\n root: string\n configured: boolean\n ignore: GitIgnoreStatus\n}> {\n const { anchor, root, configured } = resolveWorkspaceRoot(opts)\n if (configured) return { anchor, root, configured, ignore: \"skipped\" }\n const ignore = await gitIgnoreStatus(anchor, ignoreEntryFor(basename(root)), opts.runGit)\n return { anchor, root, configured, ignore }\n}\n\n/**\n * Append `entry` to the repository's local exclude file, unless git already\n * ignores it. Runs exactly once per artifact root, at the moment this CLI\n * creates it — see {@link prepareWorkspaceDir}.\n *\n * The four edge cases, each with a decided answer rather than a crash:\n *\n * - **exit 0** — already ignored, from anywhere (`.gitignore`, a previous\n * run's exclude line, `core.excludesFile`). Do nothing. This is also the\n * opt-out: a user who wants the whole team to share the rule writes it into\n * `.gitignore` themselves, and this code goes quiet forever after.\n * - **exit 1** — a repository, entry not ignored. Append.\n * - **exit 128, or no git binary** — not a repository (or no git at all).\n * There is nothing to accidentally commit into, so write the artifacts and\n * say nothing.\n * - **the append itself fails** — a read-only `.git`, a permissions problem.\n * Report it as a note and keep going. Refusing to render because a courtesy\n * ignore line could not be written would be the wrong trade by a mile.\n *\n * The exclude file's path comes from `git rev-parse --git-common-dir`, never\n * a hardcoded `.git/info/exclude`: inside a worktree or a submodule `.git` is\n * a *file* pointing elsewhere, and `--git-common-dir` is the one answer that\n * is right in all three shapes (plain clone, worktree, submodule). It can\n * come back relative to the cwd git ran in, hence the `resolve(dir, ...)`.\n */\nexport async function ensureGitIgnored(\n dir: string,\n entry: string,\n runGit: GitRunner = runGitDefault,\n): Promise<IgnoreOutcome> {\n const status = await gitIgnoreStatus(dir, entry, runGit)\n if (status === \"ignored\") return { kind: \"already-ignored\" }\n if (status === \"not-a-repo\") return { kind: \"no-repo\" }\n\n const common = await runGit([\"rev-parse\", \"--git-common-dir\"], dir)\n if (common === null || common.code !== 0 || common.stdout.trim() === \"\") return { kind: \"no-repo\" }\n const excludePath = join(resolve(dir, common.stdout.trim()), \"info\", \"exclude\")\n\n try {\n await mkdir(dirname(excludePath), { recursive: true })\n // A file that does not end in a newline would otherwise get the entry\n // glued onto its last line, silently changing that rule instead of\n // adding one.\n let existing = \"\"\n try {\n existing = await readFile(excludePath, \"utf8\")\n } catch {\n existing = \"\"\n }\n const lead = existing === \"\" || existing.endsWith(\"\\n\") ? \"\" : \"\\n\"\n await appendFile(excludePath, `${lead}${entry}\\n`)\n return { kind: \"appended\", path: excludePath }\n } catch (e) {\n return { kind: \"failed\", path: excludePath, reason: (e as Error).message }\n }\n}\n\n// ── directory preparation ───────────────────────────────────────────────\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Create `location.dir`, and — only on the run that first creates the\n * artifact root itself — make git ignore that root. Returns the note lines\n * the command should print after its summary; an empty array is the normal,\n * quiet case (the root already existed, or it was already ignored).\n *\n * The ignore step is skipped entirely when the root came from a project\n * config's `outDir` ({@link WorkspaceLocation.configured}) or when the caller\n * passed `--no-git-ignore`: both are the user having already stated where\n * artifacts go and who manages them.\n *\n * A root that cannot be created (a read-only checkout, a container mount)\n * throws a {@link PptwiseError} naming all three ways out instead of quietly\n * relocating to a temp directory — output the caller cannot find is worse\n * than output that refused to be written.\n */\nexport async function prepareWorkspaceDir(\n location: WorkspaceLocation,\n opts: { gitIgnore?: boolean; runGit?: GitRunner } = {},\n): Promise<string[]> {\n const rootExisted = await exists(location.root)\n try {\n await mkdir(location.dir, { recursive: true })\n } catch (e) {\n throw new PptwiseError(\n `cannot create the output directory ${location.dir}: ${(e as Error).message}\\n` +\n ` pass -o <path> to write somewhere writable, set \"outDir\" in pptwise.config.json, or run from a writable workspace`,\n )\n }\n if (rootExisted || location.configured || opts.gitIgnore === false) return []\n\n const ignoreEntry = ignoreEntryFor(basename(location.root))\n const outcome = await ensureGitIgnored(location.anchor, ignoreEntry, opts.runGit)\n if (outcome.kind === \"appended\") {\n return [\n `note: added ${ignoreEntry} to ${outcome.path} — a local ignore, your shared .gitignore is untouched`,\n ]\n }\n if (outcome.kind === \"failed\") {\n return [\n `note: could not write ${outcome.path} (${outcome.reason}) — add ${ignoreEntry} to your ignore rules yourself`,\n ]\n }\n return []\n}\n\n/**\n * Delete every `NNN-<type>.svg` file in `dir` ({@link RENDERED_SVG_PATTERN}).\n * Called only on the default-path write — a directory the user named with\n * `-o` could be anything at all, and this CLI has no business deleting files\n * out of it. Returns how many went, for the caller's note line. A missing\n * directory counts as zero.\n */\nexport async function pruneRenderedSvgs(dir: string): Promise<number> {\n let entries: string[]\n try {\n entries = await readdir(dir)\n } catch {\n return 0\n }\n const stale = entries.filter((name) => RENDERED_SVG_PATTERN.test(name))\n await Promise.all(stale.map((name) => unlink(join(dir, name))))\n return stale.length\n}\n\nconst WORKSPACE_IMAGE_EXTS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\"])\n\n/** Directory under a deck workspace that holds pinned stock photos + sidecars. */\nexport function workspaceStockAssetsDir(location: WorkspaceLocation): string {\n return join(location.dir, ASSETS_DIRNAME)\n}\n\n/**\n * Scan `.pptwise/<deck>/assets/` for image files. Skips `.json` sidecars and\n * dotfiles. `src` is the absolute path so {@link resolveLocalAssets} can\n * inline it without guessing. Duplicate ids (logo.png + logo.jpg) error,\n * same posture as the deck-project `assets/` scan.\n */\nexport async function scanWorkspaceAssets(assetsDir: string): Promise<Record<string, { src: string }>> {\n let names: string[]\n try {\n names = (await readdir(assetsDir, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && !entry.name.startsWith(\".\"))\n .map((entry) => entry.name)\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return {}\n throw new PptwiseError(`cannot read workspace assets directory ${assetsDir}: ${(e as Error).message}`)\n }\n const images: Record<string, { src: string }> = {}\n const sourceFile = new Map<string, string>()\n for (const name of names) {\n const ext = extname(name).toLowerCase()\n if (ext === \".json\" || !WORKSPACE_IMAGE_EXTS.has(ext)) continue\n const id = basename(name, extname(name))\n const previous = sourceFile.get(id)\n if (previous !== undefined) {\n throw new PptwiseError(\n `workspace ${ASSETS_DIRNAME}/${previous} and ${ASSETS_DIRNAME}/${name} both register image id \"${id}\" — rename one of the files`,\n )\n }\n sourceFile.set(id, name)\n images[id] = { src: join(assetsDir, name) }\n }\n return images\n}\n","/**\n * The only place in the CLI that starts a child process.\n *\n * A child started from a host with no console of its own gets one allocated,\n * and Windows shows its window, so a DSH desktop preview popped a black\n * window per child (issue #60). `windowsHide` suppresses it, defaults to\n * false, and is ignored off Windows. Written after the caller's options so a\n * spread cannot drop it.\n */\nimport {\n execFile,\n spawn,\n type ChildProcess,\n type ExecFileException,\n type ExecFileOptions,\n type SpawnOptions,\n} from \"node:child_process\"\nimport { resolveSpawnPlan } from \"./win-exec\"\n\nexport const DRAIN_GRACE_MS = 500\n\nexport function spawnHidden(\n command: string,\n args: readonly string[] = [],\n options: Omit<SpawnOptions, \"windowsHide\"> = {},\n): ChildProcess {\n return spawn(command, args, { ...options, windowsHide: true })\n}\n\nexport function execFileHidden(\n file: string,\n args: readonly string[] | undefined,\n options: Omit<ExecFileOptions, \"windowsHide\"> | undefined,\n callback: (error: ExecFileException | null, stdout: string, stderr: string) => void,\n): ChildProcess {\n return execFile(file, [...(args ?? [])], { ...options, windowsHide: true }, callback as never)\n}\n\nexport interface RunChildOptions extends Omit<SpawnOptions, \"windowsHide\" | \"stdio\"> {\n timeoutMs?: number\n signal?: AbortSignal\n}\n\nexport interface RunChildResult {\n code: number\n stdout: string\n stderr: string\n}\n\nexport class ChildTimeoutError extends Error {\n readonly timedOut = true as const\n constructor(timeoutMs: number) {\n super(`child process timed out after ${timeoutMs}ms`)\n this.name = \"ChildTimeoutError\"\n }\n}\n\n/**\n * Settle on `exit` plus a short drain, or on `close` if it arrives first.\n * A grandchild that inherited stdout used to make `close` never fire (#1).\n * After settling, destroy the pipes and unref the child so a lingering\n * descendant cannot pin this process.\n */\nexport async function runChild(\n command: string,\n args: readonly string[] = [],\n options: RunChildOptions = {},\n): Promise<RunChildResult> {\n const { timeoutMs, signal, ...spawnOptions } = options\n const cwd = typeof spawnOptions.cwd === \"string\" ? spawnOptions.cwd : undefined\n const plan = await resolveSpawnPlan(command, args, spawnOptions.env ?? process.env, cwd)\n return new Promise((resolve, reject) => {\n const child = spawnHidden(plan.command, plan.args, {\n ...spawnOptions,\n env: plan.env ?? spawnOptions.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n })\n\n let stdout = \"\"\n let stderr = \"\"\n let settled = false\n let drainTimer: NodeJS.Timeout | undefined\n let timeoutTimer: NodeJS.Timeout | undefined\n let exitCode: number | null = null\n let exited = false\n let timedOut = false\n\n const settle = (code: number | null) => {\n if (settled) return\n settled = true\n if (timeoutTimer) clearTimeout(timeoutTimer)\n if (drainTimer) clearTimeout(drainTimer)\n child.stdout?.destroy()\n child.stderr?.destroy()\n child.unref()\n signal?.removeEventListener(\"abort\", onAbort)\n if (timedOut) {\n reject(new ChildTimeoutError(timeoutMs ?? 0))\n return\n }\n resolve({ code: code ?? 0, stdout, stderr })\n }\n\n const restartDrain = () => {\n if (!exited || settled) return\n if (drainTimer) clearTimeout(drainTimer)\n drainTimer = setTimeout(() => settle(exitCode), DRAIN_GRACE_MS)\n }\n\n child.stdout?.on(\"data\", (chunk: Buffer | string) => {\n stdout += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\")\n restartDrain()\n })\n child.stderr?.on(\"data\", (chunk: Buffer | string) => {\n stderr += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\")\n restartDrain()\n })\n\n child.on(\"error\", (error) => {\n if (settled) return\n settled = true\n if (timeoutTimer) clearTimeout(timeoutTimer)\n if (drainTimer) clearTimeout(drainTimer)\n signal?.removeEventListener(\"abort\", onAbort)\n reject(error)\n })\n\n child.on(\"exit\", (code) => {\n exitCode = code\n exited = true\n restartDrain()\n })\n\n child.on(\"close\", (code) => settle(code))\n\n if (timeoutMs !== undefined) {\n timeoutTimer = setTimeout(() => {\n timedOut = true\n child.kill(\"SIGTERM\")\n settle(null)\n }, timeoutMs)\n }\n\n const onAbort = () => child.kill()\n if (signal) {\n if (signal.aborted) onAbort()\n else signal.addEventListener(\"abort\", onAbort, { once: true })\n }\n })\n}\n","/**\n * Windows cannot spawn what POSIX can. npm/pnpm install every JS CLI as a\n * trio (POSIX sh shim, `.cmd`, `.ps1`). A bare `spawn('npm')` is ENOENT.\n * Handing spawn the `.cmd` hits Node's post-CVE-2024-27980 refusal to run\n * batch files without a shell (EINVAL). Wrapping the call in cmd.exe is not\n * open to us: cmd truncates at a raw newline, and image-generator prompts are\n * multi-line. So a `.cmd` is recognised as one of the generator templates\n * and rewritten to a direct spawn. Anything else is refused, loudly.\n */\nimport * as fs from \"node:fs\"\nimport * as path from \"node:path\"\nimport { envValue, findOnPath } from \"./path-lookup\"\n\nexport interface SpawnPlan {\n command: string\n args: string[]\n env?: NodeJS.ProcessEnv\n}\n\nexport type Existence = \"present\" | \"directory\" | \"absent\" | \"unknown\"\n\nexport function existenceOf(target: string): Existence {\n try {\n return fs.statSync(target).isDirectory() ? \"directory\" : \"present\"\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"ENOENT\" ? \"absent\" : \"unknown\"\n }\n}\n\nexport interface ResolveDeps {\n platform: NodeJS.Platform\n readFileSync: (p: string) => string\n resolveOnPath: (bin: string, env: NodeJS.ProcessEnv) => Promise<string | null> | string | null\n existence: (p: string) => Existence\n}\n\nconst REAL_DEPS: ResolveDeps = {\n platform: process.platform,\n readFileSync: (p) => fs.readFileSync(p, \"utf8\"),\n resolveOnPath: (bin, env) => findOnPath(bin, env),\n existence: existenceOf,\n}\n\nexport class UnrecognizedBatchShimError extends Error {\n constructor(command: string) {\n super(\n `Cannot spawn \"${command}\" as a Windows batch file without a shell. ` +\n \"Node refuses to run .cmd/.bat files without a shell (EINVAL after CVE-2024-27980), \" +\n \"and wrapping the call in cmd.exe would truncate multi-line arguments at the first newline. \" +\n \"Use a recognised npm/pnpm shim, or spawn the real executable.\",\n )\n this.name = \"UnrecognizedBatchShimError\"\n }\n}\n\ninterface Recipe {\n command: string\n args: string[]\n env?: NodeJS.ProcessEnv\n}\n\nconst PATHEXT_EDIT = /^@?SET PATHEXT=%PATHEXT:;\\.([A-Z]+);=;%$/\nconst FLAG = /^--?[A-Za-z0-9][-A-Za-z0-9._]*(?:=[-A-Za-z0-9._/\\\\:]+)?$/\n\nfunction withWindowsEnvAssignment(env: NodeJS.ProcessEnv, name: string, value: string): NodeJS.ProcessEnv {\n const upper = name.toUpperCase()\n const next: NodeJS.ProcessEnv = {}\n let replaced = false\n for (const [key, val] of Object.entries(env)) {\n if (key.toUpperCase() === upper) {\n next[key] = value\n replaced = true\n } else {\n next[key] = val\n }\n }\n if (!replaced) next[name] = value\n return next\n}\n\nfunction withoutWindowsEnvVariable(env: NodeJS.ProcessEnv, name: string): NodeJS.ProcessEnv {\n const upper = name.toUpperCase()\n const next: NodeJS.ProcessEnv = {}\n for (const [key, val] of Object.entries(env)) {\n if (key.toUpperCase() !== upper) next[key] = val\n }\n return next\n}\n\nfunction withPathextEdit(env: NodeJS.ProcessEnv, removed: string): NodeJS.ProcessEnv {\n const current = envValue(env, \"PATHEXT\") ?? \"\"\n const edited = current.replace(new RegExp(`;\\\\.${removed};`, \"gi\"), \";\")\n if (edited === \"\") return withoutWindowsEnvVariable(env, \"PATHEXT\")\n return withWindowsEnvAssignment(env, \"PATHEXT\", edited)\n}\n\nfunction isFullyQualifiedLocalPath(target: string): boolean {\n return /^[A-Za-z]:[\\\\/]/.test(target)\n}\n\nfunction templatePath(text: string, shimDir: string): string | null {\n const rooted = /^%(?:dp0%|~dp0)\\\\?(.*)$/i.exec(text)\n if (!rooted) {\n return isFullyQualifiedLocalPath(text) && !text.includes(\"%\") ? text : null\n }\n const rest = rooted[1] ?? \"\"\n if (rest.includes(\"%\") || rest === \"\") return null\n return path.win32.normalize(path.win32.join(shimDir, rest))\n}\n\nfunction interpreterTail(middle: string, shimDir: string): string[] | null {\n const tokens = middle\n .trim()\n .split(/\\s+/)\n .filter((token) => token !== \"\")\n if (tokens.length === 0) return null\n const quoted = /^\"([^\"]*)\"$/.exec(tokens[tokens.length - 1] ?? \"\")\n if (!quoted) return null\n const entry = templatePath(quoted[1]!, shimDir)\n if (entry === null) return null\n const flags = tokens.slice(0, -1)\n return flags.every((flag) => FLAG.test(flag)) ? [...flags, entry] : null\n}\n\nasync function nodeRecipe(\n shimDir: string,\n tail: string[],\n effective: { present: NodeJS.ProcessEnv; absent: NodeJS.ProcessEnv },\n env: NodeJS.ProcessEnv,\n deps: ResolveDeps,\n): Promise<Recipe | null> {\n const local = path.win32.join(shimDir, \"node.exe\")\n const found = deps.existence(local)\n if (found === \"unknown\") return null\n if (found === \"present\" || found === \"directory\") {\n return {\n command: local,\n args: tail,\n ...(effective.present === env ? {} : { env: effective.present }),\n }\n }\n const resolved = await deps.resolveOnPath(\"node\", effective.absent)\n return resolved === null\n ? null\n : {\n command: resolved,\n args: tail,\n ...(effective.absent === env ? {} : { env: effective.absent }),\n }\n}\n\nconst NPM_PROLOGUE = [\n \"@ECHO off\",\n \"GOTO start\",\n \":find_dp0\",\n \"SET dp0=%~dp0\",\n \"EXIT /b\",\n \":start\",\n \"SETLOCAL\",\n \"CALL :find_dp0\",\n]\n\nconst NPM_PREFIX = \"endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & \"\nconst NPM_EXEC_LEGACY = /^\"%_prog%\"(.*)\\s%\\*$/\nconst NPM_EXEC_CURRENT = /^set PATHEXT=%PATHEXT:;\\.([A-Z]+);=;% & \"%_prog%\"(.*)\\s%\\*$/\nconst NPM_NATIVE_EXEC = /^\"([^\"]*)\"\\s+%\\*$/\nconst PNPM_NODE_PATH_IF = /^@IF NOT DEFINED NODE_PATH \\($/\nconst PNPM_NODE_PATH_SET = /^@SET \"NODE_PATH=([^\"%]+)\"$/\nconst PNPM_NODE_PATH_PREPEND = /^@SET \"NODE_PATH=([^\"%]+);%NODE_PATH%\"$/\nconst PNPM_IF = /^@IF EXIST \"([^\"]*)\" \\($/\nconst PNPM_ARM = /^\"([^\"]*)\"(.*)\\s%\\*$/\nconst PNPM_BARE_ARM = /^node(.*)\\s%\\*$/\nconst PNPM_ONELINE = /^@\"([^\"]*)\"(.*)\\s%\\*$/\n\nfunction normalizeLines(content: string): string[] {\n const lines = content.split(/\\r?\\n/).map((line) => line.trim())\n while (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop()\n return lines\n}\n\nasync function matchNpm(\n lines: string[],\n shimDir: string,\n env: NodeJS.ProcessEnv,\n deps: ResolveDeps,\n): Promise<Recipe | null> {\n if (lines.length < NPM_PROLOGUE.length) return null\n if (!NPM_PROLOGUE.every((expected, index) => lines[index] === expected)) return null\n const body = lines.slice(NPM_PROLOGUE.length).filter((line) => line !== \"\")\n\n if (body.length === 1) {\n const native = NPM_NATIVE_EXEC.exec(body[0] ?? \"\")\n if (!native) return null\n const target = templatePath(native[1]!, shimDir)\n if (target === null || !/\\.exe$/i.test(target)) return null\n const normalizedDir = path.win32.normalize(shimDir)\n const dp0 = normalizedDir.endsWith(\"\\\\\") ? normalizedDir : `${normalizedDir}\\\\`\n return { command: target, args: [], env: withWindowsEnvAssignment(env, \"dp0\", dp0) }\n }\n\n if (body[0] !== 'IF EXIST \"%dp0%\\\\node.exe\" (') return null\n if (body[1] !== 'SET \"_prog=%dp0%\\\\node.exe\"') return null\n if (body[2] !== \") ELSE (\") return null\n if (body[3] !== 'SET \"_prog=node\"') return null\n\n if (body.length === 7) {\n if (!PATHEXT_EDIT.test(body[4] ?? \"\")) return null\n if (body[5] !== \")\") return null\n if (!(body[6] ?? \"\").startsWith(NPM_PREFIX)) return null\n const exec = NPM_EXEC_LEGACY.exec((body[6] ?? \"\").slice(NPM_PREFIX.length))\n if (!exec) return null\n const tail = interpreterTail(exec[1] ?? \"\", shimDir)\n return tail === null ? null : nodeRecipe(shimDir, tail, { present: env, absent: env }, env, deps)\n }\n\n if (body.length === 6) {\n if (body[4] !== \")\") return null\n if (!(body[5] ?? \"\").startsWith(NPM_PREFIX)) return null\n const exec = NPM_EXEC_CURRENT.exec((body[5] ?? \"\").slice(NPM_PREFIX.length))\n if (!exec) return null\n const tail = interpreterTail(exec[2] ?? \"\", shimDir)\n if (tail === null) return null\n const edited = withPathextEdit(env, exec[1] ?? \"JS\")\n return nodeRecipe(shimDir, tail, { present: edited, absent: edited }, env, deps)\n }\n\n return null\n}\n\nasync function matchPnpm(\n lines: string[],\n shimDir: string,\n env: NodeJS.ProcessEnv,\n deps: ResolveDeps,\n): Promise<Recipe | null> {\n if (lines[0] !== \"@SETLOCAL\") return null\n let body = lines.slice(1).filter((line) => line !== \"\")\n\n let baseEnv = env\n if (body.length > 0 && PNPM_NODE_PATH_IF.test(body[0] ?? \"\")) {\n if (body.length < 5) return null\n const set = PNPM_NODE_PATH_SET.exec(body[1] ?? \"\")\n const prepend = PNPM_NODE_PATH_PREPEND.exec(body[3] ?? \"\")\n if (!set || body[2] !== \") ELSE (\" || !prepend || body[4] !== \")\") return null\n if (set[1] !== prepend[1]) return null\n const currentValue = envValue(env, \"NODE_PATH\")\n const defined = currentValue !== undefined && currentValue !== \"\"\n baseEnv = withWindowsEnvAssignment(env, \"NODE_PATH\", defined ? `${set[1]};${currentValue}` : set[1]!)\n body = body.slice(5)\n }\n\n if (body.length === 1) {\n const one = PNPM_ONELINE.exec(body[0] ?? \"\")\n if (!one) return null\n const program = templatePath(one[1]!, shimDir)\n if (program === null) return null\n const onelineEnv = baseEnv === env ? {} : { env: baseEnv }\n if ((one[2] ?? \"\").trim() === \"\") {\n return /\\.exe$/i.test(program) ? { command: program, args: [], ...onelineEnv } : null\n }\n const tail = interpreterTail(one[2] ?? \"\", shimDir)\n return tail === null || /\\.(cmd|bat)$/i.test(program) ? null : { command: program, args: tail, ...onelineEnv }\n }\n\n if (body.length !== 6) return null\n const opened = PNPM_IF.exec(body[0] ?? \"\")\n if (!opened) return null\n const local = path.win32.join(shimDir, \"node.exe\")\n const candidate = templatePath(opened[1]!, shimDir)\n if (candidate === null || candidate.toLowerCase() !== local.toLowerCase()) return null\n const present = PNPM_ARM.exec(body[1] ?? \"\")\n if (!present) return null\n const presentProgram = templatePath(present[1]!, shimDir)\n if (presentProgram === null || presentProgram.toLowerCase() !== local.toLowerCase()) return null\n if (body[2] !== \") ELSE (\") return null\n const edit = PATHEXT_EDIT.exec(body[3] ?? \"\")\n if (!edit) return null\n const absent = PNPM_BARE_ARM.exec(body[4] ?? \"\")\n if (!absent) return null\n if (body[5] !== \")\") return null\n\n const tail = interpreterTail(present[2] ?? \"\", shimDir)\n const otherTail = interpreterTail(absent[1] ?? \"\", shimDir)\n if (tail === null || otherTail === null || tail.join(\" \") !== otherTail.join(\" \")) return null\n const removed = edit[1] ?? \"JS\"\n return nodeRecipe(\n shimDir,\n tail,\n { present: baseEnv, absent: withPathextEdit(baseEnv, removed) },\n env,\n deps,\n )\n}\n\nexport async function recognizeShim(\n cmdPath: string,\n content: string,\n env: NodeJS.ProcessEnv,\n deps: ResolveDeps = REAL_DEPS,\n): Promise<Recipe | null> {\n if (!isFullyQualifiedLocalPath(cmdPath)) return null\n const shimDir = path.win32.dirname(cmdPath)\n const lines = normalizeLines(content)\n if (lines.length === 0) return null\n return (await matchNpm(lines, shimDir, env, deps)) ?? (await matchPnpm(lines, shimDir, env, deps))\n}\n\nexport async function resolveSpawnPlan(\n command: string,\n args: readonly string[],\n env: NodeJS.ProcessEnv = process.env,\n _cwd?: string,\n deps: ResolveDeps = REAL_DEPS,\n): Promise<SpawnPlan> {\n if (deps.platform !== \"win32\") return { command, args: [...args] }\n let resolved = command\n if (!command.includes(\"/\") && !command.includes(\"\\\\\")) {\n resolved = (await deps.resolveOnPath(command, env)) ?? command\n }\n if (!/\\.(cmd|bat)$/i.test(path.win32.basename(resolved))) {\n return { command: resolved, args: [...args] }\n }\n if (!isFullyQualifiedLocalPath(resolved)) {\n throw new UnrecognizedBatchShimError(command)\n }\n let content: string\n try {\n content = deps.readFileSync(resolved)\n } catch {\n throw new UnrecognizedBatchShimError(command)\n }\n const recipe = await recognizeShim(resolved, content, env, deps)\n if (recipe === null) throw new UnrecognizedBatchShimError(command)\n return {\n command: recipe.command,\n args: [...recipe.args, ...args],\n ...(recipe.env ? { env: recipe.env } : {}),\n }\n}\n","import { constants } from \"node:fs\"\nimport { access as fsAccess } from \"node:fs/promises\"\nimport { delimiter as defaultDelimiter, join as defaultJoin } from \"node:path\"\n\nconst DEFAULT_PATHEXT = \".COM;.EXE;.BAT;.CMD\"\n\n/** Case-insensitive env lookup (Windows names fold, POSIX usually does not). */\nexport function envValue(env: NodeJS.ProcessEnv, name: string): string | undefined {\n const upper = name.toUpperCase()\n for (const [key, value] of Object.entries(env)) {\n if (key.toUpperCase() === upper) return value\n }\n return undefined\n}\n\nexport interface FindOnPathDeps {\n platform?: NodeJS.Platform\n join?: (...parts: string[]) => string\n delimiter?: string\n access?: (target: string, mode?: number) => Promise<void>\n}\n\n/**\n * First executable named `bin` on PATH, or null. On Windows, PATHEXT\n * extensions are tried before the bare name so an npm POSIX shim cannot\n * hide the real `.cmd` (#30).\n */\nexport async function findOnPath(\n bin: string,\n env: NodeJS.ProcessEnv,\n deps: FindOnPathDeps = {},\n): Promise<string | null> {\n const platform = deps.platform ?? process.platform\n const join = deps.join ?? defaultJoin\n const delimiter = deps.delimiter ?? defaultDelimiter\n const tryAccess = deps.access ?? ((target: string, mode?: number) => fsAccess(target, mode))\n const dirs = (envValue(env, \"PATH\") ?? \"\").split(delimiter).filter(Boolean)\n const suffixes =\n platform === \"win32\"\n ? [...(envValue(env, \"PATHEXT\") ?? DEFAULT_PATHEXT).split(\";\").map((ext) => ext.trim()).filter(Boolean), \"\"]\n : [\"\"]\n for (const dir of dirs) {\n for (const suffix of suffixes) {\n const full = join(dir, `${bin}${suffix}`)\n try {\n await tryAccess(full, constants.X_OK)\n return full\n } catch {\n // not here\n }\n }\n }\n return null\n}\n","import readline from \"node:readline\"\nimport { PptwiseError } from \"../errors\"\n\nexport interface SecretInputIo {\n stdin: NodeJS.ReadableStream\n stderr: NodeJS.WritableStream\n}\n\n/**\n * Read a secret. TTY: hidden prompt on stderr so stdout stays clean.\n * Non-TTY: first line of stdin, trimmed. Empty after trim is an error.\n */\nexport async function readSecret(prompt: string, io: SecretInputIo = { stdin: process.stdin, stderr: process.stderr }): Promise<string> {\n const { stdin, stderr } = io\n const isTTY = Boolean((stdin as NodeJS.ReadStream).isTTY)\n if (!isTTY) {\n const line = await readFirstLine(stdin)\n if (line === \"\") throw new PptwiseError(\"API key cannot be empty\")\n return line\n }\n return await readHiddenTty(prompt, stdin, stderr)\n}\n\nfunction readFirstLine(stdin: NodeJS.ReadableStream): Promise<string> {\n return new Promise((resolve, reject) => {\n const encoding = ((stdin as NodeJS.ReadableStream & { readableEncoding?: BufferEncoding | null }).readableEncoding) ?? \"utf8\"\n let buf = \"\"\n const onData = (chunk: string | Buffer) => {\n buf += typeof chunk === \"string\" ? chunk : chunk.toString(encoding)\n const nl = buf.indexOf(\"\\n\")\n if (nl === -1) return\n cleanup()\n resolve(buf.slice(0, nl).replace(/\\r$/, \"\").trim())\n }\n const onEnd = () => {\n cleanup()\n resolve(buf.replace(/\\r$/, \"\").trim())\n }\n const onError = (e: Error) => {\n cleanup()\n reject(e)\n }\n const cleanup = () => {\n stdin.off(\"data\", onData)\n stdin.off(\"end\", onEnd)\n stdin.off(\"error\", onError)\n }\n stdin.on(\"data\", onData)\n stdin.on(\"end\", onEnd)\n stdin.on(\"error\", onError)\n })\n}\n\nfunction readHiddenTty(prompt: string, stdin: NodeJS.ReadableStream, stderr: NodeJS.WritableStream): Promise<string> {\n return new Promise((resolve, reject) => {\n const rl = readline.createInterface({ input: stdin, output: stderr, terminal: true })\n const mutable = rl as unknown as { _writeToOutput: (s: string) => void }\n mutable._writeToOutput = () => {\n // swallow echo so the value never appears on the terminal\n }\n stderr.write(prompt)\n let settled = false\n const finish = (fn: () => void) => {\n if (settled) return\n settled = true\n fn()\n }\n rl.question(\"\", (answer) => {\n finish(() => {\n rl.close()\n stderr.write(\"\\n\")\n const value = answer.trim()\n if (value === \"\") reject(new PptwiseError(\"API key cannot be empty\"))\n else resolve(value)\n })\n })\n rl.on(\"SIGINT\", () => {\n finish(() => {\n rl.close()\n stderr.write(\"\\n\")\n reject(new PptwiseError(\"cancelled\"))\n })\n })\n rl.on(\"close\", () => {\n finish(() => reject(new PptwiseError(\"cancelled\")))\n })\n })\n}\n","import { PptwiseError } from \"../errors\"\nimport { findUserConfig } from \"./config\"\nimport { userConfigPath } from \"./home\"\nimport {\n GENERATOR_IDS,\n maskKey,\n parseCliConfigKey,\n parseCliConfigValue,\n persistUserConfigValue,\n resolveGenerators,\n resolveImageKeys,\n} from \"./image-config\"\nimport { readSecret, type SecretInputIo } from \"./secret-input\"\n\nexport interface ConfigSetOptions {\n readSecret?: (prompt: string, io?: SecretInputIo) => Promise<string>\n io?: SecretInputIo\n}\n\nfunction canOmitValue(key: string): boolean {\n return key.endsWith(\".apiKey\") || key.endsWith(\".clientSecret\")\n}\n\nexport async function runConfigSet(key: string, value: string | undefined, opts: ConfigSetOptions = {}): Promise<string> {\n if (value === undefined && !canOmitValue(key)) {\n throw new PptwiseError(`${key} needs a value: pptwise config set ${key} <value>`)\n }\n const parsed = parseCliConfigKey(key)\n let resolved = value\n if (resolved === undefined) {\n const read = opts.readSecret ?? readSecret\n resolved = await read(`${key} (input hidden): `, opts.io)\n }\n const stored = parseCliConfigValue(parsed, resolved)\n const path = await persistUserConfigValue(parsed.path, stored)\n return `Saved ${key} to ${path}`\n}\n\nexport async function runConfigShow(opts: { env?: NodeJS.ProcessEnv } = {}): Promise<string> {\n const path = userConfigPath()\n const hit = await findUserConfig()\n const file = hit?.config ?? null\n const keys = resolveImageKeys({ file, env: opts.env ?? process.env })\n const lines = [`User config: ${path}`, \"\"]\n for (const provider of [\"pexels\", \"pixabay\"] as const) {\n const label = `${provider}.apiKey`\n const entry = keys[provider]\n if (!entry.apiKey) {\n lines.push(`${label} missing`)\n continue\n }\n const src = entry.source === null ? \"\" : ` (${entry.source})`\n lines.push(`${label} ${maskKey(entry.apiKey)}${src}`)\n }\n const ov = keys.openverse\n const ovSrc = ov.source === null ? \"\" : ` (${ov.source})`\n if (ov.clientId) lines.push(`openverse.clientId ${maskKey(ov.clientId)}${ovSrc}`)\n else lines.push(\"openverse.clientId missing\")\n if (ov.clientSecret) lines.push(`openverse.clientSecret ${maskKey(ov.clientSecret)}${ovSrc}`)\n else lines.push(\"openverse.clientSecret missing\")\n\n const gens = resolveGenerators({ file })\n lines.push(\"\")\n for (const id of GENERATOR_IDS) {\n lines.push(`images.generators.${id}.enabled ${gens.enabled[id] ? \"true\" : \"false\"}`)\n }\n const rawGens = file?.images?.generators\n if (rawGens?.order) lines.push(`images.generators.order ${rawGens.order.join(\",\")}`)\n if (rawGens?.timeoutMs !== undefined) lines.push(`images.generators.timeoutMs ${rawGens.timeoutMs}`)\n return lines.join(\"\\n\")\n}\n","// `pptwise doctor`: diagnose this machine's install without a single network\n// call. Rendering a PPTX is still zero-config and fully local. Optional\n// stock-photo search reads Pexels/Pixabay/Openverse credentials from\n// `$PPTWISE_HOME/config.json` or the env, and this report says whether those\n// keys are present and where they came from — never the values. What can actually go wrong is: an\n// installed skill copy frozen at an old version, a dsh plugin left behind, a\n// Node below the floor, a missing optional capability, a broken render chain,\n// or a user config file that is group/other-readable. Each of those gets its\n// own check below.\n//\n// The split is deliberate (ported from modlens's own doctor): buildDoctorReport\n// produces a structured report and never prints, renderDoctorReport turns that\n// report into text. `--json` hands the structure straight through, and the\n// tests assert against the structure instead of scraping formatted output.\nimport { lstatSync } from \"node:fs\"\nimport { access, readFile, readdir } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { basename, join } from \"node:path\"\nimport { formatIssues, generatePptx, renderSlideSvg, validateIr } from \"../api\"\nimport { isMissingModuleError } from \"../platform/node\"\nimport { VERSION } from \"../version\"\nimport { findConfig, findUserConfig } from \"./config\"\nimport { userConfigPath } from \"./home\"\nimport { resolveGenerators, resolveImageKeys, type GeneratorId, type ImageProviderId, type KeySource } from \"./image-config\"\nimport { probeGenerators, type ProcessRunner } from \"./image-generators\"\nimport { findOnPath } from \"./path-lookup\"\nimport { compareVersions, PACKAGE_NAME } from \"./update\"\nimport { inspectWorkspace, type GitIgnoreStatus, type GitRunner } from \"./workspace\"\n\n/** The lowest Node this release supports — mirrors package.json `engines.node`\n * (`doctor.test.ts` asserts the two stay in step, the same way\n * `version-sync.test.ts` pins `VERSION` against package.json). */\nexport const MIN_NODE = \"22.19\"\n\n/** The folder name a skill copy lands under, in every harness's skill root. */\nexport const SKILL_DIR_NAME = \"pptwise\"\nexport const LEGACY_SKILL_DIR_NAMES = [\"pptpress\", \"pptfast\"] as const\n\n/** Relative paths every current skill copy must contain, in this order.\n * A copy that pins the running CLI but is missing `references/` is\n * incomplete, not stale. */\nexport const SKILL_COPY_FILES: readonly string[] = [\n \"SKILL.md\",\n \"SKILL.zh-CN.md\",\n \"scripts/run.sh\",\n \"scripts/run.ps1\",\n \"references/spec.md\",\n \"references/spec.zh-CN.md\",\n \"references/layouts.md\",\n \"references/layouts.zh-CN.md\",\n \"references/components.md\",\n \"references/components.zh-CN.md\",\n \"references/density.md\",\n \"references/density.zh-CN.md\",\n \"references/branding.md\",\n \"references/branding.zh-CN.md\",\n \"references/images.md\",\n \"references/images.zh-CN.md\",\n \"references/validate.md\",\n \"references/validate.zh-CN.md\",\n]\n\n/** Where each harness reads global skills from — this table is exactly what\n * INSTALL.md step 2 documents, `~/.agents/skills` serving Pi and OpenCode\n * both. dsh is deliberately absent: there the CLI ships inside the plugin\n * package, so drift shows up as a stale plugin, not a stale skill copy\n * ({@link inspectDsh} below). */\nconst SKILL_ROOTS = [\n { harness: \"Claude Code\", relative: join(\".claude\", \"skills\") },\n { harness: \"Codex\", relative: join(\".codex\", \"skills\") },\n { harness: \"Pi, OpenCode\", relative: join(\".agents\", \"skills\") },\n] as const\n\n/** The tiny deck {@link runSelfTest} pushes through the real pipeline. Kept\n * minimal on purpose — two pages is enough to exercise cover + content +\n * a component, and the point is proving the chain runs, not covering it. */\nconst SELF_TEST_DECK = {\n version: \"4\",\n filename: \"pptwise-doctor-self-test.pptx\",\n theme: { id: \"consulting\" },\n slides: [\n { type: \"cover\", heading: \"pptwise doctor\", subheading: \"self-test render\" },\n {\n type: \"content\",\n heading: \"Core chain\",\n components: [{ type: \"bullets\", items: [\"validate the IR\", \"render a slide to SVG\", \"generate the .pptx bytes\"] }],\n },\n ],\n} as const\n\n/** One thing worth telling the user about, at either severity. `check` names\n * the section it came from so a `--json` consumer can group without parsing\n * prose, and `fix` is the concrete command or action when one exists. */\nexport interface DoctorFinding {\n check: string\n message: string\n fix?: string\n}\n\nexport interface DoctorRuntime {\n node: string\n /** Bun's own version when running under Bun, else null. `process.version`\n * above is Bun's Node-compatibility version either way, so the floor check\n * applies unchanged — this only says which runtime is executing. */\n bun: string | null\n minimum: string\n meetsMinimum: boolean\n}\n\nexport interface DoctorSkillCopy {\n /** The harness that reads this skill root (INSTALL.md's own labels). */\n harness: string\n /** The skill root scanned, e.g. `~/.claude/skills`. */\n root: string\n /** The copy directory itself, `<root>/pptwise`. */\n path: string\n /** The launcher the pin was read from, or null when the copy has no\n * `scripts/run.sh` at all (a partial copy — reported, never a crash). */\n launcher: string | null\n /** The version this copy pins, or null when it cannot be determined. */\n pinned: string | null\n /** True when the pin is older than the CLI doing the reporting. */\n stale: boolean\n /** `SKILL_COPY_FILES` entries this copy does not have. Empty means the\n * copy is complete for this CLI. Independent of {@link stale}. */\n missing: string[]\n /** True when this is a leftover `pptpress` or `pptfast` skill directory from before the rename. */\n legacy: boolean\n}\n\nexport interface DoctorSkills {\n /** Every root that was looked at, present or not — so \"no copies found\"\n * can name where it looked instead of sounding like a failed search. */\n scanned: string[]\n copies: DoctorSkillCopy[]\n}\n\nexport interface DoctorDshProfile {\n /** The profile directory name, i.e. what `--profile` takes. */\n name: string\n path: string\n installed: boolean\n version: string | null\n /** Where the version came from: the resolved package in the profile's own\n * `node_modules` (authoritative — it is what actually loads), or the\n * profile `package.json`'s dependency range (a fallback, used when the\n * package is declared but not installed yet). */\n source: \"node_modules\" | \"package.json\" | null\n stale: boolean\n}\n\nexport interface DoctorDsh {\n /** False when there is no `~/.dsh` at all — the check does not apply, which\n * is not the same thing as failing it. */\n applicable: boolean\n home: string\n profiles: DoctorDshProfile[]\n}\n\nexport interface DoctorCapability {\n name: string\n available: boolean\n detail: string\n fix?: string\n}\n\nexport interface DoctorSelfTest {\n ok: boolean\n elapsedMs: number\n slides: number\n /** Byte length of the generated .pptx, on success. */\n bytes: number | null\n error?: string\n}\n\n/** Where `render`/`preview` write when `-o` is omitted, from the cwd doctor\n * was invoked in. Informational only — never a warning or an error. */\nexport interface DoctorWorkspace {\n anchor: string\n root: string\n configured: boolean\n ignore: GitIgnoreStatus\n}\n\nexport interface DoctorImageProvider {\n provider: ImageProviderId\n present: boolean\n source: KeySource | null\n}\n\nexport interface DoctorImages {\n configPath: string\n configExists: boolean\n /** True when POSIX bits say group or other can read the file. Always false\n * on Windows (`typeof process.getuid !== \"function\"`). */\n groupOrOtherReadable: boolean\n providers: DoctorImageProvider[]\n}\n\nexport interface DoctorGenerator {\n id: GeneratorId\n found: boolean\n bin: string | null\n version: string | null\n enabled: boolean\n}\n\nexport interface DoctorReport {\n /** The running CLI's version — every drift comparison below is against it. */\n version: string\n runtime: DoctorRuntime\n skills: DoctorSkills\n dsh: DoctorDsh\n capabilities: DoctorCapability[]\n selfTest: DoctorSelfTest\n workspace: DoctorWorkspace\n images: DoctorImages\n generators: DoctorGenerator[]\n /** Hard failures: the only thing that makes `pptwise doctor` exit non-zero. */\n errors: DoctorFinding[]\n /** Worth fixing, never fatal — a stale skill copy or a missing optional\n * capability still leaves the main flow working, so exit stays 0. */\n warnings: DoctorFinding[]\n}\n\nexport interface DoctorInput {\n /** The CLI version every pin is compared against. Defaults to `VERSION`. */\n version?: string\n /** Home directory the skill and dsh scans read from. Injectable so tests\n * build a fake home in a temp directory and never touch the real `~`. */\n home?: string\n /** Environment the PATH probe reads. Defaults to `process.env`. */\n env?: NodeJS.ProcessEnv\n /** The Node version checked against the floor. Defaults to `process.version`\n * — overridable so the below-the-floor path is testable on a machine that\n * is, by definition, running a supported Node. */\n nodeVersion?: string\n /** Working directory the workspace-artifacts line is resolved from.\n * Defaults to `process.cwd()`. Injectable so tests do not report the\n * repository they happen to be running in. */\n cwd?: string\n /** Injectable git runner for the workspace ignore probe. */\n runGit?: GitRunner\n /** Injectable process runner for generator `--version` probes. */\n runProcess?: ProcessRunner\n}\n\n/** `PINNED=\"0.18.0\"` out of a launcher's text — the exact line\n * `scripts/stamp.mts` writes at release time. */\nexport function readPinnedVersion(launcher: string): string | null {\n return /^PINNED=\"([^\"]+)\"/m.exec(launcher)?.[1] ?? null\n}\n\n/** `compareVersions` throws on anything it cannot parse (`./update.ts`). A\n * doctor run must never die on one weird version string, so every comparison\n * goes through here: unparsable compares as \"not older\", i.e. never reported\n * as drift we cannot actually prove. */\nfunction isOlder(version: string, current: string): boolean {\n try {\n return compareVersions(version, current) < 0\n } catch {\n return false\n }\n}\n\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await access(target)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Every installed skill copy on this machine, with the version each one pins.\n *\n * An installed skill is a *copy*: `INSTALL.md` step 2 copies the folder into\n * the harness's skill root, and that copy keeps its install-time launcher\n * forever. Upgrading the CLI does not touch it, so a machine can sit on a\n * months-old pin while `pptwise --version` reports something much newer, and\n * nothing surfaces that gap until someone asks. This is that question.\n *\n * Finding zero copies is a perfectly normal state (dsh users have no skill\n * folder at all, and the CLI runs fine on its own), so it is reported as a\n * plain fact rather than a warning. A copy that exists but has no `run.sh`, or\n * a `run.sh` with no `PINNED` line, reports `pinned: null` — \"version unknown\"\n * is an answer, an exception is not.\n */\nexport async function scanSkillCopies(home: string, currentVersion: string): Promise<DoctorSkills> {\n const scanned: string[] = []\n const copies: DoctorSkillCopy[] = []\n for (const { harness, relative } of SKILL_ROOTS) {\n const root = join(home, relative)\n scanned.push(root)\n for (const { dirName, legacy } of [\n { dirName: SKILL_DIR_NAME, legacy: false },\n ...LEGACY_SKILL_DIR_NAMES.map((dirName) => ({ dirName, legacy: true })),\n ]) {\n const copyDir = join(root, dirName)\n if (!(await pathExists(copyDir))) continue\n const launcherPath = join(copyDir, \"scripts\", \"run.sh\")\n let pinned: string | null = null\n let launcher: string | null = null\n try {\n pinned = readPinnedVersion(await readFile(launcherPath, \"utf8\"))\n launcher = launcherPath\n } catch {\n // no launcher (or unreadable) — the copy still counts, version unknown\n }\n const missing: string[] = []\n for (const rel of SKILL_COPY_FILES) {\n if (!(await pathExists(join(copyDir, rel)))) missing.push(rel)\n }\n copies.push({\n harness,\n root,\n path: copyDir,\n launcher,\n pinned,\n stale: legacy || (pinned !== null && isOlder(pinned, currentVersion)),\n missing,\n legacy,\n })\n }\n }\n return { scanned, copies }\n}\n\n/** The version of `@liustack/pptwise` actually resolvable inside a dsh\n * profile's own `node_modules`, or null. This is the authoritative reading —\n * it is the package that would really load — and it works through the `link:`\n * symlink a locally-developed plugin uses, since the symlink target's own\n * `package.json` is what gets read. */\nasync function readInstalledPluginVersion(profileDir: string): Promise<string | null> {\n const pkgPath = join(profileDir, \"node_modules\", ...PACKAGE_NAME.split(\"/\"), \"package.json\")\n try {\n const pkg = JSON.parse(await readFile(pkgPath, \"utf8\")) as { version?: unknown }\n return typeof pkg.version === \"string\" ? pkg.version : null\n } catch {\n return null\n }\n}\n\n/**\n * dsh plugin status, per profile.\n *\n * On dsh, pptwise is not a skill folder but a native plugin: the deck skill and\n * the CLI both ship inside the installed package (INSTALL.md step 0), so the\n * question \"which version is this machine actually running\" is answered by the\n * profile's own installed package, not by a launcher pin.\n *\n * Profiles are directories under `~/.dsh/profiles` that carry their own\n * `package.json` (each one a tiny private package listing its bundles and\n * dependencies) plus their own `node_modules`. That `package.json` test is what\n * separates a real profile from the shared `node_modules` store sitting in the\n * same directory — an entry without one is not a profile.\n *\n * No `~/.dsh` means the check does not apply. That is reported as such, never\n * as a failure: the overwhelming majority of machines running this command are\n * not on dsh at all.\n */\nexport async function inspectDsh(home: string, currentVersion: string): Promise<DoctorDsh> {\n const dshHome = join(home, \".dsh\")\n if (!(await pathExists(dshHome))) return { applicable: false, home: dshHome, profiles: [] }\n const profilesDir = join(dshHome, \"profiles\")\n let entries: string[]\n try {\n const dirents = await readdir(profilesDir, { withFileTypes: true })\n entries = dirents.filter((d) => d.isDirectory() && d.name !== \"node_modules\").map((d) => d.name)\n } catch {\n return { applicable: true, home: dshHome, profiles: [] }\n }\n const profiles: DoctorDshProfile[] = []\n for (const name of entries.sort()) {\n const profileDir = join(profilesDir, name)\n let declared: unknown\n try {\n const pkg = JSON.parse(await readFile(join(profileDir, \"package.json\"), \"utf8\")) as {\n dependencies?: Record<string, unknown>\n }\n declared = pkg.dependencies?.[PACKAGE_NAME]\n } catch {\n continue // not a profile directory — no package.json of its own\n }\n const installedVersion = await readInstalledPluginVersion(profileDir)\n // A declared range is only used when nothing is resolvable in node_modules,\n // and only when it is a bare version (`\"0.18.0\"`) — a `link:` or a range\n // like `^0.18.0` names no single installed version to compare against.\n const declaredVersion =\n typeof declared === \"string\" && /^\\d+\\.\\d+\\.\\d+/.test(declared) ? declared.match(/^\\d+\\.\\d+\\.\\d+/)![0] : null\n const version = installedVersion ?? declaredVersion\n const installed = installedVersion !== null || declared !== undefined\n profiles.push({\n name,\n path: profileDir,\n installed,\n version,\n source: installedVersion !== null ? \"node_modules\" : declaredVersion !== null ? \"package.json\" : null,\n stale: installed && version !== null && isOlder(version, currentVersion),\n })\n }\n return { applicable: true, home: dshHome, profiles }\n}\n\n/** `npx -y @deepseek-ai/dsh plugin --profile web add @liustack/pptwise@0.18.0`\n * — the pinned form INSTALL.md step 0 insists on, because dsh installs through\n * a pnpm that holds back fresh releases and silently resolves `@latest` to an\n * older one. A named version is a deliberate request. */\nfunction dshInstallCommand(profile: string, version: string): string {\n return `npx -y @deepseek-ai/dsh plugin --profile ${profile} add ${PACKAGE_NAME}@${version}`\n}\n\n/** Both optional capabilities, each with the exact consequence of not having\n * it — and, just as important, what still works without it. Neither is ever an\n * error: the main write-IR → validate → render flow needs neither. */\nasync function inspectCapabilities(env: NodeJS.ProcessEnv): Promise<DoctorCapability[]> {\n const capabilities: DoctorCapability[] = []\n\n let sharpAvailable = false\n let sharpError: string | null = null\n try {\n await import(\"sharp\")\n sharpAvailable = true\n } catch (e) {\n sharpError = isMissingModuleError(e) ? \"not installed\" : e instanceof Error ? e.message : String(e)\n }\n capabilities.push({\n name: \"sharp\",\n available: sharpAvailable,\n detail: sharpAvailable\n ? \"importable — preview rasterization and `audit --pixels` are available\"\n : `${sharpError} — preview rasterization and \\`audit --pixels\\` are unavailable. Plain SVG preview and .pptx rendering are unaffected`,\n fix: sharpAvailable ? undefined : \"npm i sharp (optional dependency — install it only if you want the pixel checks)\",\n })\n\n const soffice = await findOnPath(\"soffice\", env)\n capabilities.push({\n name: \"soffice\",\n available: soffice !== null,\n detail:\n soffice !== null\n ? `found at ${soffice} — the PDF export path is available`\n : \"not on PATH — the PDF export path is unavailable. Rendering and validating .pptx files are unaffected\",\n fix: soffice !== null ? undefined : \"install LibreOffice (https://libreoffice.org) and make sure `soffice` is on PATH\",\n })\n\n return capabilities\n}\n\nfunction posixBitsApply(): boolean {\n return typeof process.getuid === \"function\"\n}\n\nexport async function inspectImages(env: NodeJS.ProcessEnv): Promise<DoctorImages> {\n const configPath = userConfigPath()\n let configExists = false\n let groupOrOtherReadable = false\n try {\n const st = lstatSync(configPath)\n configExists = true\n if (posixBitsApply() && !st.isSymbolicLink()) {\n groupOrOtherReadable = (st.mode & 0o077) !== 0\n }\n } catch {\n configExists = false\n }\n let file: Awaited<ReturnType<typeof findUserConfig>> = null\n try {\n file = await findUserConfig()\n } catch {\n file = null\n }\n const keys = resolveImageKeys({ file: file?.config ?? null, env })\n const providers: DoctorImageProvider[] = ([\"pexels\", \"pixabay\"] as const).map((provider) => ({\n provider,\n present: Boolean(keys[provider].apiKey),\n source: keys[provider].source,\n }))\n providers.push({\n provider: \"openverse\",\n present: keys.openverse.ready,\n source: keys.openverse.ready ? keys.openverse.source : null,\n })\n return { configPath, configExists, groupOrOtherReadable, providers }\n}\n\nexport async function inspectGenerators(env: NodeJS.ProcessEnv, run?: ProcessRunner): Promise<DoctorGenerator[]> {\n let file: Awaited<ReturnType<typeof findUserConfig>> = null\n try {\n file = await findUserConfig()\n } catch {\n file = null\n }\n const flags = resolveGenerators({ file: file?.config ?? null })\n return probeGenerators({ env, run, enabled: flags.enabled })\n}\n\n/**\n * Push a tiny built-in deck through the real pipeline — validate, render one\n * slide to SVG, generate the .pptx bytes — entirely in memory, nothing written\n * to disk. Everything else in this report is an observation about the\n * environment; this is the one check that proves the thing actually works.\n * A failure here is a hard error: if this deck cannot render, no deck can.\n */\nexport async function runSelfTest(): Promise<DoctorSelfTest> {\n const started = performance.now()\n const elapsed = () => Math.round(performance.now() - started)\n try {\n const v = validateIr(SELF_TEST_DECK)\n if (!v.ok) throw new Error(`the built-in self-test deck failed validation:\\n${formatIssues(v.errors)}`)\n const svg = renderSlideSvg(v.ir!, 0)\n if (!svg.includes(\"<svg\")) throw new Error(\"the SVG renderer produced no <svg> element\")\n const bytes = await generatePptx(v.ir!)\n if (bytes.length === 0) throw new Error(\"the PPTX generator produced zero bytes\")\n return { ok: true, elapsedMs: elapsed(), slides: v.ir!.slides.length, bytes: bytes.length }\n } catch (e) {\n return {\n ok: false,\n elapsedMs: elapsed(),\n slides: SELF_TEST_DECK.slides.length,\n bytes: null,\n error: e instanceof Error ? e.message : String(e),\n }\n }\n}\n\n/** How to refresh a stale skill copy: exactly INSTALL.md step 2's clone and\n * copy, aimed at the copy that is actually behind. Re-running it overwrites in\n * place, which is the whole update procedure. The source is a fresh clone\n * rather than the global npm root on purpose — installing the skill needs no\n * global CLI install, so there may well be no npm root to copy from. */\nfunction skillRefreshCommand(copyDir: string): string {\n return `rm -rf /tmp/pptwise-src && git clone --depth 1 https://github.com/liustack/pptwise.git /tmp/pptwise-src && cp -R /tmp/pptwise-src/skills/${SKILL_DIR_NAME}/. ${copyDir}/`\n}\n\nexport async function buildDoctorReport(input: DoctorInput = {}): Promise<DoctorReport> {\n const version = input.version ?? VERSION\n const home = input.home ?? homedir()\n const env = input.env ?? process.env\n const nodeVersion = input.nodeVersion ?? process.version\n\n const cwd = input.cwd ?? process.cwd()\n const [skills, dsh, capabilities, selfTest, projectHit, images, generators] = await Promise.all([\n scanSkillCopies(home, version),\n inspectDsh(home, version),\n inspectCapabilities(env),\n runSelfTest(),\n findConfig(cwd),\n inspectImages(env),\n inspectGenerators(env, input.runProcess),\n ])\n const workspace = await inspectWorkspace({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n runGit: input.runGit,\n })\n\n // An unparsable version string can never prove the runtime is too old, so it\n // passes rather than hard-failing a run over a string this cannot read.\n let meetsMinimum = true\n try {\n meetsMinimum = compareVersions(nodeVersion, MIN_NODE) >= 0\n } catch {\n meetsMinimum = true\n }\n const runtime: DoctorRuntime = {\n node: nodeVersion,\n bun: process.versions.bun ?? null,\n minimum: MIN_NODE,\n meetsMinimum,\n }\n\n const errors: DoctorFinding[] = []\n const warnings: DoctorFinding[] = []\n\n if (!runtime.meetsMinimum) {\n errors.push({\n check: \"runtime\",\n message: `Node ${runtime.node} is below the ${MIN_NODE} floor pptwise needs`,\n fix: `install Node ${MIN_NODE}+ from https://nodejs.org (or \\`nvm install 22\\`), open a new shell, then re-run \\`pptwise doctor\\``,\n })\n }\n if (!selfTest.ok) {\n errors.push({\n check: \"self-test\",\n message: `the self-test render failed: ${selfTest.error}`,\n fix: `reinstall the CLI: npm install -g ${PACKAGE_NAME}`,\n })\n }\n\n for (const copy of skills.copies) {\n if (copy.legacy) {\n warnings.push({\n check: \"skill copy\",\n message: `${copy.path} is a leftover ${basename(copy.path)} skill copy`,\n fix: `remove it and install the pptwise skill in ${join(copy.root, SKILL_DIR_NAME)}`,\n })\n } else if (copy.stale) {\n warnings.push({\n check: \"skill copy\",\n message: `${copy.path} pins ${copy.pinned}, behind this CLI's ${version}`,\n fix: `re-run the install to overwrite the copy in place: ${skillRefreshCommand(copy.path)}`,\n })\n } else if (copy.pinned === null) {\n warnings.push({\n check: \"skill copy\",\n message: `${copy.path} has ${copy.launcher === null ? \"no scripts/run.sh\" : \"a scripts/run.sh with no PINNED line\"} — version unknown, so drift cannot be ruled out`,\n fix: `re-run the install to restore a complete copy: ${skillRefreshCommand(copy.path)}`,\n })\n }\n if (copy.missing.length > 0) {\n warnings.push({\n check: \"skill copy\",\n message: `${copy.path} is missing ${copy.missing.join(\", \")}`,\n fix: `re-run the install to restore a complete copy: ${skillRefreshCommand(copy.path)}`,\n })\n }\n }\n\n for (const profile of dsh.profiles) {\n if (profile.stale) {\n warnings.push({\n check: \"dsh plugin\",\n message: `profile \"${profile.name}\" has ${PACKAGE_NAME} ${profile.version}, behind this CLI's ${version}`,\n fix: dshInstallCommand(profile.name, version),\n })\n }\n }\n\n for (const capability of capabilities) {\n if (!capability.available) {\n warnings.push({ check: \"capability\", message: `${capability.name}: ${capability.detail}`, fix: capability.fix })\n }\n }\n\n // Missing stock-photo keys stay in the Images section as `[-]`, not in\n // `warnings`. Rendering PPTX does not need them, so they must not steal\n // the \"pptwise is healthy\" line. A group/other-readable config file is\n // the one images finding that is a real warning.\n if (images.groupOrOtherReadable) {\n warnings.push({\n check: \"images\",\n message: `${images.configPath} is group/other-readable — API keys should be mode 0600`,\n fix: `chmod 600 ${images.configPath}`,\n })\n }\n\n return { version, runtime, skills, dsh, capabilities, selfTest, workspace, images, generators, errors, warnings }\n}\n\nfunction mark(state: \"ok\" | \"warn\" | \"fail\" | \"n/a\"): string {\n return state === \"ok\" ? \"[ok]\" : state === \"warn\" ? \"[!]\" : state === \"fail\" ? \"[!!]\" : \"[-]\"\n}\n\nexport function renderDoctorReport(report: DoctorReport): string {\n const lines: string[] = []\n\n lines.push(`pptwise doctor — CLI ${report.version}`)\n lines.push(\"(local diagnostics only: nothing is written, no network call is made)\")\n lines.push(\"\")\n\n lines.push(\"Installed skill copies (a copy keeps its install-time version forever)\")\n if (report.skills.copies.length === 0) {\n lines.push(\" [-] no installed skill copy found — the CLI runs fine on its own, and dsh ships the\")\n lines.push(\" skill inside the plugin instead\")\n lines.push(` looked in: ${report.skills.scanned.join(\", \")}`)\n } else {\n for (const copy of report.skills.copies) {\n const state = copy.legacy || copy.stale || copy.pinned === null || copy.missing.length > 0 ? \"warn\" : \"ok\"\n const pin = copy.legacy\n ? `leftover ${basename(copy.path)} copy`\n : copy.pinned === null\n ? \"version unknown\"\n : `pins ${copy.pinned}`\n lines.push(` ${mark(state)} ${copy.harness}: ${copy.path} — ${pin}${copy.stale && !copy.legacy ? \" (stale)\" : \"\"}`)\n if (copy.legacy) {\n lines.push(` fix: remove it and install the pptwise skill in ${join(copy.root, SKILL_DIR_NAME)}`)\n } else if (copy.stale) {\n lines.push(` fix: ${skillRefreshCommand(copy.path)}`)\n } else if (copy.pinned === null) {\n lines.push(` ${copy.launcher === null ? \"no scripts/run.sh in this copy\" : \"scripts/run.sh carries no PINNED line\"}`)\n lines.push(` fix: ${skillRefreshCommand(copy.path)}`)\n }\n if (copy.missing.length > 0) {\n lines.push(` missing: ${copy.missing.join(\", \")}`)\n lines.push(` fix: ${skillRefreshCommand(copy.path)}`)\n }\n }\n }\n lines.push(\"\")\n\n lines.push(\"DSH plugin\")\n if (!report.dsh.applicable) {\n lines.push(` ${mark(\"n/a\")} no ${report.dsh.home} — not a dsh machine, this check does not apply`)\n } else if (report.dsh.profiles.length === 0) {\n lines.push(` ${mark(\"n/a\")} ${report.dsh.home} exists but has no profiles to inspect`)\n } else {\n for (const profile of report.dsh.profiles) {\n if (!profile.installed) {\n lines.push(` ${mark(\"n/a\")} ${profile.name}: ${PACKAGE_NAME} not installed in this profile`)\n continue\n }\n const version = profile.version ?? \"version unknown\"\n lines.push(` ${mark(profile.stale ? \"warn\" : \"ok\")} ${profile.name}: ${version}${profile.source ? ` (via ${profile.source})` : \"\"}${profile.stale ? \" (behind this CLI)\" : \"\"}`)\n if (profile.stale) {\n lines.push(` fix: ${dshInstallCommand(profile.name, report.version)}`)\n }\n }\n }\n lines.push(\"\")\n\n lines.push(\"Runtime\")\n lines.push(` ${mark(report.runtime.meetsMinimum ? \"ok\" : \"fail\")} Node ${report.runtime.node} (minimum ${report.runtime.minimum})`)\n if (report.runtime.bun !== null) {\n lines.push(` ${mark(\"ok\")} running under Bun ${report.runtime.bun}`)\n }\n lines.push(\"\")\n\n lines.push(\"Optional capabilities (neither is needed by the main flow)\")\n for (const capability of report.capabilities) {\n lines.push(` ${mark(capability.available ? \"ok\" : \"warn\")} ${capability.name}: ${capability.detail}`)\n if (capability.fix) lines.push(` fix: ${capability.fix}`)\n }\n lines.push(\"\")\n\n lines.push(\"Self-test render (a built-in deck through the real pipeline, in memory)\")\n lines.push(\n report.selfTest.ok\n ? ` ${mark(\"ok\")} ${report.selfTest.slides} slides validated, rendered, and packed into ${report.selfTest.bytes} bytes in ${report.selfTest.elapsedMs}ms`\n : ` ${mark(\"fail\")} failed after ${report.selfTest.elapsedMs}ms: ${report.selfTest.error}`,\n )\n lines.push(\"\")\n\n lines.push(\"Workspace artifacts (where render/preview write when -o is omitted)\")\n lines.push(` ${mark(\"ok\")} anchor ${report.workspace.anchor}`)\n lines.push(\n report.workspace.configured\n ? ` ${mark(\"ok\")} output ${report.workspace.root} (from pptwise.config.json outDir)`\n : ` ${mark(\"ok\")} output ${report.workspace.root}`,\n )\n if (report.workspace.ignore === \"ignored\") {\n lines.push(` ${mark(\"ok\")} git-ignored`)\n } else if (report.workspace.ignore === \"not-ignored\") {\n lines.push(` ${mark(\"n/a\")} not git-ignored — the first render will add a local exclude line`)\n } else if (report.workspace.ignore === \"skipped\") {\n lines.push(` ${mark(\"n/a\")} git-ignore skipped (outDir is set in pptwise.config.json)`)\n } else {\n lines.push(` ${mark(\"n/a\")} not a git repository`)\n }\n lines.push(\"\")\n\n lines.push(\"Images (optional stock-photo search — rendering PPTX still needs no credentials)\")\n lines.push(` ${mark(\"ok\")} config ${report.images.configPath}${report.images.configExists ? \"\" : \" (missing)\"}`)\n if (report.images.groupOrOtherReadable) {\n lines.push(` ${mark(\"warn\")} file is group/other-readable — chmod 600`)\n }\n for (const provider of report.images.providers) {\n if (provider.present) {\n lines.push(` ${mark(\"ok\")} ${provider.provider}: present (${provider.source})`)\n } else if (provider.provider === \"openverse\") {\n lines.push(` ${mark(\"n/a\")} openverse: missing — pptwise config set openverse.clientId`)\n } else {\n lines.push(` ${mark(\"n/a\")} ${provider.provider}: missing — pptwise config set ${provider.provider}.apiKey`)\n }\n }\n lines.push(\"\")\n\n lines.push(\"Image generators (optional, off until enabled)\")\n for (const gen of report.generators) {\n if (!gen.found) {\n lines.push(` ${mark(\"n/a\")} ${gen.id}: not found`)\n continue\n }\n const ver = gen.version ? `, ${gen.version}` : \"\"\n const state = gen.enabled ? \"enabled\" : \"disabled\"\n const hint = gen.enabled ? \"\" : ` — pptwise config set images.generators.${gen.id}.enabled true`\n lines.push(` ${mark(\"ok\")} ${gen.id}: found (${gen.bin}${ver}) ${state}${hint}`)\n }\n lines.push(\"\")\n\n lines.push(\n `${report.errors.length} error${report.errors.length === 1 ? \"\" : \"s\"}, ${report.warnings.length} warning${report.warnings.length === 1 ? \"\" : \"s\"}`,\n )\n for (const error of report.errors) {\n lines.push(` [!!] ${error.check}: ${error.message}`)\n if (error.fix) lines.push(` fix: ${error.fix}`)\n }\n for (const warning of report.warnings) {\n lines.push(` [!] ${warning.check}: ${warning.message}`)\n }\n if (report.errors.length === 0) {\n lines.push(report.warnings.length === 0 ? \"pptwise is healthy on this machine\" : \"nothing blocking — the warnings above are worth fixing when convenient\")\n }\n\n return lines.join(\"\\n\")\n}\n\nexport interface DoctorOptions extends DoctorInput {\n json?: boolean\n}\n\nexport interface DoctorCliResult {\n /** The human report ({@link renderDoctorReport}) or, with `opts.json`, the\n * `JSON.stringify`'d {@link DoctorReport} verbatim. */\n output: string\n /** True only when a hard error was found (runtime below the floor, or a\n * failed self-test render). The CLI prints `output` either way, then exits 1\n * on this signal alone — a stale skill copy or a missing optional\n * capability is a warning and still exits 0, same advisory posture\n * `runValidate`'s own warnings already have. */\n hasErrors: boolean\n}\n\nexport async function runDoctor(opts: DoctorOptions = {}): Promise<DoctorCliResult> {\n const report = await buildDoctorReport(opts)\n return {\n output: opts.json ? JSON.stringify(report, null, 2) : renderDoctorReport(report),\n hasErrors: report.errors.length > 0,\n }\n}\n","/**\n * Local image-generator probe and adapters (grok / codex / antigravity).\n * Detection looks up binaries on PATH and optionally runs `--version`.\n * Generation is invoked through an injected {@link ProcessRunner} so tests\n * never spawn the real CLIs.\n */\nimport { copyFile, readdir, stat } from \"node:fs/promises\"\nimport { join } from \"node:path\"\nimport { PptwiseError } from \"../errors\"\nimport { sniffImageFormat } from \"../ir/asset-sniff\"\nimport { ChildTimeoutError, runChild } from \"./child\"\nimport { GENERATOR_IDS, type GeneratorId } from \"./image-config\"\nimport { findOnPath } from \"./path-lookup\"\n\nexport { findOnPath }\n\nexport const GENERATOR_PROBE_TIMEOUT_MS = 2000\n\nexport interface ProcessRun {\n command: string\n args: string[]\n cwd?: string\n timeoutMs: number\n env?: NodeJS.ProcessEnv\n}\nexport type ProcessRunner = (req: ProcessRun) => Promise<{ code: number; stdout: string; stderr: string }>\n\nexport interface GeneratorProbe {\n id: GeneratorId\n found: boolean\n bin: string | null\n version: string | null\n enabled: boolean\n}\n\nexport interface AdapterRequest {\n bin: string\n workdir: string\n dest: string\n prompt: string\n timeoutMs: number\n run: ProcessRunner\n}\n\nconst BIN_NAMES: Record<GeneratorId, readonly string[]> = {\n grok: [\"grok\"],\n codex: [\"codex\"],\n antigravity: [\"antigravity\", \"agy\"],\n}\n\nexport async function locateGeneratorBin(id: GeneratorId, env: NodeJS.ProcessEnv): Promise<string | null> {\n for (const name of BIN_NAMES[id]) {\n const found = await findOnPath(name, env)\n if (found) return found\n }\n return null\n}\n\nexport function parseGeneratorVersion(stdout: string): string {\n const line = stdout.trim().split(\"\\n\")[0] ?? \"\"\n const m = /(\\d+\\.\\d+\\.\\d+\\S*)/.exec(line)\n return m?.[1] ?? line\n}\n\nexport async function defaultProcessRunner(req: ProcessRun): Promise<{ code: number; stdout: string; stderr: string }> {\n try {\n return await runChild(req.command, req.args, {\n cwd: req.cwd,\n env: req.env,\n timeoutMs: req.timeoutMs,\n })\n } catch (error) {\n if (error instanceof ChildTimeoutError) {\n throw new PptwiseError(`image generator timed out after ${req.timeoutMs}ms`)\n }\n const message = error instanceof Error ? error.message : String(error)\n return { code: 1, stdout: \"\", stderr: message }\n }\n}\n\nexport async function probeGenerators(opts: {\n env?: NodeJS.ProcessEnv\n run?: ProcessRunner\n enabled: Record<GeneratorId, boolean>\n}): Promise<GeneratorProbe[]> {\n const env = opts.env ?? process.env\n const run = opts.run ?? defaultProcessRunner\n const probes: GeneratorProbe[] = []\n for (const id of GENERATOR_IDS) {\n const bin = await locateGeneratorBin(id, env)\n let version: string | null = null\n if (bin) {\n try {\n const result = await run({ command: bin, args: [\"--version\"], cwd: undefined, timeoutMs: GENERATOR_PROBE_TIMEOUT_MS, env })\n if (result.code === 0) version = parseGeneratorVersion(result.stdout)\n } catch {\n version = null\n }\n }\n probes.push({ id, found: bin !== null, bin, version, enabled: opts.enabled[id] === true })\n }\n return probes\n}\n\nfunction destContractPrompt(userPrompt: string, destAbs: string, toolName: string): string {\n return [\n `Generate exactly one image with the ${toolName} tool using this prompt:`,\n userPrompt,\n `Then copy or move the saved file to ${destAbs} so that path exists as a real image file.`,\n \"Print DONE when that path exists. Edit no other files.\",\n ].join(\"\\n\")\n}\n\nasync function isSniffedImage(path: string): Promise<boolean> {\n try {\n const { readFile } = await import(\"node:fs/promises\")\n const bytes = await readFile(path)\n return sniffImageFormat(bytes) !== null\n } catch {\n return false\n }\n}\n\nasync function collectFiles(dir: string, acc: { path: string; mtime: number }[]): Promise<void> {\n let entries\n try {\n entries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n await collectFiles(full, acc)\n } else if (entry.isFile()) {\n try {\n const st = await stat(full)\n acc.push({ path: full, mtime: st.mtimeMs })\n } catch {\n // skip\n }\n }\n }\n}\n\nasync function harvestNewestImage(workdir: string, dest: string): Promise<boolean> {\n const files: { path: string; mtime: number }[] = []\n await collectFiles(workdir, files)\n const images: { path: string; mtime: number }[] = []\n for (const file of files) {\n if (file.path === dest) continue\n if (await isSniffedImage(file.path)) images.push(file)\n }\n images.sort((a, b) => b.mtime - a.mtime)\n const newest = images[0]\n if (!newest) return false\n await copyFile(newest.path, dest)\n return isSniffedImage(dest)\n}\n\nasync function settleDest(req: AdapterRequest, result: { code: number; stdout: string; stderr: string }): Promise<void> {\n if (await isSniffedImage(req.dest)) return\n if (await harvestNewestImage(req.workdir, req.dest)) return\n const detail = (result.stderr || result.stdout).trim().slice(0, 400)\n throw new PptwiseError(\n result.code === 0\n ? `produced no image file at ${req.dest}${detail ? `: ${detail}` : \"\"}`\n : `exited ${result.code}${detail ? `: ${detail}` : \"\"}`,\n )\n}\n\nexport async function runGrokAdapter(req: AdapterRequest): Promise<void> {\n const result = await req.run({\n command: req.bin,\n args: [\n \"-p\",\n destContractPrompt(req.prompt, req.dest, \"image_gen\"),\n \"--cwd\",\n req.workdir,\n \"--permission-mode\",\n \"bypassPermissions\",\n \"--max-turns\",\n \"12\",\n ],\n cwd: req.workdir,\n timeoutMs: req.timeoutMs,\n })\n await settleDest(req, result)\n}\n\nexport async function runCodexAdapter(req: AdapterRequest): Promise<void> {\n const result = await req.run({\n command: req.bin,\n args: [\n \"exec\",\n \"--dangerously-bypass-approvals-and-sandbox\",\n \"--skip-git-repo-check\",\n \"--ephemeral\",\n \"-C\",\n req.workdir,\n destContractPrompt(req.prompt, req.dest, \"image_gen__imagegen\"),\n ],\n cwd: req.workdir,\n timeoutMs: req.timeoutMs,\n })\n await settleDest(req, result)\n}\n\nexport async function runAntigravityAdapter(req: AdapterRequest): Promise<void> {\n const seconds = Math.max(1, Math.ceil(req.timeoutMs / 1000))\n const result = await req.run({\n command: req.bin,\n args: [\n \"-p\",\n destContractPrompt(req.prompt, req.dest, \"generate_image\"),\n \"--dangerously-skip-permissions\",\n \"--print-timeout\",\n `${seconds}s`,\n ],\n cwd: req.workdir,\n timeoutMs: req.timeoutMs,\n })\n await settleDest(req, result)\n}\n\nexport const GENERATOR_ADAPTERS: Record<GeneratorId, (req: AdapterRequest) => Promise<void>> = {\n grok: runGrokAdapter,\n codex: runCodexAdapter,\n antigravity: runAntigravityAdapter,\n}\n","// Ported from markpress src/update.ts (same author), minus its playwright\n// post-install step — pptwise has no browser dependency to refresh.\nimport { runChild } from \"./child\"\n\nexport const PACKAGE_NAME = \"@liustack/pptwise\"\n\nexport interface UpdateInfo {\n packageName: string\n currentVersion: string\n latestVersion: string | null\n updateAvailable: boolean\n checked: boolean\n error?: string\n}\n\nexport interface SelfUpdateResult extends UpdateInfo {\n updated: boolean\n}\n\nexport type CommandRunner = (command: string, args: string[]) => Promise<string>\n\nfunction normalizeVersion(version: string): string {\n const normalized = version.trim().replace(/^v/i, \"\").split(\"-\")[0]\n if (!normalized) throw new Error(`invalid version: ${version}`)\n return normalized\n}\n\nfunction parseVersion(version: string): number[] {\n return normalizeVersion(version)\n .split(\".\")\n .map((segment) => {\n const value = Number.parseInt(segment, 10)\n if (!Number.isFinite(value)) throw new Error(`invalid version segment: ${segment}`)\n return value\n })\n}\n\nexport function compareVersions(left: string, right: string): number {\n const l = parseVersion(left)\n const r = parseVersion(right)\n for (let i = 0; i < Math.max(l.length, r.length); i++) {\n const a = l[i] ?? 0\n const b = r[i] ?? 0\n if (a !== b) return a < b ? -1 : 1\n }\n return 0\n}\n\nexport const runCommand: CommandRunner = async (command, args) => {\n const { code, stdout, stderr } = await runChild(command, args)\n if (code !== 0) throw new Error(stderr.trim() || `exited ${code}`)\n return stdout.trim()\n}\n\nexport interface CheckForUpdateOptions {\n currentVersion: string\n packageName?: string\n run?: CommandRunner\n}\n\n/** Never throws — an unreachable registry reports { checked: false, error }. */\nexport async function checkForUpdate({\n currentVersion,\n packageName = PACKAGE_NAME,\n run = runCommand,\n}: CheckForUpdateOptions): Promise<UpdateInfo> {\n const current = normalizeVersion(currentVersion)\n try {\n const latest = normalizeVersion(await run(\"npm\", [\"view\", packageName, \"version\"]))\n return {\n packageName,\n currentVersion: current,\n latestVersion: latest,\n updateAvailable: compareVersions(current, latest) < 0,\n checked: true,\n }\n } catch (error) {\n return {\n packageName,\n currentVersion: current,\n latestVersion: null,\n updateAvailable: false,\n checked: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\nexport function createSelfUpdater(run: CommandRunner = runCommand) {\n return async ({\n currentVersion,\n packageName = PACKAGE_NAME,\n }: {\n currentVersion: string\n packageName?: string\n }): Promise<SelfUpdateResult> => {\n const info = await checkForUpdate({ currentVersion, packageName, run })\n if (!info.checked) throw new Error(`unable to check for updates: ${info.error ?? \"unknown error\"}`)\n if (!info.updateAvailable) return { ...info, updated: false }\n await run(\"npm\", [\"install\", \"-g\", `${packageName}@latest`])\n return { ...info, updated: true }\n }\n}\n","/**\n * `pptwise images search|fetch|list|generate` — Pexels first, Pixabay as\n * empty-result fallback, then Openverse (cc0/pdm). Local generators pin\n * through the same sidecar path. Inject `fetch` / `resizeToJpeg` / `run`\n * so tests never touch the network or spawn real CLIs.\n */\nimport { mkdir, mkdtemp, readFile, readdir, rename, rm, unlink, writeFile } from \"node:fs/promises\"\nimport { tmpdir } from \"node:os\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { PptwiseError } from \"../errors\"\nimport { sniffImageFormat } from \"../ir/asset-sniff\"\nimport { isMissingModuleError } from \"../platform/node\"\nimport { buildAssetBrief } from \"../svg/asset-brief\"\nimport { VERSION } from \"../version\"\nimport type * as Sharp from \"sharp\"\nimport { loadValidatedDeckIr } from \"./commands\"\nimport { findConfig, findUserConfig } from \"./config\"\nimport { assertSafeFileSegment, ASSETS_DIRNAME, isDeckDirectory, pathExists, resolveDeckTarget } from \"./deck-dir\"\nimport {\n GENERATOR_IDS,\n knownSecretsFrom,\n missingKeysError,\n resolveGenerators,\n resolveImageKeys,\n type GeneratorId,\n type ImageProviderId,\n type ResolvedImageKeys,\n} from \"./image-config\"\nimport {\n defaultProcessRunner,\n GENERATOR_ADAPTERS,\n locateGeneratorBin,\n type ProcessRunner,\n} from \"./image-generators\"\nimport { defaultSleep, loadOpenverseDetail, searchOpenverse, type SleepFn } from \"./image-openverse\"\nimport { proxyFetch } from \"./proxy-fetch\"\nimport { assertSafeRemoteTarget, defaultDnsLookup, pinnedFetch, type DnsLookup } from \"./ssrf\"\nimport { redactSecrets } from \"./redact\"\nimport { resolveWorkspaceLocation, type WorkspaceLocation } from \"./workspace\"\n\nexport type { ProcessRun, ProcessRunner } from \"./image-generators\"\n\nexport const BYTE_CAP = 15 * 1024 * 1024\nexport const MAX_LONG_EDGE = 1920\nconst PER_PAGE = 8\n\nexport type ResizeToJpeg = (bytes: Buffer, maxLongEdge: number) => Promise<Buffer>\n\nexport interface StockSidecar {\n provider: ImageProviderId\n photo_id?: string\n license: string\n author?: string\n page_url?: string\n attribution?: string\n source?: string\n query?: string\n prompt?: string\n downloaded_at?: string\n generated_at?: string\n}\n\nexport interface SearchHit {\n id: string\n provider: ImageProviderId\n photoId: string\n thumb: string\n width: number\n height: number\n author: string\n license: string\n pageUrl: string\n attribution: string\n source?: string\n}\n\nexport interface ImagesSearchOptions {\n orientation?: string\n color?: string\n minWidth?: number\n minHeight?: number\n fetch?: typeof fetch\n env?: NodeJS.ProcessEnv\n sleep?: SleepFn\n}\n\nexport interface ImagesFetchOptions {\n deck: string\n as: string\n cwd?: string\n query?: string\n fetch?: typeof fetch\n resizeToJpeg?: ResizeToJpeg\n env?: NodeJS.ProcessEnv\n now?: () => Date\n sleep?: SleepFn\n lookup?: DnsLookup\n}\n\nexport interface ImagesListOptions {\n deck: string\n cwd?: string\n}\n\nexport interface ImagesGenerateOptions {\n deck: string\n as: string\n prompt?: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n run?: ProcessRunner\n resolvePrompt?: (opts: { deck: string; as: string; cwd: string }) => Promise<string | undefined>\n resizeToJpeg?: ResizeToJpeg\n now?: () => Date\n}\n\ntype FetchImpl = typeof fetch\n\nfunction userAgent(): string {\n return `pptwise/${VERSION} (+https://pptwise.com)`\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n return null\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nasync function loadKeys(env: NodeJS.ProcessEnv): Promise<ResolvedImageKeys> {\n const hit = await findUserConfig()\n return resolveImageKeys({ file: hit?.config ?? null, env })\n}\n\nasync function fetchJson(\n url: string,\n init: RequestInit,\n fetchImpl: FetchImpl,\n secrets: string[],\n): Promise<unknown> {\n let res: Response\n try {\n res = await fetchImpl(url, init)\n } catch (e) {\n throw new PptwiseError(\n redactSecrets(`stock image request failed: ${e instanceof Error ? e.message : String(e)}`, secrets),\n )\n }\n const text = await res.text()\n const redacted = redactSecrets(text.slice(0, 800), secrets)\n if (!res.ok) {\n if (res.status === 429) {\n throw new PptwiseError(`stock image API rate-limited (HTTP 429): ${redacted}`)\n }\n throw new PptwiseError(`stock image API HTTP ${res.status}: ${redacted}`)\n }\n try {\n return JSON.parse(text) as unknown\n } catch {\n throw new PptwiseError(`stock image API returned non-JSON (HTTP ${res.status})`)\n }\n}\n\nfunction pexelsHeaders(apiKey: string): Record<string, string> {\n return { Authorization: apiKey, \"User-Agent\": userAgent() }\n}\n\nconst ORIENTATIONS = new Set([\"landscape\", \"portrait\", \"square\"])\n\nfunction parseOrientation(raw: string | undefined): \"landscape\" | \"portrait\" | \"square\" | undefined {\n if (raw === undefined || raw === \"\") return undefined\n if (!ORIENTATIONS.has(raw)) {\n throw new PptwiseError(`invalid --orientation \"${raw}\" — expected landscape, portrait, or square`)\n }\n return raw as \"landscape\" | \"portrait\" | \"square\"\n}\n\nfunction clientFilter(hits: SearchHit[], minWidth?: number, minHeight?: number): SearchHit[] {\n return hits.filter((hit) => {\n if (minWidth !== undefined && hit.width < minWidth) return false\n if (minHeight !== undefined && hit.height < minHeight) return false\n return true\n })\n}\n\nfunction parsePexelsPhotos(json: unknown): SearchHit[] {\n const root = asRecord(json)\n const photos = Array.isArray(root?.photos) ? root.photos : []\n const hits: SearchHit[] = []\n for (const raw of photos) {\n const photo = asRecord(raw)\n if (!photo) continue\n const id = photo.id\n const photoId = typeof id === \"number\" || typeof id === \"string\" ? String(id) : \"\"\n if (!photoId) continue\n const src = asRecord(photo.src) ?? {}\n const author = asString(photo.photographer) ?? \"unknown\"\n const pageUrl = asString(photo.url) ?? `https://www.pexels.com/photo/${photoId}/`\n hits.push({\n id: `pexels:${photoId}`,\n provider: \"pexels\",\n photoId,\n thumb: asString(src.medium) ?? asString(src.tiny) ?? \"\",\n width: asNumber(photo.width) ?? 0,\n height: asNumber(photo.height) ?? 0,\n author,\n license: \"Pexels License\",\n pageUrl,\n attribution: `Photo by ${author} on Pexels`,\n })\n }\n return hits.slice(0, PER_PAGE)\n}\n\nfunction parsePixabayHits(json: unknown): SearchHit[] {\n const root = asRecord(json)\n const list = Array.isArray(root?.hits) ? root.hits : []\n const hits: SearchHit[] = []\n for (const raw of list) {\n const photo = asRecord(raw)\n if (!photo) continue\n const photoId = photo.id !== undefined ? String(photo.id) : \"\"\n if (!photoId) continue\n const author = asString(photo.user) ?? \"unknown\"\n const pageUrl = asString(photo.pageURL) ?? `https://pixabay.com/photos/${photoId}/`\n hits.push({\n id: `pixabay:${photoId}`,\n provider: \"pixabay\",\n photoId,\n thumb: asString(photo.previewURL) ?? \"\",\n width: asNumber(photo.imageWidth) ?? 0,\n height: asNumber(photo.imageHeight) ?? 0,\n author,\n license: \"Pixabay License\",\n pageUrl,\n attribution: `Photo by ${author} on Pixabay`,\n })\n }\n return hits.slice(0, PER_PAGE)\n}\n\nasync function searchPexels(\n query: string,\n apiKey: string,\n opts: ImagesSearchOptions,\n fetchImpl: FetchImpl,\n secrets: string[],\n): Promise<SearchHit[]> {\n const url = new URL(\"https://api.pexels.com/v1/search\")\n url.searchParams.set(\"query\", query)\n url.searchParams.set(\"locale\", \"zh-CN\")\n url.searchParams.set(\"per_page\", String(PER_PAGE))\n const orientation = parseOrientation(opts.orientation)\n if (orientation) url.searchParams.set(\"orientation\", orientation)\n if (opts.color) url.searchParams.set(\"color\", opts.color)\n const json = await fetchJson(url.toString(), { headers: pexelsHeaders(apiKey) }, fetchImpl, secrets)\n return clientFilter(parsePexelsPhotos(json), opts.minWidth, opts.minHeight)\n}\n\nfunction pixabayOrientation(orientation: string | undefined): string | undefined {\n if (orientation === \"landscape\") return \"horizontal\"\n if (orientation === \"portrait\") return \"vertical\"\n return undefined\n}\n\nasync function searchPixabay(\n query: string,\n apiKey: string,\n opts: ImagesSearchOptions,\n fetchImpl: FetchImpl,\n secrets: string[],\n): Promise<SearchHit[]> {\n const url = new URL(\"https://pixabay.com/api/\")\n url.searchParams.set(\"key\", apiKey)\n url.searchParams.set(\"q\", query.slice(0, 100))\n url.searchParams.set(\"lang\", \"zh\")\n url.searchParams.set(\"per_page\", String(PER_PAGE))\n url.searchParams.set(\"safesearch\", \"true\")\n const mapped = pixabayOrientation(parseOrientation(opts.orientation))\n if (mapped) url.searchParams.set(\"orientation\", mapped)\n if (opts.color) url.searchParams.set(\"colors\", opts.color)\n if (opts.minWidth !== undefined) url.searchParams.set(\"min_width\", String(opts.minWidth))\n if (opts.minHeight !== undefined) url.searchParams.set(\"min_height\", String(opts.minHeight))\n const json = await fetchJson(url.toString(), { headers: { \"User-Agent\": userAgent() } }, fetchImpl, secrets)\n return parsePixabayHits(json)\n}\n\nfunction formatHits(hits: SearchHit[]): string {\n return hits\n .map((hit) => {\n const thumb = hit.thumb ? `\\n ${hit.thumb}` : \"\"\n const source = hit.source ? ` ${hit.source}` : \"\"\n return `${hit.id} ${hit.width}x${hit.height} ${hit.author} ${hit.license}${source}${thumb}\\n ${hit.attribution}\\n ${hit.pageUrl}`\n })\n .join(\"\\n\")\n}\n\nfunction openverseNotes(anonymous: boolean, pixabaySkipped: boolean): string[] {\n const lines = [\"Openverse does not verify individual licenses. Results are filtered to cc0/pdm.\"]\n if (anonymous) {\n lines.push(\n \"Anonymous Openverse quota is very low. Set credentials with `pptwise config set openverse.clientId` and `pptwise config set openverse.clientSecret`.\",\n )\n }\n if (pixabaySkipped) {\n lines.push(\"Pixabay is unconfigured — pptwise config set pixabay.apiKey\")\n }\n return lines\n}\n\nexport async function runImagesSearch(query: string, opts: ImagesSearchOptions = {}): Promise<string> {\n const q = query.trim()\n if (q === \"\") throw new PptwiseError(\"search query must not be empty\")\n const env = opts.env ?? process.env\n const keys = await loadKeys(env)\n const secrets = knownSecretsFrom(keys)\n const fetchImpl = opts.fetch ?? proxyFetch\n const sleep = opts.sleep ?? defaultSleep\n\n if (keys.pexels.apiKey) {\n const pexelsHits = await searchPexels(q, keys.pexels.apiKey, opts, fetchImpl, secrets)\n if (pexelsHits.length > 0) return formatHits(pexelsHits)\n }\n\n if (keys.pixabay.apiKey) {\n const pixabayHits = await searchPixabay(q, keys.pixabay.apiKey, opts, fetchImpl, secrets)\n if (pixabayHits.length > 0) return formatHits(pixabayHits)\n }\n\n const orientation = parseOrientation(opts.orientation)\n const ovHits = await searchOpenverse({\n query: q,\n orientation,\n minWidth: opts.minWidth,\n minHeight: opts.minHeight,\n clientId: keys.openverse.ready ? keys.openverse.clientId : undefined,\n clientSecret: keys.openverse.ready ? keys.openverse.clientSecret : undefined,\n fetch: fetchImpl,\n secrets,\n sleep,\n })\n const notes = openverseNotes(!keys.openverse.ready, !keys.pixabay.apiKey)\n if (ovHits.length === 0) {\n return [\"No photos found.\", ...notes].join(\"\\n\")\n }\n const hits: SearchHit[] = ovHits.map((hit) => ({\n id: `openverse:${hit.id}`,\n provider: \"openverse\",\n photoId: hit.id,\n thumb: hit.thumbnail,\n width: hit.width,\n height: hit.height,\n author: hit.creator,\n license: hit.license,\n pageUrl: hit.foreignLandingUrl,\n attribution: hit.attribution,\n source: hit.source,\n }))\n return [...notes, formatHits(hits)].join(\"\\n\")\n}\n\ntype PhotoRefProvider = \"pexels\" | \"pixabay\" | \"openverse\"\n\nfunction parsePhotoRef(ref: string): { provider: PhotoRefProvider; photoId: string } {\n const m = /^(pexels|pixabay|openverse):(.+)$/.exec(ref.trim())\n if (!m) {\n throw new PptwiseError(`invalid photo ref \"${ref}\" — expected pexels:<id>, pixabay:<id>, or openverse:<id>`)\n }\n const photoId = m[2]!.trim()\n if (photoId === \"\" || photoId.includes(\"/\") || photoId.includes(\"\\\\\") || photoId.includes(\"..\")) {\n throw new PptwiseError(`invalid photo id in \"${ref}\"`)\n }\n return { provider: m[1] as PhotoRefProvider, photoId }\n}\n\nasync function resolveDeckWorkspace(\n deckArg: string,\n cwd: string,\n): Promise<{ location: WorkspaceLocation; assetsDir: string }> {\n const [projectHit, userHit] = await Promise.all([findConfig(cwd), findUserConfig()])\n const decksDirSource =\n projectHit?.config.decksDir !== undefined\n ? { decksDir: resolve(dirname(projectHit.path), projectHit.config.decksDir) }\n : userHit?.config\n const target = await resolveDeckTarget(deckArg, decksDirSource, cwd)\n const isDir = await isDeckDirectory(target)\n const location = resolveWorkspaceLocation({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n target,\n isDir,\n })\n return { location, assetsDir: join(location.dir, ASSETS_DIRNAME) }\n}\n\nfunction assertSafeDownloadUrl(url: string): URL {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n throw new PptwiseError(\"invalid download URL\")\n }\n if (parsed.protocol !== \"https:\") {\n throw new PptwiseError(\"refusing non-HTTPS download URL\")\n }\n if (parsed.username !== \"\" || parsed.password !== \"\") {\n throw new PptwiseError(\"refusing download URL with embedded userinfo\")\n }\n return parsed\n}\n\nasync function downloadBytes(\n url: string,\n fetchImpl: FetchImpl | undefined,\n secrets: string[],\n lookup?: DnsLookup,\n): Promise<Buffer> {\n const parsed = assertSafeDownloadUrl(url)\n let pin\n try {\n pin = await assertSafeRemoteTarget(parsed, lookup)\n } catch (e) {\n throw new PptwiseError(redactSecrets(e instanceof Error ? e.message : String(e), secrets))\n }\n let res: Response\n try {\n const init = { headers: { \"User-Agent\": userAgent() } }\n res = fetchImpl\n ? await fetchImpl(parsed.toString(), init)\n : await pinnedFetch(parsed, pin, init)\n } catch (e) {\n throw new PptwiseError(\n redactSecrets(`download failed: ${e instanceof Error ? e.message : String(e)}`, secrets),\n )\n }\n if (!res.ok) {\n throw new PptwiseError(`download HTTP ${res.status}`)\n }\n const declared = Number(res.headers.get(\"content-length\") ?? \"0\")\n if (declared > BYTE_CAP) {\n throw new PptwiseError(`download exceeds the ${BYTE_CAP} byte cap`)\n }\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.byteLength > BYTE_CAP) {\n throw new PptwiseError(`download exceeds the ${BYTE_CAP} byte cap`)\n }\n return buf\n}\n\nexport async function defaultResizeToJpeg(bytes: Buffer, maxLongEdge: number): Promise<Buffer> {\n let sharpMod: typeof Sharp.default\n try {\n sharpMod = (await import(\"sharp\")).default as unknown as typeof Sharp.default\n } catch (e) {\n if (isMissingModuleError(e)) {\n throw new PptwiseError(`Resizing stock photos requires the optional dependency \"sharp\" (npm i sharp)`)\n }\n throw e\n }\n const image = sharpMod(bytes)\n const meta = await image.metadata()\n const width = meta.width ?? 0\n const height = meta.height ?? 0\n const long = Math.max(width, height)\n const pipeline = long > maxLongEdge ? (width >= height ? image.resize({ width: maxLongEdge }) : image.resize({ height: maxLongEdge })) : image\n return pipeline.jpeg({ quality: 85 }).toBuffer()\n}\n\nasync function toJpeg(\n bytes: Buffer,\n apiLongEdge: number | undefined,\n resize: ResizeToJpeg,\n): Promise<Buffer> {\n const format = sniffImageFormat(bytes)\n if (format === null) {\n throw new PptwiseError(\"downloaded bytes are not a recognized image (png/jpeg/gif/webp)\")\n }\n if (format === \"jpeg\" && apiLongEdge !== undefined && apiLongEdge <= MAX_LONG_EDGE) {\n return bytes\n }\n return resize(bytes, MAX_LONG_EDGE)\n}\n\ninterface PhotoMeta {\n author: string\n pageUrl: string\n license: string\n downloadUrl: string\n fallbackUrl?: string\n width?: number\n height?: number\n attribution?: string\n source?: string\n}\n\nasync function loadPexelsPhoto(photoId: string, apiKey: string, fetchImpl: FetchImpl, secrets: string[]): Promise<PhotoMeta> {\n const url = `https://api.pexels.com/v1/photos/${encodeURIComponent(photoId)}`\n const json = await fetchJson(url, { headers: pexelsHeaders(apiKey) }, fetchImpl, secrets)\n const photo = asRecord(json)\n if (!photo) throw new PptwiseError(`Pexels photo ${photoId} was not found`)\n const src = asRecord(photo.src) ?? {}\n const original = asString(src.original)\n const large2x = asString(src.large2x)\n const downloadUrl = original ?? large2x\n if (!downloadUrl) throw new PptwiseError(`Pexels photo ${photoId} has no download URL`)\n const author = asString(photo.photographer) ?? \"unknown\"\n return {\n author,\n pageUrl: asString(photo.url) ?? `https://www.pexels.com/photo/${photoId}/`,\n license: \"Pexels License\",\n downloadUrl,\n fallbackUrl: original ? large2x : undefined,\n width: asNumber(photo.width),\n height: asNumber(photo.height),\n }\n}\n\nasync function loadPixabayPhoto(photoId: string, apiKey: string, fetchImpl: FetchImpl, secrets: string[]): Promise<PhotoMeta> {\n const url = new URL(\"https://pixabay.com/api/\")\n url.searchParams.set(\"key\", apiKey)\n url.searchParams.set(\"id\", photoId)\n const json = await fetchJson(url.toString(), { headers: { \"User-Agent\": userAgent() } }, fetchImpl, secrets)\n const hits = parsePixabayHits(json)\n const hit = hits[0]\n const root = asRecord(json)\n const rawHits = Array.isArray(root?.hits) ? root.hits : []\n const raw = asRecord(rawHits[0])\n const downloadUrl = asString(raw?.largeImageURL)\n if (!hit || !downloadUrl) throw new PptwiseError(`Pixabay photo ${photoId} was not found`)\n return {\n author: hit.author,\n pageUrl: hit.pageUrl,\n license: \"Pixabay License\",\n downloadUrl,\n width: hit.width,\n height: hit.height,\n }\n}\n\nasync function readSidecar(path: string): Promise<StockSidecar | null> {\n try {\n const raw = JSON.parse(await readFile(path, \"utf8\")) as unknown\n const rec = asRecord(raw)\n if (!rec) return null\n const provider = asString(rec.provider) as ImageProviderId | undefined\n const known =\n provider === \"pexels\" ||\n provider === \"pixabay\" ||\n provider === \"openverse\" ||\n provider === \"grok\" ||\n provider === \"codex\" ||\n provider === \"antigravity\"\n if (!known || !provider) return null\n const photoId = asString(rec.photo_id)\n if ((provider === \"pexels\" || provider === \"pixabay\") && !photoId) return null\n return {\n provider,\n photo_id: photoId,\n license: asString(rec.license) ?? \"\",\n author: asString(rec.author),\n page_url: asString(rec.page_url),\n attribution: asString(rec.attribution),\n source: asString(rec.source),\n query: asString(rec.query),\n prompt: asString(rec.prompt),\n downloaded_at: asString(rec.downloaded_at),\n generated_at: asString(rec.generated_at),\n }\n } catch {\n return null\n }\n}\n\nexport async function runImagesFetch(ref: string, opts: ImagesFetchOptions): Promise<string> {\n const { provider, photoId } = parsePhotoRef(ref)\n assertSafeFileSegment(opts.as, \"asset id\")\n const cwd = opts.cwd ?? process.cwd()\n const env = opts.env ?? process.env\n const keys = await loadKeys(env)\n const secrets = knownSecretsFrom(keys)\n if (provider !== \"openverse\") {\n const apiKey = keys[provider].apiKey\n if (!apiKey) throw missingKeysError(provider)\n }\n\n const { assetsDir } = await resolveDeckWorkspace(opts.deck, cwd)\n const jpgPath = join(assetsDir, `${opts.as}.jpg`)\n const jsonPath = join(assetsDir, `${opts.as}.json`)\n if ((await pathExists(jpgPath)) && (await pathExists(jsonPath))) {\n const existing = await readSidecar(jsonPath)\n if (existing && existing.photo_id === photoId && existing.provider === provider) {\n return `already pinned ${provider}:${photoId} as ${opts.as} — skipped`\n }\n }\n\n const fetchImpl = opts.fetch ?? proxyFetch\n const sleep = opts.sleep ?? defaultSleep\n const downloadFetch = opts.fetch\n const lookup = opts.lookup ?? (opts.fetch ? undefined : defaultDnsLookup)\n let meta: PhotoMeta\n if (provider === \"openverse\") {\n const hit = await loadOpenverseDetail(\n photoId,\n {\n clientId: keys.openverse.ready ? keys.openverse.clientId : undefined,\n clientSecret: keys.openverse.ready ? keys.openverse.clientSecret : undefined,\n },\n fetchImpl,\n secrets,\n sleep,\n )\n meta = {\n author: hit.creator,\n pageUrl: hit.foreignLandingUrl,\n license: hit.license,\n downloadUrl: hit.url,\n width: hit.width,\n height: hit.height,\n attribution: hit.attribution,\n source: hit.source,\n }\n } else {\n const apiKey = keys[provider].apiKey!\n meta =\n provider === \"pexels\"\n ? await loadPexelsPhoto(photoId, apiKey, fetchImpl, secrets)\n : await loadPixabayPhoto(photoId, apiKey, fetchImpl, secrets)\n }\n\n let bytes: Buffer\n try {\n bytes = await downloadBytes(meta.downloadUrl, downloadFetch, secrets, lookup)\n } catch (e) {\n if (meta.fallbackUrl) {\n bytes = await downloadBytes(meta.fallbackUrl, downloadFetch, secrets, lookup)\n } else {\n throw e\n }\n }\n\n const apiLong = meta.width !== undefined && meta.height !== undefined ? Math.max(meta.width, meta.height) : undefined\n const resize = opts.resizeToJpeg ?? defaultResizeToJpeg\n const jpeg = await toJpeg(bytes, apiLong, resize)\n\n await mkdir(assetsDir, { recursive: true })\n await writeFile(jpgPath, jpeg)\n const sidecar: StockSidecar = {\n provider,\n photo_id: photoId,\n license: meta.license,\n author: meta.author,\n page_url: meta.pageUrl,\n downloaded_at: (opts.now ?? (() => new Date()))().toISOString(),\n }\n if (opts.query) sidecar.query = opts.query\n if (meta.attribution) sidecar.attribution = meta.attribution\n if (meta.source) sidecar.source = meta.source\n const json = JSON.stringify(sidecar, null, 2) + \"\\n\"\n if (/\"apiKey\"\\s*:/.test(json) || /\"key\"\\s*:/.test(json) || /\"clientSecret\"\\s*:/.test(json)) {\n throw new PptwiseError(\"internal error: sidecar would have contained a key field\")\n }\n await writeFile(jsonPath, json)\n return `pinned ${provider}:${photoId} as ${opts.as} → ${jpgPath}`\n}\n\nexport async function runImagesList(opts: ImagesListOptions): Promise<string> {\n const cwd = opts.cwd ?? process.cwd()\n const { assetsDir } = await resolveDeckWorkspace(opts.deck, cwd)\n let names: string[]\n try {\n names = (await readdir(assetsDir)).filter((n) => n.endsWith(\".json\") && !n.startsWith(\".\")).sort()\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return \"No pinned stock photos.\"\n throw e\n }\n const lines: string[] = []\n for (const name of names) {\n const sidecar = await readSidecar(join(assetsDir, name))\n if (!sidecar) continue\n const assetId = name.slice(0, -\".json\".length)\n const id = sidecar.photo_id ? `${sidecar.provider}:${sidecar.photo_id}` : sidecar.provider\n const rest = [sidecar.author, sidecar.license, sidecar.page_url ?? sidecar.generated_at].filter(Boolean)\n lines.push(`${assetId} ${id} ${rest.join(\" \")}`)\n }\n return lines.length === 0 ? \"No pinned stock photos.\" : lines.join(\"\\n\")\n}\n\nasync function writePinnedAsset(\n assetsDir: string,\n assetId: string,\n jpeg: Buffer,\n sidecar: StockSidecar,\n): Promise<string> {\n await mkdir(assetsDir, { recursive: true })\n const jpgPath = join(assetsDir, `${assetId}.jpg`)\n const jsonPath = join(assetsDir, `${assetId}.json`)\n const tmpJpg = join(assetsDir, `.${assetId}.jpg.tmp`)\n const tmpJson = join(assetsDir, `.${assetId}.json.tmp`)\n const json = JSON.stringify(sidecar, null, 2) + \"\\n\"\n if (/\"apiKey\"\\s*:/.test(json) || /\"key\"\\s*:/.test(json) || /\"clientSecret\"\\s*:/.test(json)) {\n throw new PptwiseError(\"internal error: sidecar would have contained a key field\")\n }\n try {\n await writeFile(tmpJpg, jpeg)\n await writeFile(tmpJson, json)\n await rename(tmpJpg, jpgPath)\n await rename(tmpJson, jsonPath)\n } catch (e) {\n await unlink(tmpJpg).catch(() => undefined)\n await unlink(tmpJson).catch(() => undefined)\n await unlink(jpgPath).catch(() => undefined)\n throw e\n }\n return jpgPath\n}\n\nasync function defaultResolvePrompt(opts: { deck: string; as: string; cwd: string }): Promise<string | undefined> {\n const ir = await loadValidatedDeckIr(opts.deck, opts.cwd)\n const brief = buildAssetBrief(ir)\n const item = brief.items.find((entry) => entry.asset_id === opts.as && entry.suggested_prompt.trim() !== \"\")\n return item?.suggested_prompt\n}\n\nexport async function runImagesGenerate(opts: ImagesGenerateOptions): Promise<string> {\n assertSafeFileSegment(opts.as, \"asset id\")\n const cwd = opts.cwd ?? process.cwd()\n const env = opts.env ?? process.env\n const hit = await findUserConfig()\n const gens = resolveGenerators({ file: hit?.config ?? null })\n const fromFlag = opts.prompt?.trim()\n const prompt =\n fromFlag && fromFlag !== \"\"\n ? fromFlag\n : await (opts.resolvePrompt ?? defaultResolvePrompt)({ deck: opts.deck, as: opts.as, cwd })\n if (!prompt) {\n throw new PptwiseError(`no prompt for asset \"${opts.as}\" — pass --prompt`)\n }\n\n const bins = {} as Record<GeneratorId, string | null>\n for (const id of GENERATOR_IDS) {\n bins[id] = await locateGeneratorBin(id, env)\n }\n\n const anyEnabled = GENERATOR_IDS.some((id) => gens.enabled[id])\n if (!anyEnabled) {\n const foundDisabled = GENERATOR_IDS.filter((id) => bins[id] !== null)\n if (foundDisabled.length === 0) {\n throw new PptwiseError(\n \"No image generator is enabled. Looked for grok, codex, antigravity. None were found on PATH.\",\n )\n }\n const listed = foundDisabled\n .map((id) => `${id} — pptwise config set images.generators.${id}.enabled true`)\n .join(\"; \")\n throw new PptwiseError(`No image generator is enabled. Found but disabled: ${listed}`)\n }\n\n const { assetsDir } = await resolveDeckWorkspace(opts.deck, cwd)\n const workdir = await mkdtemp(join(tmpdir(), \"pptwise-gen-\"))\n const dest = join(workdir, \"generated.jpg\")\n const run = opts.run ?? defaultProcessRunner\n const attempts: string[] = []\n try {\n for (const id of gens.order) {\n if (!gens.enabled[id]) continue\n const bin = bins[id]\n if (!bin) continue\n try {\n await GENERATOR_ADAPTERS[id]({\n bin,\n workdir,\n dest,\n prompt,\n timeoutMs: gens.timeoutMs,\n run,\n })\n const bytes = await readFile(dest)\n if (sniffImageFormat(bytes) === null) {\n throw new PptwiseError(\"produced bytes that are not a recognized image\")\n }\n const resize = opts.resizeToJpeg ?? defaultResizeToJpeg\n const jpeg = await toJpeg(bytes, undefined, resize)\n const sidecar: StockSidecar = {\n provider: id,\n license: \"user-generated\",\n prompt,\n generated_at: (opts.now ?? (() => new Date()))().toISOString(),\n }\n const pinned = await writePinnedAsset(assetsDir, opts.as, jpeg, sidecar)\n return `pinned ${id} as ${opts.as} → ${pinned}`\n } catch (e) {\n attempts.push(`${id}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n if (attempts.length === 0) {\n throw new PptwiseError(\n \"No enabled image generator was found on PATH. Looked for grok, codex, antigravity.\",\n )\n }\n throw new PptwiseError(`All image generators failed: ${attempts.join(\"; \")}`)\n } finally {\n await rm(workdir, { recursive: true, force: true })\n }\n}\n","/**\n * Strip secrets from strings that might leak into errors, logs, sidecars, or\n * stdout. Pixabay puts the API key in the query string as `key=`, which a\n * typical `api_key=` regex does not catch — both that form and an exact\n * replace of every known secret (length ≥ 6) have to run.\n */\n\nconst KEY_QUERY = /([?&](?:api_)?key=)[^&\\s\"'<>]*/gi\nconst CLIENT_SECRET_QUERY = /(client_secret=)[^&\\s\"'<>]*/gi\n\nexport function redactSecrets(text: string, knownSecrets: string[] = []): string {\n let out = text\n const secrets = knownSecrets.filter((s) => s.length >= 6).sort((a, b) => b.length - a.length)\n for (const secret of secrets) {\n out = out.split(secret).join(\"[redacted]\")\n }\n out = out.replace(KEY_QUERY, \"$1[redacted]\")\n out = out.replace(CLIENT_SECRET_QUERY, \"$1[redacted]\")\n return out\n}\n","/**\n * Openverse search / detail / OAuth token cache. Fetch and sleep are\n * injected so unit tests never hit the network or wait on backoff.\n */\nimport { PptwiseError } from \"../errors\"\nimport { VERSION } from \"../version\"\nimport { redactSecrets } from \"./redact\"\n\nexport const OPENVERSE_SEARCH_URL = \"https://api.openverse.org/v1/images/\"\nexport const OPENVERSE_TOKEN_URL = \"https://api.openverse.org/v1/auth_tokens/token/\"\nexport const OPENVERSE_PAGE_SIZE = 8\nconst TOKEN_SKEW_MS = 30_000\n\nexport type SleepFn = (ms: number) => Promise<void>\nexport const defaultSleep: SleepFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))\n\nexport interface OpenverseHit {\n id: string\n url: string\n foreignLandingUrl: string\n creator: string\n license: string\n attribution: string\n source: string\n width: number\n height: number\n thumbnail: string\n}\n\ninterface CachedToken {\n accessToken: string\n expiresAt: number\n}\n\nconst tokenCache = new Map<string, CachedToken>()\n\nexport function resetOpenverseTokenCache(): void {\n tokenCache.clear()\n}\n\nfunction userAgent(): string {\n return `pptwise/${VERSION} (+https://pptwise.com)`\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n return null\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nexport function isCc0OrPdm(license: string | undefined): boolean {\n const token = (license ?? \"\").toLowerCase()\n return token === \"cc0\" || token === \"pdm\"\n}\n\nfunction retryDelayMs(res: Response, attempt: number): number {\n const raw = res.headers.get(\"Retry-After\")\n if (raw !== null && raw !== \"\") {\n const seconds = Number(raw)\n if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1000)\n }\n return attempt === 0 ? 500 : 1500\n}\n\nasync function fetchOpenverseJson(\n url: string,\n init: RequestInit,\n fetchImpl: typeof fetch,\n secrets: string[],\n sleep: SleepFn,\n): Promise<unknown> {\n let attempt = 0\n while (true) {\n let res: Response\n try {\n res = await fetchImpl(url, init)\n } catch (e) {\n throw new PptwiseError(\n redactSecrets(`stock image request failed: ${e instanceof Error ? e.message : String(e)}`, secrets),\n )\n }\n const text = await res.text()\n if (res.status === 429) {\n if (attempt >= 2) {\n throw new PptwiseError(\n `stock image API rate-limited (HTTP 429): ${redactSecrets(text.slice(0, 800), secrets)}`,\n )\n }\n await sleep(retryDelayMs(res, attempt))\n attempt += 1\n continue\n }\n if (!res.ok) {\n throw new PptwiseError(`stock image API HTTP ${res.status}: ${redactSecrets(text.slice(0, 800), secrets)}`)\n }\n try {\n return JSON.parse(text) as unknown\n } catch {\n throw new PptwiseError(`stock image API returned non-JSON (HTTP ${res.status})`)\n }\n }\n}\n\nexport async function getOpenverseAccessToken(\n clientId: string,\n clientSecret: string,\n fetchImpl: typeof fetch,\n secrets: string[],\n sleep: SleepFn,\n now: () => number = Date.now,\n): Promise<string> {\n const cached = tokenCache.get(clientId)\n if (cached && now() < cached.expiresAt - TOKEN_SKEW_MS) return cached.accessToken\n const body = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: clientId,\n client_secret: clientSecret,\n }).toString()\n const json = await fetchOpenverseJson(\n OPENVERSE_TOKEN_URL,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\": userAgent(),\n },\n body,\n },\n fetchImpl,\n secrets,\n sleep,\n )\n const rec = asRecord(json)\n const accessToken = asString(rec?.access_token)\n const expiresIn = asNumber(rec?.expires_in) ?? 3600\n if (!accessToken) throw new PptwiseError(\"Openverse token response was missing access_token\")\n tokenCache.set(clientId, { accessToken, expiresAt: now() + expiresIn * 1000 })\n return accessToken\n}\n\nasync function authHeaders(\n creds: { clientId?: string; clientSecret?: string },\n fetchImpl: typeof fetch,\n secrets: string[],\n sleep: SleepFn,\n): Promise<Record<string, string>> {\n const headers: Record<string, string> = { \"User-Agent\": userAgent() }\n if (creds.clientId && creds.clientSecret) {\n const token = await getOpenverseAccessToken(creds.clientId, creds.clientSecret, fetchImpl, secrets, sleep)\n headers.Authorization = `Bearer ${token}`\n }\n return headers\n}\n\nfunction parseHit(raw: unknown): OpenverseHit | null {\n const photo = asRecord(raw)\n if (!photo) return null\n const id = asString(photo.id)\n const url = asString(photo.url)\n if (!id || !url) return null\n const license = (asString(photo.license) ?? \"\").toLowerCase()\n if (!isCc0OrPdm(license)) return null\n const source = asString(photo.source) ?? asString(photo.provider) ?? \"openverse\"\n return {\n id,\n url,\n foreignLandingUrl: asString(photo.foreign_landing_url) ?? \"\",\n creator: asString(photo.creator) ?? \"unknown\",\n license,\n attribution: asString(photo.attribution) ?? \"\",\n source,\n width: asNumber(photo.width) ?? 0,\n height: asNumber(photo.height) ?? 0,\n thumbnail: asString(photo.thumbnail) ?? \"\",\n }\n}\n\nexport interface OpenverseSearchOpts {\n query: string\n orientation?: \"landscape\" | \"portrait\" | \"square\"\n minWidth?: number\n minHeight?: number\n clientId?: string\n clientSecret?: string\n fetch: typeof fetch\n secrets: string[]\n sleep: SleepFn\n}\n\nfunction matchesOrientation(hit: OpenverseHit, orientation: \"landscape\" | \"portrait\" | \"square\" | undefined): boolean {\n if (!orientation) return true\n if (orientation === \"landscape\") return hit.width > hit.height\n if (orientation === \"portrait\") return hit.height > hit.width\n return hit.width === hit.height\n}\n\nexport async function searchOpenverse(opts: OpenverseSearchOpts): Promise<OpenverseHit[]> {\n const url = new URL(OPENVERSE_SEARCH_URL)\n url.searchParams.set(\"q\", opts.query)\n url.searchParams.set(\"license_type\", \"commercial\")\n url.searchParams.set(\"license\", \"cc0,pdm\")\n url.searchParams.set(\"page_size\", String(OPENVERSE_PAGE_SIZE))\n const headers = await authHeaders(\n { clientId: opts.clientId, clientSecret: opts.clientSecret },\n opts.fetch,\n opts.secrets,\n opts.sleep,\n )\n const json = await fetchOpenverseJson(url.toString(), { headers }, opts.fetch, opts.secrets, opts.sleep)\n const root = asRecord(json)\n const list = Array.isArray(root?.results) ? root.results : []\n const hits: OpenverseHit[] = []\n for (const raw of list) {\n const hit = parseHit(raw)\n if (!hit) continue\n if (opts.minWidth !== undefined && hit.width < opts.minWidth) continue\n if (opts.minHeight !== undefined && hit.height < opts.minHeight) continue\n if (!matchesOrientation(hit, opts.orientation)) continue\n hits.push(hit)\n if (hits.length >= OPENVERSE_PAGE_SIZE) break\n }\n return hits\n}\n\nexport async function loadOpenverseDetail(\n id: string,\n creds: { clientId?: string; clientSecret?: string },\n fetchImpl: typeof fetch,\n secrets: string[],\n sleep: SleepFn,\n): Promise<OpenverseHit> {\n const url = `${OPENVERSE_SEARCH_URL}${encodeURIComponent(id)}/`\n const headers = await authHeaders(creds, fetchImpl, secrets, sleep)\n const json = await fetchOpenverseJson(url, { headers }, fetchImpl, secrets, sleep)\n const license = asString(asRecord(json)?.license)\n if (!isCc0OrPdm(license)) {\n throw new PptwiseError(\n `Openverse photo ${id} is licensed \"${license ?? \"unknown\"}\", not cc0/pdm — refusing to download`,\n )\n }\n const hit = parseHit(json)\n if (!hit) throw new PptwiseError(`Openverse photo ${id} was not found`)\n return hit\n}\n","/**\n * SSRF guards for gallery downloads. Search APIs may use a proxy. The\n * pin-to-disk path must not: a proxy would connect to an address this\n * check never saw. DNS is resolved, every address is checked, and the\n * download pins the socket to the IP that passed.\n */\nimport { lookup as dnsLookup } from \"node:dns/promises\"\nimport { isIP } from \"node:net\"\nimport { Agent, fetch as undiciFetch } from \"undici\"\n\nexport interface PinnedTarget {\n hostname: string\n address: string\n family: number\n}\n\nexport type DnsLookup = (hostname: string) => Promise<Array<{ address: string; family: number }>>\n\nexport async function defaultDnsLookup(hostname: string): Promise<Array<{ address: string; family: number }>> {\n return dnsLookup(hostname, { all: true, verbatim: true })\n}\n\nconst BLOCKED_HOSTNAMES = new Set([\n \"localhost\",\n \"localhost.localdomain\",\n \"metadata.google.internal\",\n \"metadata.amazonaws.com\",\n \"metadata.azure.internal\",\n])\n\nexport function isBlockedHostname(hostname: string): boolean {\n const normalized = hostname.trim().toLowerCase()\n if (!normalized) return true\n if (BLOCKED_HOSTNAMES.has(normalized)) return true\n if (normalized.endsWith(\".localhost\")) return true\n return false\n}\n\nexport function isPrivateIpAddress(ipAddress: string): boolean {\n const normalized = ipAddress.trim().toLowerCase()\n const family = isIP(normalized)\n if (family === 4) return isPrivateIPv4(normalized)\n if (family === 6) return isPrivateIPv6(normalized)\n return true\n}\n\nexport async function assertSafeRemoteTarget(url: URL, lookup?: DnsLookup): Promise<PinnedTarget> {\n if (isBlockedHostname(url.hostname)) {\n throw new Error(blockedMessage(url.hostname))\n }\n\n const hostname = stripIpv6Brackets(url.hostname)\n const ipFamily = isIP(hostname)\n if (ipFamily > 0) {\n if (isPrivateIpAddress(hostname)) throw new Error(blockedMessage(hostname))\n return { hostname, address: hostname, family: ipFamily }\n }\n\n if (!lookup) return { hostname, address: hostname, family: 0 }\n\n let resolved: Array<{ address: string; family: number }>\n try {\n resolved = await lookup(hostname)\n } catch (error) {\n throw new Error(\n `DNS lookup failed for host ${hostname}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n if (resolved.length === 0) {\n throw new Error(`Host ${hostname} did not resolve to any IP address.`)\n }\n const blocked = resolved.find((record) => isPrivateIpAddress(record.address))\n if (blocked) throw new Error(blockedMessage(`${hostname} -> ${blocked.address}`))\n const [chosen] = resolved\n return { hostname, address: chosen!.address, family: chosen!.family }\n}\n\nexport async function pinnedFetch(url: URL, pin: PinnedTarget, init?: RequestInit): Promise<Response> {\n const dispatcher = new Agent({\n connect: {\n lookup: (_hostname: string, options: { all?: boolean } | undefined, callback: (...args: unknown[]) => void) => {\n const record = { address: pin.address, family: pin.family }\n if (options?.all) callback(null, [record])\n else callback(null, pin.address, pin.family)\n },\n } as never,\n })\n try {\n const response = await undiciFetch(url, {\n ...(init as Parameters<typeof undiciFetch>[1]),\n dispatcher,\n })\n const buffered = Buffer.from(await response.arrayBuffer())\n await dispatcher.close()\n return new Response(buffered, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers as HeadersInit,\n })\n } catch (error) {\n await dispatcher.close().catch(() => {})\n throw error\n }\n}\n\nfunction blockedMessage(target: string): string {\n return `Blocked private or reserved download target: ${target}. pptwise does not download from private addresses.`\n}\n\nfunction stripIpv6Brackets(hostname: string): string {\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) return hostname.slice(1, -1)\n return hostname\n}\n\nfunction isPrivateIPv4(ipAddress: string): boolean {\n const octets = ipAddress.split(\".\").map((part) => Number.parseInt(part, 10))\n if (octets.length !== 4 || octets.some((value) => !Number.isFinite(value) || value < 0 || value > 255)) {\n return true\n }\n const value = octets[0]! * 256 ** 3 + octets[1]! * 256 ** 2 + octets[2]! * 256 + octets[3]!\n return (\n inRange(value, \"0.0.0.0\", \"0.255.255.255\") ||\n inRange(value, \"10.0.0.0\", \"10.255.255.255\") ||\n inRange(value, \"100.64.0.0\", \"100.127.255.255\") ||\n inRange(value, \"127.0.0.0\", \"127.255.255.255\") ||\n inRange(value, \"169.254.0.0\", \"169.254.255.255\") ||\n inRange(value, \"172.16.0.0\", \"172.31.255.255\") ||\n inRange(value, \"192.0.0.0\", \"192.0.0.255\") ||\n inRange(value, \"192.168.0.0\", \"192.168.255.255\") ||\n inRange(value, \"198.18.0.0\", \"198.19.255.255\") ||\n inRange(value, \"224.0.0.0\", \"255.255.255.255\")\n )\n}\n\nfunction inRange(value: number, start: string, end: string): boolean {\n return value >= ipv4ToNumber(start) && value <= ipv4ToNumber(end)\n}\n\nfunction ipv4ToNumber(ipAddress: string): number {\n const octets = ipAddress.split(\".\").map((part) => Number.parseInt(part, 10))\n return octets[0]! * 256 ** 3 + octets[1]! * 256 ** 2 + octets[2]! * 256 + octets[3]!\n}\n\nfunction isPrivateIPv6(ipAddress: string): boolean {\n const groups = expandIpv6(ipAddress)\n if (groups !== null && hasMappedV4Prefix(groups)) {\n const mapped = [groups[6]! >> 8, groups[6]! & 0xff, groups[7]! >> 8, groups[7]! & 0xff].join(\".\")\n return isPrivateIPv4(mapped)\n }\n const normalized = ipAddress.split(\"%\")[0]!\n const mapped = extractMappedIpv4(normalized)\n if (mapped && isPrivateIPv4(mapped)) return true\n const value = ipv6ToBigInt(normalized)\n if (value === null) return true\n return (\n inIpv6Range(value, \"::\", 128) ||\n inIpv6Range(value, \"::1\", 128) ||\n inIpv6Range(value, \"fc00::\", 7) ||\n inIpv6Range(value, \"fe80::\", 10) ||\n inIpv6Range(value, \"ff00::\", 8) ||\n inIpv6Range(value, \"2001:db8::\", 32)\n )\n}\n\nfunction hasMappedV4Prefix(groups: number[]): boolean {\n return groups.slice(0, 5).every((group) => group === 0) && groups[5] === 0xffff\n}\n\nfunction extractMappedIpv4(ipAddress: string): string | null {\n const lower = ipAddress.toLowerCase()\n const marker = \"::ffff:\"\n if (!lower.startsWith(marker)) return null\n const candidate = lower.slice(marker.length)\n return isIP(candidate) === 4 ? candidate : null\n}\n\nfunction inIpv6Range(value: bigint, start: string, prefixLength: number): boolean {\n const startValue = ipv6ToBigInt(start)\n if (startValue === null) return false\n const mask = prefixLength === 0 ? 0n : ((1n << BigInt(prefixLength)) - 1n) << BigInt(128 - prefixLength)\n return (value & mask) === (startValue & mask)\n}\n\nfunction ipv6ToBigInt(ipAddress: string): bigint | null {\n const expanded = expandIpv6(ipAddress)\n if (!expanded) return null\n return expanded.reduce((acc, group) => (acc << 16n) + BigInt(group), 0n)\n}\n\nfunction expandIpv6(ipAddress: string): number[] | null {\n const value = ipAddress.toLowerCase()\n if (value.includes(\"::\")) {\n const [left, right] = value.split(\"::\")\n const leftGroups = left ? left.split(\":\").filter(Boolean) : []\n const rightGroups = right ? right.split(\":\").filter(Boolean) : []\n if (leftGroups.length + rightGroups.length > 8) return null\n const middle = new Array(8 - leftGroups.length - rightGroups.length).fill(\"0\")\n return parseIpv6Groups([...leftGroups, ...middle, ...rightGroups])\n }\n return parseIpv6Groups(value.split(\":\"))\n}\n\nfunction parseIpv6Groups(groups: string[]): number[] | null {\n if (groups.length !== 8) return null\n const parsed = groups.map((group) => Number.parseInt(group || \"0\", 16))\n if (parsed.some((value) => !Number.isFinite(value) || value < 0 || value > 0xffff)) return null\n return parsed\n}\n","/**\n * `pptwise serve <target>` (serve wave, task S1, spec-plan.md\n * `.issues/2026-07-25-serve/spec-plan.md`): a live-reloading HTTP preview of\n * the exact same `preview.html` bundle `pptwise preview --html` writes to\n * disk (`buildDeckPreview`, `./commands.ts`) — this module never builds its\n * own HTML, only serves and refreshes what that shared pipeline produces\n * (design ruling 5: \"buildPreviewHtml 复用现状 ... 禁止 fork 一份 preview 构建\n * 逻辑\").\n *\n * Two layers:\n * - {@link createServeServer}: the testable factory — a plain `node:http`\n * server (design ruling 1: zero new dependencies, no express/ws/chokidar)\n * bound hard to `127.0.0.1` (design ruling 6: no remote bind, no auth — a\n * local dev tool), an `fs.watch`-based rebuild loop, and an SSE channel for\n * push (design ruling 2: v1 is a whole-page `location.reload()` over SSE,\n * no partial DOM patching). No process-level side effects (no `SIGINT`\n * handler, no browser launch) — a caller (tests, or {@link runServe} below)\n * owns that, which is what keeps this factory usable outside the CLI.\n * - {@link runServe}: the CLI-facing wrapper — prints the URL, opens a\n * browser unless `--no-open`, wires `SIGINT` to a clean shutdown.\n *\n * Routes: `GET /` returns the in-memory cached HTML — after {@link injectServeClient}\n * has spliced this module's own `<script>` into it (task S2; see that\n * function's own doc comment) — rebuilt on change, never per-request (a\n * request never blocks on a render). `GET /events` is the SSE stream: a\n * `retry:` hint on connect, a `: heartbeat` comment frame every 30s (keeps\n * the connection alive through an idle-timeout proxy — pure SSE comment\n * syntax, invisible to `EventSource`), an `event: reload` frame after every\n * successful rebuild, an `event: error` frame with a JSON `{message}` body\n * after a failed one. Everything else 404s.\n *\n * This server is read-only. It carried a `POST /revision-request` endpoint\n * until 2026-08-16, which took the preview annotation panel's export and\n * wrote it into the deck directory; the panel went first, leaving the\n * endpoint with no producer, and the pair was removed together. A reviewer\n * who wants something changed says so in the conversation — a screenshot\n * reaches the agent faster than a panel whose output has to be exported and\n * routed back — and the agent edits `pages/*.json` through the same gate as\n * every other change.\n *\n * Watch roots (design ruling 3) come straight from {@link buildDeckPreview}'s\n * own `resolvedTarget`/`isDir` — the exact path `loadDeckTarget`\n * (`./commands.ts`) already resolved `target` to — rather than this module\n * re-deriving the bare-name/`decksDir` resolution a second time: a deck\n * project directory watches `deck.spec.json` + `pages/` + `assets/`\n * (non-recursive `fs.watch` on each — three flat, non-nested directories\n * cover the whole deck-project layout anyway, `docs/deck-projects.md`, so\n * `{recursive: true}` buys nothing here even now that the repo's floor\n * (Node 22.19, `package.json#engines`) has it on every platform); a bare IR\n * target watches that one file. Multiple `fs.watch` events firing for a\n * single logical save (editors that write via a temp file + rename, or\n * saving several page files in one \"save all\") are coalesced by a 200ms\n * debounce into one rebuild.\n *\n * Resilience (design ruling 3's other half): a rebuild that throws — a\n * mid-edit malformed JSON save is the common case — never crashes the server\n * or throws out of the watch handler. It's caught, turned into an `error` SSE\n * event, and the previous good `html` stays cached and keeps serving `GET /`\n * until a later rebuild succeeds. Only the *first* build (at\n * `createServeServer` call time, before the server starts listening) is\n * allowed to reject the whole call — same \"throw `PptwiseError` → CLI exit 1\"\n * contract every other `run*` command already has (`./commands.ts`), since\n * there is no previous-good HTML yet to fall back to.\n */\nimport { type FSWatcher, watch } from \"node:fs\"\nimport { createServer, type Server, type ServerResponse } from \"node:http\"\nimport { platform as osPlatform } from \"node:os\"\nimport { join } from \"node:path\"\nimport { PptwiseError } from \"../errors\"\nimport { spawnHidden } from \"./child\"\nimport { buildDeckPreview } from \"./commands\"\nimport { findConfig } from \"./config\"\nimport { ASSETS_DIRNAME, PAGES_DIRNAME, SPEC_FILENAME } from \"./deck-dir\"\nimport { resolveWorkspaceLocation } from \"./workspace\"\n\n/** `pptwise serve`'s own default (spec-plan.md §2's worked example,\n * `pptwise serve <target> [--port 4400] [--no-open]`) — never\n * auto-incremented on conflict (design ruling 7: \"不自动递增——agent 要可\n * 预测的 URL\"), so a busy port is a hard error naming `--port` as the way\n * out, never a silent fallback to some other port the caller didn't ask\n * for. */\nexport const DEFAULT_PORT = 4400\n\nconst DEBOUNCE_MS = 200\nconst HEARTBEAT_MS = 30_000\n\n\n\nexport interface ServeOptions {\n /** Same target shape every deck-accepting command accepts: an IR JSON\n * file, a deck project directory, or a bare name under\n * `~/.pptwise/decks` (`buildDeckPreview`/`loadDeckTarget`, `./commands.ts`). */\n target: string\n /** Default {@link DEFAULT_PORT}. `0` binds an OS-assigned ephemeral port\n * (tests only — `pptwise serve` itself always resolves a fixed port, see\n * {@link DEFAULT_PORT}'s own doc comment on why this command never\n * auto-increments). */\n port?: number\n cwd?: string\n /** `--theme-file <path>` (brand-extract wave) — threaded into every\n * `buildDeckPreview` call (initial and each rebuild). The registration is\n * idempotent per id (`registerBrandThemeFile`, `../themes/brand-theme-file.ts`),\n * so re-running it every rebuild is safe — but note the flip side: the\n * first successful registration wins for the process's lifetime, so\n * editing the theme file itself mid-serve does not live-reload the brand\n * (restart `pptwise serve` for that). */\n themeFilePath?: string\n}\n\nexport interface ServeHandle {\n server: Server\n /** Re-run the build pipeline immediately and push the result over SSE\n * (`reload` on success, `error` on failure) — never throws, same\n * catch-and-broadcast contract the `fs.watch` path uses internally.\n * Exposed so a caller (or a test) can force a synchronous rebuild without\n * waiting on the 200ms debounce. */\n rebuild: () => Promise<void>\n /** Stops watching, closes every open SSE connection, and closes the HTTP\n * server. Safe to call more than once. */\n close: () => Promise<void>\n /** `http://127.0.0.1:<port>` — the actual bound port, resolved even when\n * `options.port` was `0`. */\n url: string\n port: number\n}\n\n/** The concrete paths `createServeServer` should `fs.watch` for `target`,\n * given `buildDeckPreview`'s own `resolvedTarget`/`isDir` for it — see this\n * module's own doc comment for why these three (deck-dir mode) or this one\n * (bare-IR mode) are the whole watch surface. */\nfunction watchRoots(resolvedTarget: string, isDir: boolean, extra: string[] = []): string[] {\n const roots = isDir\n ? [join(resolvedTarget, SPEC_FILENAME), join(resolvedTarget, PAGES_DIRNAME), join(resolvedTarget, ASSETS_DIRNAME)]\n : [resolvedTarget]\n return [...roots, ...extra]\n}\n\n/** Marker on the injected `<script>` element (task S2: \"serve 模式检测(注入的\n * 脚本自带标记)\", spec-plan.md §4) — lets a test (`serve.test.ts`) or later\n * tooling confirm a served page carries this module's client wiring\n * without parsing or executing it, and gives {@link injectServeClient} a\n * fixed string to check for (a defensive double-injection guard —\n * `createServeServer` only ever calls it on a fresh `buildDeckPreview`\n * result, which never already contains it, but the check costs nothing). */\nexport const SERVE_CLIENT_SCRIPT_ID = \"pptwise-serve-client\"\n\n/**\n * The serve-mode client (task S2), spliced into every served page by\n * {@link injectServeClient} — never seen by the non-serve `pptwise preview\n * --html` download path. Two jobs:\n *\n * 1. Live reload: opens `EventSource('/events')`, reloads the whole page on\n * `reload` (design ruling 2), shows a fixed top banner on `error`. The\n * server's own custom `event: error` frame and `EventSource`'s *built-in*\n * connection-failure event share the same DOM event name on this one\n * object — a real connection hiccup is a plain `Event` with no `data`\n * (EventSource auto-reconnects itself off the server's `retry:` hint,\n * nothing for this page to do); the server's frame is a `MessageEvent`\n * whose `data` is a JSON `{message}` string. Checking for `.data` first\n * tells the two apart. No explicit \"clear the banner\" path either: every\n * successful rebuild's `reload` does a full `location.reload()`, wiping\n * the banner along with the rest of the DOM — a separate hide-on-success\n * branch would be dead code a reload always beats to it.\n *\n * 2. Revision-request submit: rewires the existing export/download button\n * (`#pf-export-btn`, `buildPreviewHtml`/`./preview-html.ts`) to POST\n * instead of only downloading. The exact serialized payload comes from\n * `window.__pptwiseBuildExportBlob` — a plain function reference that\n * file's own `<script>` closure assigns onto `window` specifically as\n * this module's seam (see that file's own doc comment for the full\n * rationale; design ruling 5 forbids a second copy of any part of the\n * preview-build logic, and calling back into the original closure's own\n * function is how this reuses it instead of re-deriving the\n * `{version, deck, requests}` shape here). Called through\n * `Promise.resolve(...).then(...)` rather than invoked and trusted\n * directly — cheap insurance that both a synchronous throw *and* a\n * rejected/async return from `buildExportBlob()` land in the same\n * `.catch` as a network failure, all surfaced as the same inline\n * status-line feedback, never a silent no-op. (An earlier version of\n * this file took a different approach here — briefly monkey-patching\n * `URL.createObjectURL`/`HTMLAnchorElement.prototype.click` around a\n * programmatic click on the original button, to capture the `Blob` it\n * built without a seam existing yet. Reviewed out: it only worked\n * because that handler happened to be perfectly synchronous start to\n * finish, an assumption a later change to it — one `await` — could\n * silently break with zero user-visible error, on the one feature this\n * whole command exists to make possible.) The rewired button (a\n * `cloneNode` swapped in for the original — `cloneNode` never copies\n * `addEventListener` listeners, so the original element, though detached\n * from the document, keeps `buildPreviewHtml`'s own listener intact and\n * still runnable via `.click()`) shows success/failure inline; a small\n * secondary link next to it just calls `originalBtn.click()` — the\n * untouched, real download path — so a manual copy is always still one\n * click away regardless of whether the POST succeeds.\n *\n * Exported (S3, S2 re-review's named test carry) purely so\n * `serve-client.test.ts` can execute this exact string under jsdom instead of\n * only grepping it as markup — this file has no other export consumer, isn't\n * re-exported from anywhere `pptwise --help` or the SDK's public surface ever\n * reads, and stays exactly as inert to import as before: `src/cli/serve.ts`\n * is already Node-only (AGENTS.md's layout rule), never reachable from\n * `src/index.ts`'s browser-safe closure regardless of what it exports.\n */\nexport const SERVE_CLIENT_JS = `\n(function () {\n // Live reload is the whole of this client (the revision-request submit\n // that used to sit beside it was removed on 2026-08-16 — see this\n // module's own header). It keeps its own function and its own try/catch\n // at the call site below: an EventSource construction that throws in some\n // unusual embedding must degrade to a page that simply does not\n // auto-refresh, not abort the IIFE.\n\n function setUpLiveReload() {\n var es = new EventSource('/events')\n es.addEventListener('reload', function () { location.reload() })\n\n var banner = document.createElement('div')\n banner.id = 'pptwise-serve-error-banner'\n banner.setAttribute('role', 'alert')\n banner.style.cssText =\n 'display:none;position:fixed;top:0;left:0;right:0;z-index:2147483647;' +\n 'background:#dc2626;color:#fff;font:13px/1.4 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif;' +\n 'padding:8px 16px;text-align:center'\n document.body.appendChild(banner)\n\n function showBanner(message) {\n banner.textContent = 'pptwise serve: ' + message\n banner.style.display = 'block'\n }\n\n es.addEventListener('error', function (e) {\n if (!e || typeof e.data !== 'string') return // a real connection hiccup, not the server's own rebuild-failed frame\n var message = 'rebuild failed'\n try {\n var parsed = JSON.parse(e.data)\n if (parsed && typeof parsed.message === 'string') message = parsed.message\n } catch (err) {}\n showBanner(message)\n })\n }\n\n try {\n setUpLiveReload()\n } catch (e) {\n console.error('pptwise serve: failed to set up live reload', e)\n }\n})()\n`.trim()\n\n/** Wraps {@link SERVE_CLIENT_JS} in its own `<script>` tag, marked with\n * {@link SERVE_CLIENT_SCRIPT_ID}. */\nfunction buildServeClientScriptTag(): string {\n return `<script id=\"${SERVE_CLIENT_SCRIPT_ID}\">${SERVE_CLIENT_JS}</script>`\n}\n\n/**\n * Post-processing HTML injection (design ruling 5: `buildPreviewHtml`\n * (`./preview-html.ts`) has no seam of its own for extra script content,\n * and forking a second copy of its build logic is forbidden — so this\n * rewrites the *string* `buildDeckPreview` already returned instead,\n * leaving that module — and every byte it produces for the non-serve\n * `pptwise preview --html` download path — completely untouched).\n * `createServeServer` is the only caller, on every fresh\n * `buildDeckPreview` result (initial build and every rebuild alike).\n * Inserted right before the document's one `</body>`: by the time it runs,\n * every element the injected script itself touches (`#pf-export-btn`, ...)\n * already exists, the same reasoning `buildPreviewHtml` already places its\n * own `<script>` there for.\n */\nexport function injectServeClient(html: string): string {\n if (html.includes(SERVE_CLIENT_SCRIPT_ID)) return html\n return html.replace(\"</body>\", `${buildServeClientScriptTag()}\\n</body>`)\n}\n\n\n/**\n * The testable factory (serve wave, task S1). Builds once up front — a\n * failure here rejects the whole call, see this module's own doc comment —\n * then starts listening and watching. Every fs/network resource this\n * function opens (the watchers, the heartbeat timer, the HTTP server) is\n * torn down by the returned {@link ServeHandle.close} and by nothing else:\n * this function has no other side effect a caller would need to separately\n * clean up, which is what makes it safe to call directly from a test without\n * going through the CLI at all.\n */\nexport async function createServeServer(options: ServeOptions): Promise<ServeHandle> {\n const cwd = options.cwd ?? process.cwd()\n const requestedPort = options.port ?? DEFAULT_PORT\n if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {\n throw new PptwiseError(`invalid port ${requestedPort} — expected an integer between 0 and 65535`)\n }\n\n // First build happens before the server ever starts listening — deliberate:\n // there is no previous-good HTML to fall back to yet, so an invalid target\n // must fail this call outright (CLI exit 1, same as every other command)\n // rather than start a server with nothing to show at `GET /`.\n const initial = await buildDeckPreview(options.target, { cwd, themeFilePath: options.themeFilePath })\n let cachedHtml = injectServeClient(initial.html)\n const sseClients = new Set<ServerResponse>()\n\n function writeToAll(chunk: string): void {\n for (const res of sseClients) {\n try {\n res.write(chunk)\n } catch {\n // A client that disconnected mid-broadcast — its own `close`/`error`\n // listener (registered where it's added to `sseClients` below)\n // removes it; one dead client must never stop the rest from hearing\n // about this rebuild.\n }\n }\n }\n\n function broadcast(event: string, data: unknown): void {\n writeToAll(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`)\n }\n\n async function rebuild(): Promise<void> {\n try {\n const result = await buildDeckPreview(options.target, { cwd, themeFilePath: options.themeFilePath })\n cachedHtml = injectServeClient(result.html)\n broadcast(\"reload\", {})\n } catch (e) {\n broadcast(\"error\", { message: e instanceof Error ? e.message : String(e) })\n }\n }\n\n const server = createServer((req, res) => {\n const pathname = (req.url ?? \"/\").split(\"?\")[0]\n if (req.method === \"GET\" && pathname === \"/\") {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(cachedHtml)\n return\n }\n if (req.method === \"GET\" && pathname === \"/events\") {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n Connection: \"keep-alive\",\n })\n res.write(\"retry: 2000\\n\\n\")\n sseClients.add(res)\n res.on(\"close\", () => sseClients.delete(res))\n res.on(\"error\", () => sseClients.delete(res))\n return\n }\n res.writeHead(404, { \"Content-Type\": \"text/plain; charset=utf-8\" })\n res.end(\"not found\")\n })\n\n const heartbeat = setInterval(() => writeToAll(\": heartbeat\\n\\n\"), HEARTBEAT_MS)\n\n let debounceTimer: NodeJS.Timeout | undefined\n function scheduleRebuild(): void {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = undefined\n void rebuild()\n }, DEBOUNCE_MS)\n }\n\n const projectHit = await findConfig(cwd)\n const workspaceAssets = join(\n resolveWorkspaceLocation({\n cwd,\n projectConfigPath: projectHit?.path,\n outDir: projectHit?.config.outDir,\n target: initial.resolvedTarget,\n isDir: initial.isDir,\n }).dir,\n ASSETS_DIRNAME,\n )\n\n const watchers: FSWatcher[] = []\n for (const path of watchRoots(initial.resolvedTarget, initial.isDir, [workspaceAssets])) {\n try {\n watchers.push(watch(path, () => scheduleRebuild()))\n } catch (e) {\n // `pages/`/`assets/` may not exist yet for a brand-new deck project\n // (nothing filled in, no local images) — nothing to watch there until\n // it's created, not a reason to fail serve startup. Anything other\n // than \"doesn't exist yet\" (permissions, ...) is a real problem.\n // Consequence (S1 review carry): this watch-setup pass only ever runs\n // once, at `createServeServer` call time — a directory that gets\n // created *later* in the same session (e.g. the first local image\n // asset is added, materializing `assets/` mid-edit) is never picked\n // up, since nothing here re-scans for newly-appeared watch roots\n // afterward. Changes under such a directory go unnoticed until the\n // user restarts `pptwise serve`.\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e\n }\n }\n\n function teardownWatchersAndTimers(): void {\n clearInterval(heartbeat)\n if (debounceTimer) clearTimeout(debounceTimer)\n for (const w of watchers) w.close()\n }\n\n try {\n await new Promise<void>((resolveListen, rejectListen) => {\n const onError = (err: NodeJS.ErrnoException) => {\n server.removeListener(\"listening\", onListening)\n rejectListen(err)\n }\n const onListening = () => {\n server.removeListener(\"error\", onError)\n resolveListen()\n }\n server.once(\"error\", onError)\n server.once(\"listening\", onListening)\n server.listen(requestedPort, \"127.0.0.1\")\n })\n } catch (e) {\n teardownWatchersAndTimers()\n if ((e as NodeJS.ErrnoException).code === \"EADDRINUSE\") {\n throw new PptwiseError(`port ${requestedPort} is already in use — pick a different one with --port`)\n }\n throw e\n }\n\n const address = server.address()\n const actualPort = typeof address === \"object\" && address !== null ? address.port : requestedPort\n\n let closed = false\n async function close(): Promise<void> {\n if (closed) return\n closed = true\n teardownWatchersAndTimers()\n for (const res of sseClients) res.end()\n sseClients.clear()\n // `res.end()` above finishes each SSE response, but the socket behind a\n // `Connection: keep-alive` response (`GET /events`'s own header) is not\n // guaranteed to be released the instant the response ends —\n // `server.close()`'s callback only fires once every socket the server\n // ever accepted has actually closed, so a lingering keep-alive socket\n // can otherwise leave it hanging indefinitely (S1 review carry).\n // `closeIdleConnections`/`closeAllConnections` (\"http: added connection\n // closing methods\", nodejs/node#42812) exist on every Node this repo\n // supports (floor 22.19, package.json#engines), so the `typeof` guard is\n // only there for a non-node http server double passed in by a test.\n // Calling both explicitly rather than trusting `close()` is deliberate:\n // whether `close()` alone releases idle keep-alive sockets has varied by\n // release (nodejs/node#52336), and calling both is correct either way, a\n // harmless no-op wherever `close()` already handled it.\n if (typeof server.closeIdleConnections === \"function\") server.closeIdleConnections()\n if (typeof server.closeAllConnections === \"function\") server.closeAllConnections()\n await new Promise<void>((resolveClose, rejectClose) => {\n server.close((err) => (err ? rejectClose(err) : resolveClose()))\n })\n }\n\n return { server, rebuild, close, url: `http://127.0.0.1:${actualPort}`, port: actualPort }\n}\n\n/**\n * Best-effort browser launch (spec-plan.md S1: \"--no-open: 默认行为打开浏览器\n * ... 若无则 spawn open (darwin) / xdg-open (linux)\"). Nothing\n * in this repo already opens URLs (`./update.ts` runs `npm`, not\n * a GUI app) — this is the one place that does. Never throws and never\n * rejects a caller's own flow: a headless box, a sandboxed CI runner, or a\n * missing `xdg-open` binary all fail silently — the URL `runServe` already\n * printed to the terminal is the fallback, so a failed launch here degrades\n * to \"the user copies the URL themselves\", not a broken `pptwise serve`.\n * Windows is out of scope (this repo's own dev-machine assumption is\n * macOS/Linux, spec-plan.md design ruling 1) — falls through to the\n * `xdg-open` branch, which simply fails to spawn (caught below) rather than\n * crashing.\n */\nexport function openBrowser(url: string): void {\n const command = osPlatform() === \"darwin\" ? \"open\" : \"xdg-open\"\n try {\n const child = spawnHidden(command, [url], { stdio: \"ignore\", detached: true })\n child.on(\"error\", () => {})\n child.unref()\n } catch {\n // spawn() itself can throw synchronously (e.g. EMFILE) — equally non-fatal.\n }\n}\n\nexport interface RunServeOptions {\n port?: number\n /** `false` suppresses the browser launch (`--no-open`). Default `true`. */\n open?: boolean\n cwd?: string\n /** `--theme-file <path>` — see {@link ServeOptions.themeFilePath}. */\n themeFilePath?: string\n}\n\n/**\n * `pptwise serve <target>` (`../cli.ts`'s CLI wiring). Resolving does not\n * mean the command is finished — unlike every other `run*` (`./commands.ts`),\n * which does its one unit of work and returns, this one starts a long-lived\n * server and returns almost immediately after; the open listening socket\n * `createServeServer` set up is what keeps the CLI process alive from here\n * (the standard long-running-dev-server shape — same reason `vite dev`'s own\n * process doesn't exit right after printing its URL), not this function\n * blocking on anything.\n */\nexport async function runServe(target: string, opts: RunServeOptions = {}): Promise<void> {\n const handle = await createServeServer({ target, port: opts.port, cwd: opts.cwd, themeFilePath: opts.themeFilePath })\n console.log(`pptwise serve: ${handle.url} (Ctrl+C to stop)`)\n if (opts.open !== false) openBrowser(handle.url)\n process.on(\"SIGINT\", () => {\n void handle.close().then(\n () => process.exit(0),\n () => process.exit(1),\n )\n })\n}\n"],"mappings":"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","names":["mkdir","readdir","readFile","writeFile","basename","dirname","join","relative","resolve","readFile","join","resolve","z","dirname","config","nonempty","apiKey","clientId","clientSecret","ready","path","z","path","readFile","resolve","join","mkdir","readFile","writeFile","basename","extname","isAbsolute","join","resolve","readFile","resolve","readFile","resolve","resolve","isAbsolute","path","config","join","readFile","extname","basename","images","spec","mkdir","writeFile","attr","existsSync","mkdir","readFile","readdir","stat","basename","dirname","extname","join","resolve","join","program","tail","plan","resolve","existsSync","join","dirname","basename","extname","resolve","mkdir","readFile","path","stat","readdir","readdir","images","extname","basename","join","path","join","resolve","dirname","images","stock","mkdir","writeFile","spec","basename","readFile","relative","readdir","raw","pages","migrated","specNote","resolve","path","lstatSync","readFile","readdir","basename","join","copyFile","readdir","stat","join","path","readFile","readdir","join","stat","images","copyFile","join","pathExists","relative","dirName","readFile","readdir","lstatSync","images","basename","mkdir","readFile","readdir","rm","unlink","writeFile","dirname","join","resolve","resolve","value","mapped","userAgent","asRecord","asString","asNumber","resolve","dirname","join","path","readFile","mkdir","writeFile","readdir","unlink","rm","join","join","path"]}
|