@christophervr/pptx-viewer 1.1.45 → 1.1.46

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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
5
+ by [git-cliff](https://git-cliff.org); do not edit it by hand.
6
+
7
+ ## [1.1.45](https://github.com/ChristopherVR/pptx-viewer/releases/tag/@christophervr/pptx-viewer@1.1.45) - 2026-07-02
8
+
9
+ ### Features
10
+
11
+ - **cli:** Add interactive @christophervr/pptx-viewer installer (by @ChristopherVR) ([4df680d](https://github.com/ChristopherVR/pptx-viewer/commit/4df680d9791d18e38c0f413420e8e1e5f9f2907e))
package/README.md CHANGED
@@ -19,18 +19,21 @@ This is what you get one `npx` away: a working `.pptx` viewer/editor, wired up i
19
19
  npx @christophervr/pptx-viewer@latest
20
20
  ```
21
21
 
22
- It first asks what you're building (multiple choice, comma-separated):
22
+ It first asks what you're building with an arrow-key checklist (`↑`/`↓` to move, `space` to toggle, `a` to select all, `enter` to confirm):
23
23
 
24
24
  ```
25
25
  What are you building with pptx-viewer? (you can pick more than one)
26
+ (↑/↓ move, space toggle, a select all, enter confirm)
26
27
 
27
- 1) React - pptx-react-viewer, a viewer/editor component for a React 19 app
28
- 2) Vue - pptx-vue-viewer, a viewer/editor component for a Vue 3.5+ app
29
- 3) Angular - pptx-angular-viewer, a viewer/editor component for an Angular 22+ app
30
- 4) Core engine only - pptx-viewer-core, the framework-agnostic SDK, no UI
31
- 5) MCP server - pptx-viewer-mcp, PowerPoint editing tools for AI agents
28
+ React - pptx-react-viewer, a viewer/editor component for a React 19 app
29
+ Vue - pptx-vue-viewer, a viewer/editor component for a Vue 3.5+ app
30
+ Angular - pptx-angular-viewer, a viewer/editor component for an Angular 22+ app
31
+ Core engine only - pptx-viewer-core, the framework-agnostic SDK, no UI
32
+ MCP server - pptx-viewer-mcp, PowerPoint editing tools for AI agents
32
33
  ```
33
34
 
35
+ The whole flow is colour-highlighted (current row, confirmations, warnings, errors) and falls back to a plain numbered prompt in shells without raw keyboard input (piped stdin, some CI runners) or when `NO_COLOR`/a non-TTY output disables colour.
36
+
34
37
  Picking more than one is fine, for example React plus the MCP server to get both a viewer and AI-agent tooling in the same repo. `pptx-viewer-mcp` never gets installed as a dependency: since it's meant to be launched by an MCP client via `npx`, this just prints the client config to paste in.
35
38
 
36
39
  ### Compatibility check
@@ -42,7 +45,7 @@ If you picked React, Vue, or Angular and a `package.json` already exists in the
42
45
  When exactly one UI framework is selected, you're asked how to set it up:
43
46
 
44
47
  - **Install here** adds the package(s) to the project in the current directory (a `package.json` must already exist; run `npm init -y` first if not).
45
- - **Scaffold a new project** bootstraps a brand-new starter app in its own folder, using the framework's own official scaffolding tool ([`create-vite`](https://www.npmjs.com/package/create-vite) for React/Vue, [`@angular/cli`](https://www.npmjs.com/package/@angular/cli) for Angular), then wires in a minimal working `PowerPointViewer` example and installs the viewer package on top.
48
+ - **Scaffold a new project** bootstraps a brand-new starter app in its own folder, using the framework's own official scaffolding tool ([`create-vite`](https://www.npmjs.com/package/create-vite) for React/Vue, [`@angular/cli`](https://www.npmjs.com/package/@angular/cli) for Angular), then wires in a working `PowerPointViewer` example (same pattern as the [live demos](https://christophervr.github.io/pptx-viewer/demo/): open an existing `.pptx`, or click "New Presentation" to build a blank deck with `PptxHandler.createBlank` and start editing right away) and installs the viewer package plus `pptx-viewer-core` on top.
46
49
 
47
50
  Scaffolding is only offered for a single framework at a time; if you select more than one UI framework together, the CLI installs into the current project instead.
48
51
 
package/dist/index.mjs CHANGED
@@ -52,6 +52,29 @@ function parseArgs(args) {
52
52
  return parsed;
53
53
  }
54
54
 
55
+ // src/colors.ts
56
+ var isColorEnabled = process.env.NO_COLOR === void 0 && (process.env.FORCE_COLOR !== void 0 || Boolean(process.stdout.isTTY));
57
+ function wrap(open, close) {
58
+ return (text) => isColorEnabled ? `\x1B[${open}m${text}\x1B[${close}m` : text;
59
+ }
60
+ var bold = wrap(1, 22);
61
+ var dim = wrap(2, 22);
62
+ var red = wrap(31, 39);
63
+ var green = wrap(32, 39);
64
+ var yellow = wrap(33, 39);
65
+ var blue = wrap(34, 39);
66
+ var magenta = wrap(35, 39);
67
+ var cyan = wrap(36, 39);
68
+ var gray = wrap(90, 39);
69
+ var symbols = {
70
+ pointer: "\u276F",
71
+ check: "\u2714",
72
+ cross: "\u2718",
73
+ radioOn: "\u25C9",
74
+ radioOff: "\u25EF",
75
+ bullet: "\xB7"
76
+ };
77
+
55
78
  // src/project-deps.ts
56
79
  import { existsSync, readFileSync } from "fs";
57
80
  import { join } from "path";
@@ -150,6 +173,102 @@ function installCommand(pm, packages) {
150
173
 
151
174
  // src/prompt.ts
152
175
  import { createInterface } from "readline/promises";
176
+
177
+ // src/interactive-menu.ts
178
+ import { emitKeypressEvents } from "readline";
179
+ var HIDE_CURSOR = "\x1B[?25l";
180
+ var SHOW_CURSOR = "\x1B[?25h";
181
+ var CLEAR_LINE = "\x1B[2K";
182
+ function moveUp(lines) {
183
+ return lines > 0 ? `\x1B[${lines}A` : "";
184
+ }
185
+ function renderChoice(choice, isCursor, checked) {
186
+ const pointer = isCursor ? cyan(symbols.pointer) : " ";
187
+ const box = checked === null ? "" : `${checked ? green(symbols.radioOn) : gray(symbols.radioOff)} `;
188
+ const label = isCursor ? bold(choice.label) : choice.label;
189
+ return `${pointer} ${box}${label} ${dim(`- ${choice.description}`)}`;
190
+ }
191
+ function runMenu(choices, multi) {
192
+ return new Promise((resolve) => {
193
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
194
+ resolve(null);
195
+ return;
196
+ }
197
+ let cursor = 0;
198
+ const checked = /* @__PURE__ */ new Set();
199
+ let settled = false;
200
+ const hint = multi ? dim("(\u2191/\u2193 move, space toggle, a select all, enter confirm)") : dim("(\u2191/\u2193 move, enter confirm)");
201
+ console.log(hint);
202
+ process.stdout.write(HIDE_CURSOR);
203
+ function draw(first) {
204
+ if (!first) {
205
+ process.stdout.write(moveUp(choices.length));
206
+ }
207
+ for (const [i, choice] of choices.entries()) {
208
+ const checkedState = multi ? checked.has(i) : null;
209
+ process.stdout.write(`${CLEAR_LINE}${renderChoice(choice, i === cursor, checkedState)}
210
+ `);
211
+ }
212
+ }
213
+ function cleanup() {
214
+ process.stdin.setRawMode?.(false);
215
+ process.stdin.removeListener("keypress", onKeypress);
216
+ process.stdin.pause();
217
+ process.stdout.write(SHOW_CURSOR);
218
+ }
219
+ function finish(result) {
220
+ if (settled) {
221
+ return;
222
+ }
223
+ settled = true;
224
+ cleanup();
225
+ resolve(result);
226
+ }
227
+ function onKeypress(_str, key) {
228
+ if (key?.ctrl && key.name === "c") {
229
+ finish(null);
230
+ process.exit(130);
231
+ return;
232
+ }
233
+ if (key?.name === "up") {
234
+ cursor = (cursor - 1 + choices.length) % choices.length;
235
+ draw(false);
236
+ } else if (key?.name === "down") {
237
+ cursor = (cursor + 1) % choices.length;
238
+ draw(false);
239
+ } else if (multi && key?.name === "space") {
240
+ if (checked.has(cursor)) {
241
+ checked.delete(cursor);
242
+ } else {
243
+ checked.add(cursor);
244
+ }
245
+ draw(false);
246
+ } else if (multi && key?.name === "a") {
247
+ if (checked.size === choices.length) {
248
+ checked.clear();
249
+ } else {
250
+ choices.forEach((_, i) => checked.add(i));
251
+ }
252
+ draw(false);
253
+ } else if (key?.name === "return") {
254
+ if (multi) {
255
+ if (checked.size > 0) {
256
+ finish([...checked].sort((a, b) => a - b));
257
+ }
258
+ } else {
259
+ finish([cursor]);
260
+ }
261
+ }
262
+ }
263
+ emitKeypressEvents(process.stdin);
264
+ process.stdin.setRawMode(true);
265
+ process.stdin.on("keypress", onKeypress);
266
+ process.stdin.resume();
267
+ draw(true);
268
+ });
269
+ }
270
+
271
+ // src/prompt.ts
153
272
  function parseSelection(answer, count) {
154
273
  const trimmed = answer.trim().toLowerCase();
155
274
  if (trimmed === "all" || trimmed === "a") {
@@ -171,15 +290,12 @@ function parseSelection(answer, count) {
171
290
  }
172
291
  function printOptions(options) {
173
292
  options.forEach((opt, i) => {
174
- console.log(` ${i + 1}) ${opt.label} - ${opt.description}`);
293
+ console.log(` ${cyan(`${i + 1})`)} ${bold(opt.label)} ${dim(`- ${opt.description}`)}`);
175
294
  });
176
295
  }
177
- async function selectOption(question, options) {
296
+ async function selectByNumber(options) {
178
297
  const rl = createInterface({ input: process.stdin, output: process.stdout });
179
298
  try {
180
- console.log(`
181
- ${question}
182
- `);
183
299
  printOptions(options);
184
300
  for (; ; ) {
185
301
  const answer = (await rl.question(`
@@ -194,12 +310,9 @@ Enter a number (1-${options.length}): `)).trim();
194
310
  rl.close();
195
311
  }
196
312
  }
197
- async function multiSelect(question, options) {
313
+ async function selectManyByNumber(options) {
198
314
  const rl = createInterface({ input: process.stdin, output: process.stdout });
199
315
  try {
200
- console.log(`
201
- ${question}
202
- `);
203
316
  printOptions(options);
204
317
  for (; ; ) {
205
318
  const answer = await rl.question(
@@ -216,10 +329,32 @@ Enter one or more numbers, comma-separated (e.g. "1,3"), or "all": `
216
329
  rl.close();
217
330
  }
218
331
  }
332
+ async function selectOption(question, options) {
333
+ console.log(`
334
+ ${bold(question)}`);
335
+ const picked = await runMenu(options, false);
336
+ if (!picked) {
337
+ return selectByNumber(options);
338
+ }
339
+ const choice = options[picked[0]];
340
+ console.log(`${green("\u2714")} ${choice.label}`);
341
+ return choice;
342
+ }
343
+ async function multiSelect(question, options) {
344
+ console.log(`
345
+ ${bold(question)}`);
346
+ const picked = await runMenu(options, true);
347
+ if (!picked) {
348
+ return selectManyByNumber(options);
349
+ }
350
+ const choices = picked.map((i) => options[i]);
351
+ console.log(`${green("\u2714")} ${choices.map((c) => c.label).join(", ")}`);
352
+ return choices;
353
+ }
219
354
  async function confirm(question) {
220
355
  const rl = createInterface({ input: process.stdin, output: process.stdout });
221
356
  try {
222
- const answer = (await rl.question(`${question} (Y/n): `)).trim().toLowerCase();
357
+ const answer = (await rl.question(`${bold(question)} ${dim("(Y/n)")} `)).trim().toLowerCase();
223
358
  return answer === "" || answer === "y" || answer === "yes";
224
359
  } finally {
225
360
  rl.close();
@@ -228,7 +363,7 @@ async function confirm(question) {
228
363
  async function input(question, defaultValue) {
229
364
  const rl = createInterface({ input: process.stdin, output: process.stdout });
230
365
  try {
231
- const answer = (await rl.question(`${question} (${defaultValue}): `)).trim();
366
+ const answer = (await rl.question(`${bold(question)} ${dim(`(${defaultValue})`)} `)).trim();
232
367
  return answer === "" ? defaultValue : answer;
233
368
  } finally {
234
369
  rl.close();
@@ -236,48 +371,91 @@ async function input(question, defaultValue) {
236
371
  }
237
372
 
238
373
  // src/targets.ts
239
- var REACT_APP_TSX = `import { useState } from 'react';
374
+ var REACT_APP_TSX = `import { useCallback, useState } from 'react';
375
+ import { PptxHandler } from 'pptx-viewer-core';
240
376
  import { PowerPointViewer } from 'pptx-react-viewer';
241
377
  import 'pptx-react-viewer/styles';
242
378
 
243
379
  export default function App() {
244
- const [content, setContent] = useState<ArrayBuffer | null>(null);
245
-
246
- const onPick = (e: React.ChangeEvent<HTMLInputElement>) =>
247
- e.target.files?.[0]?.arrayBuffer().then(setContent);
380
+ const [content, setContent] = useState<Uint8Array | null>(null);
381
+
382
+ const loadFile = useCallback((file: File) => {
383
+ const reader = new FileReader();
384
+ reader.onload = () => setContent(new Uint8Array(reader.result as ArrayBuffer));
385
+ reader.readAsArrayBuffer(file);
386
+ }, []);
387
+
388
+ const newPresentation = useCallback(async () => {
389
+ const { handler, data } = await PptxHandler.createBlank({
390
+ title: 'Untitled Presentation',
391
+ initialSlideCount: 1,
392
+ });
393
+ setContent(await handler.save(data.slides));
394
+ }, []);
395
+
396
+ if (content) {
397
+ return (
398
+ <div style={{ height: '100vh' }}>
399
+ <PowerPointViewer content={content} canEdit />
400
+ </div>
401
+ );
402
+ }
248
403
 
249
404
  return (
250
- <div style={{ height: '100vh' }}>
251
- {content ? (
252
- <PowerPointViewer content={content} canEdit />
253
- ) : (
254
- <input type="file" accept=".pptx" onChange={onPick} />
255
- )}
405
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, height: '100vh' }}>
406
+ <input
407
+ type="file"
408
+ accept=".pptx"
409
+ onChange={(e) => {
410
+ const file = e.target.files?.[0];
411
+ if (file) loadFile(file);
412
+ }}
413
+ />
414
+ <button onClick={() => void newPresentation()}>or create a New Presentation</button>
256
415
  </div>
257
416
  );
258
417
  }
259
418
  `;
260
419
  var VUE_APP_VUE = `<script setup lang="ts">
261
420
  import { ref } from 'vue';
421
+ import { PptxHandler } from 'pptx-viewer-core';
262
422
  import { PowerPointViewer } from 'pptx-vue-viewer';
263
423
  import 'pptx-vue-viewer/styles';
264
424
 
265
425
  const content = ref<Uint8Array>();
266
426
 
427
+ function loadFile(file: File) {
428
+ const reader = new FileReader();
429
+ reader.onload = () => (content.value = new Uint8Array(reader.result as ArrayBuffer));
430
+ reader.readAsArrayBuffer(file);
431
+ }
432
+
267
433
  function onPick(e: Event) {
268
434
  const file = (e.target as HTMLInputElement).files?.[0];
269
- file?.arrayBuffer().then((buf) => (content.value = new Uint8Array(buf)));
435
+ if (file) loadFile(file);
436
+ }
437
+
438
+ async function newPresentation() {
439
+ const { handler, data } = await PptxHandler.createBlank({
440
+ title: 'Untitled Presentation',
441
+ initialSlideCount: 1,
442
+ });
443
+ content.value = await handler.save(data.slides);
270
444
  }
271
445
  </script>
272
446
 
273
447
  <template>
274
- <div style="height: 100vh">
275
- <PowerPointViewer v-if="content" :content="content" style="height: 100%" />
276
- <input v-else type="file" accept=".pptx" @change="onPick" />
448
+ <div v-if="content" style="height: 100vh">
449
+ <PowerPointViewer :content="content" can-edit style="height: 100%" />
450
+ </div>
451
+ <div v-else style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
452
+ <input type="file" accept=".pptx" @change="onPick" />
453
+ <button @click="newPresentation">or create a New Presentation</button>
277
454
  </div>
278
455
  </template>
279
456
  `;
280
457
  var ANGULAR_APP_TS = `import { Component, signal } from '@angular/core';
458
+ import { PptxHandler } from 'pptx-viewer-core';
281
459
  import { PowerPointViewerComponent } from 'pptx-angular-viewer';
282
460
 
283
461
  @Component({
@@ -285,17 +463,20 @@ import { PowerPointViewerComponent } from 'pptx-angular-viewer';
285
463
  standalone: true,
286
464
  imports: [PowerPointViewerComponent],
287
465
  template: \`
288
- <div style="height: 100vh">
289
- @if (content(); as c) {
290
- <pptx-power-point-viewer [content]="c" style="height: 100%" />
291
- } @else {
466
+ @if (content(); as c) {
467
+ <div style="height: 100vh">
468
+ <pptx-power-point-viewer [content]="c" [canEdit]="true" style="height: 100%" />
469
+ </div>
470
+ } @else {
471
+ <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: 100vh">
292
472
  <input type="file" accept=".pptx" (change)="onPick($event)" />
293
- }
294
- </div>
473
+ <button (click)="newPresentation()">or create a New Presentation</button>
474
+ </div>
475
+ }
295
476
  \`,
296
477
  })
297
478
  export class App {
298
- content = signal<ArrayBuffer | null>(null);
479
+ content = signal<ArrayBuffer | Uint8Array | null>(null);
299
480
 
300
481
  async onPick(e: Event) {
301
482
  const file = (e.target as HTMLInputElement).files?.[0];
@@ -303,6 +484,14 @@ export class App {
303
484
  this.content.set(await file.arrayBuffer());
304
485
  }
305
486
  }
487
+
488
+ async newPresentation() {
489
+ const { handler, data } = await PptxHandler.createBlank({
490
+ title: 'Untitled Presentation',
491
+ initialSlideCount: 1,
492
+ });
493
+ this.content.set(await handler.save(data.slides));
494
+ }
306
495
  }
307
496
  `;
308
497
  var TARGETS = [
@@ -336,6 +525,7 @@ Docs: https://www.npmjs.com/package/pptx-react-viewer`,
336
525
  args: (dir) => [dir, "--template", "react-ts"],
337
526
  extraPackages: [
338
527
  "pptx-react-viewer",
528
+ "pptx-viewer-core",
339
529
  "framer-motion",
340
530
  "lucide-react",
341
531
  "react-icons",
@@ -369,7 +559,7 @@ Docs: https://www.npmjs.com/package/pptx-vue-viewer`,
369
559
  scaffold: {
370
560
  command: "create-vite@latest",
371
561
  args: (dir) => [dir, "--template", "vue-ts"],
372
- extraPackages: ["pptx-vue-viewer", "jszip", "fast-xml-parser"],
562
+ extraPackages: ["pptx-vue-viewer", "pptx-viewer-core", "jszip", "fast-xml-parser"],
373
563
  entryCandidates: ["src/App.vue"],
374
564
  entryContent: VUE_APP_VUE
375
565
  }
@@ -390,7 +580,7 @@ Docs: https://www.npmjs.com/package/pptx-angular-viewer`,
390
580
  scaffold: {
391
581
  command: "@angular/cli@latest",
392
582
  args: (dir) => ["new", dir, "--standalone", "--skip-git", "--style=css", "--skip-install"],
393
- extraPackages: ["pptx-angular-viewer"],
583
+ extraPackages: ["pptx-angular-viewer", "pptx-viewer-core"],
394
584
  // Angular v20+ generates `app.ts`; older schematics generate `app.component.ts`.
395
585
  entryCandidates: ["src/app/app.ts", "src/app/app.component.ts"],
396
586
  entryContent: ANGULAR_APP_TS
@@ -520,29 +710,34 @@ async function scaffoldProject(recipe, projectName, pm, cwd) {
520
710
  }
521
711
 
522
712
  // src/index.ts
713
+ function printBanner() {
714
+ console.log(`
715
+ ${bold(cyan("pptx-viewer"))} ${dim("\xB7 interactive installer")}`);
716
+ }
523
717
  function printUsage() {
718
+ printBanner();
524
719
  console.log(`
525
- @christophervr/pptx-viewer - interactive installer for the pptx-viewer packages
526
-
527
- Usage: npx @christophervr/pptx-viewer [options]
528
-
529
- Options:
530
- --target <ids> Skip the picker; comma-separated, any of: ${TARGETS.map((t) => t.id).join(", ")}
531
- --scaffold Bootstrap a brand-new starter project instead of installing here
532
- --dir <name> Project directory name for --scaffold
533
- --pm <manager> Package manager to use: bun, pnpm, yarn, npm (default: auto-detected)
534
- --yes, -y Skip confirmation prompts
535
- --help, -h Show this help
536
-
537
- Examples:
538
- npx @christophervr/pptx-viewer
539
- npx @christophervr/pptx-viewer --target react,mcp --yes
540
- npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes
720
+ ${bold("Usage:")} npx @christophervr/pptx-viewer [options]
721
+
722
+ ${bold("Options:")}
723
+ ${cyan("--target <ids>")} Skip the picker; comma-separated, any of: ${TARGETS.map((t) => t.id).join(", ")}
724
+ ${cyan("--scaffold")} Bootstrap a brand-new starter project instead of installing here
725
+ ${cyan("--dir <name>")} Project directory name for --scaffold
726
+ ${cyan("--pm <manager>")} Package manager to use: bun, pnpm, yarn, npm (default: auto-detected)
727
+ ${cyan("--yes, -y")} Skip confirmation prompts
728
+ ${cyan("--help, -h")} Show this help
729
+
730
+ ${bold("Examples:")}
731
+ ${gray("npx @christophervr/pptx-viewer")}
732
+ ${gray("npx @christophervr/pptx-viewer --target react,mcp --yes")}
733
+ ${gray("npx @christophervr/pptx-viewer --target react --scaffold --dir my-app --yes")}
541
734
  `);
542
735
  }
543
736
  async function resolveTargets(requested) {
544
737
  if (requested) {
545
- return findTargetsByIds(parseTargetIds(requested));
738
+ const targets = findTargetsByIds(parseTargetIds(requested));
739
+ console.log(`${green("\u2714")} ${targets.map((t) => t.label).join(", ")}`);
740
+ return targets;
546
741
  }
547
742
  if (!process.stdin.isTTY) {
548
743
  throw new Error("Not running in a terminal: pass --target explicitly (see --help).");
@@ -559,7 +754,7 @@ async function confirmCompat(cwd, targets) {
559
754
  continue;
560
755
  }
561
756
  console.log(`
562
- Warning: ${result.message}`);
757
+ ${yellow("Warning:")} ${result.message}`);
563
758
  if (process.stdin.isTTY) {
564
759
  const proceed = await confirm("Continue anyway?");
565
760
  if (!proceed) {
@@ -579,7 +774,8 @@ async function resolveScaffoldChoice(installTargets, args) {
579
774
  }
580
775
  if (scaffoldable.length > 1) {
581
776
  console.log(
582
- "\nScaffolding a new project only supports one UI framework at a time; installing instead."
777
+ `
778
+ ${dim("Scaffolding a new project only supports one UI framework at a time; installing instead.")}`
583
779
  );
584
780
  return { useScaffold: false };
585
781
  }
@@ -609,13 +805,14 @@ async function runScaffoldMode(target, args, configTargets, cwd) {
609
805
  const pm = args.pm ?? detectPackageManager(cwd);
610
806
  console.log(
611
807
  `
612
- About to scaffold "${projectName}" with ${recipe.command} (${target.label}), then install with ${pm}.
808
+ ${bold("About to scaffold")} "${cyan(projectName)}" with ${recipe.command} (${target.label}), then install with ${pm}.
613
809
  `
614
810
  );
615
811
  if (!args.yes && process.stdin.isTTY) {
616
812
  const proceed = await confirm("Continue?");
617
813
  if (!proceed) {
618
- console.log("\nSkipped.");
814
+ console.log(`
815
+ ${dim("Skipped.")}`);
619
816
  return;
620
817
  }
621
818
  }
@@ -623,15 +820,15 @@ About to scaffold "${projectName}" with ${recipe.command} (${target.label}), the
623
820
  if (!result.patchedFile) {
624
821
  console.log(
625
822
  `
626
- Scaffolded the project, but could not find an entry file to wire up automatically. See the quick-start snippet below and add it yourself.`
823
+ ${yellow("Scaffolded the project, but could not find an entry file to wire up automatically.")} See the quick-start snippet below and add it yourself.`
627
824
  );
628
825
  }
629
826
  console.log(
630
827
  `
631
- Done. Next steps:
828
+ ${green("\u2714")} ${bold("Done.")} Next steps:
632
829
 
633
- cd ${projectName}
634
- ${pm} run dev
830
+ ${cyan(`cd ${projectName}`)}
831
+ ${cyan(`${pm} run dev`)}
635
832
 
636
833
  ${target.nextSteps}
637
834
  `
@@ -650,22 +847,23 @@ async function runInstallMode(installTargets, configTargets, args, cwd) {
650
847
  }
651
848
  const proceedPastCompat = await confirmCompat(cwd, installTargets);
652
849
  if (!proceedPastCompat) {
653
- console.log("\nAborted.");
850
+ console.log(`
851
+ ${red("Aborted.")}`);
654
852
  return;
655
853
  }
656
854
  const packages = mergePackages(installTargets);
657
855
  const pm = args.pm ?? detectPackageManager(cwd);
658
856
  const [command, cmdArgs] = installCommand(pm, packages);
659
857
  console.log(`
660
- About to run: ${command} ${cmdArgs.join(" ")}
858
+ ${bold("About to run:")} ${cyan(`${command} ${cmdArgs.join(" ")}`)}
661
859
  `);
662
860
  if (!args.yes && process.stdin.isTTY) {
663
861
  const proceed = await confirm("Install now?");
664
862
  if (!proceed) {
665
863
  console.log(
666
864
  `
667
- Skipped. Run this yourself when ready:
668
- ${command} ${cmdArgs.join(" ")}
865
+ ${dim("Skipped.")} Run this yourself when ready:
866
+ ${cyan(`${command} ${cmdArgs.join(" ")}`)}
669
867
  `
670
868
  );
671
869
  return;
@@ -676,7 +874,7 @@ Skipped. Run this yourself when ready:
676
874
  throw new Error(`${command} exited with code ${exitCode}`);
677
875
  }
678
876
  console.log(`
679
- Done. Next steps:`);
877
+ ${green("\u2714")} ${bold("Done.")} Next steps:`);
680
878
  for (const target of installTargets) {
681
879
  console.log(`
682
880
  ${target.nextSteps}
@@ -695,9 +893,8 @@ async function main() {
695
893
  printUsage();
696
894
  return;
697
895
  }
896
+ printBanner();
698
897
  const targets = await resolveTargets(args.target);
699
- console.log(`
700
- Selected: ${targets.map((t) => t.label).join(", ")}`);
701
898
  const installTargets = targets.filter((t) => t.mode === "install");
702
899
  const configTargets = targets.filter((t) => t.mode === "print-config");
703
900
  const cwd = process.cwd();
@@ -710,6 +907,6 @@ Selected: ${targets.map((t) => t.label).join(", ")}`);
710
907
  }
711
908
  main().catch((err) => {
712
909
  const message = err instanceof Error ? err.message : String(err);
713
- console.error(`Error: ${message}`);
910
+ console.error(`${red("\u2718 Error:")} ${message}`);
714
911
  process.exit(1);
715
912
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christophervr/pptx-viewer",
3
- "version": "1.1.45",
3
+ "version": "1.1.46",
4
4
  "description": "Interactive installer for the pptx-viewer family of packages: React, Vue, and Angular viewer components, the framework-agnostic core engine, and the MCP server.",
5
5
  "keywords": [
6
6
  "cli",