@pep/term-deck 1.0.32 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # term-deck
2
2
 
3
- A terminal-based presentation tool with a cyberpunk aesthetic. Create beautiful slideshows in your terminal with matrix rain backgrounds, glitch effects, and ASCII art.
3
+ A terminal-based presentation tool with a cyberpunk aesthetic. Create beautiful slideshows in your terminal with matrix rain backgrounds, glitch effects, and ASCII art. Share them on the web or play them anywhere.
4
4
 
5
5
  ![npm version](https://img.shields.io/npm/v/@pep/term-deck?color=green)
6
6
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue?logo=typescript)
@@ -12,6 +12,8 @@ A terminal-based presentation tool with a cyberpunk aesthetic. Create beautiful
12
12
 
13
13
  *Matrix rain backgrounds, glitch animations, and ASCII art in your terminal*
14
14
 
15
+ **Try it online:** [term-deck-web.vercel.app](https://term-deck-web.vercel.app)
16
+
15
17
  ## Features
16
18
 
17
19
  - 🌊 **Matrix Rain Background** - Animated katakana/symbol rain effects
@@ -26,6 +28,7 @@ A terminal-based presentation tool with a cyberpunk aesthetic. Create beautiful
26
28
  - 🔧 **Fully Themeable** - Create custom themes
27
29
  - ⚡ **Beautiful CLI** - Colorful, styled terminal output
28
30
  - 📦 **Type-Safe** - Full TypeScript with Zod validation
31
+ - 🌐 **Web Platform** - Share decks online and play from URLs
29
32
 
30
33
  ## Installation
31
34
 
@@ -52,6 +55,9 @@ term-deck present .
52
55
 
53
56
  # Export to video
54
57
  term-deck export . -o presentation.mp4
58
+
59
+ # Play a shared deck from the web
60
+ npx @pep/term-deck play https://term-deck-web.vercel.app/d/demo
55
61
  ```
56
62
 
57
63
  Output:
@@ -116,6 +122,11 @@ Run `term-deck --help` to see the styled help:
116
122
  init <name> Create a new presentation deck
117
123
  -t, --theme <name> Theme preset (default: matrix)
118
124
 
125
+ play <url> Play a deck from term-deck web
126
+ -s, --start <n> Start at slide number
127
+ -n, --notes Show presenter notes
128
+ -l, --loop Loop back after last slide
129
+
119
130
  ▶ HOTKEYS:
120
131
 
121
132
  Space / → Next slide
@@ -264,6 +275,35 @@ term-deck record . -o presentation.cast
264
275
  asciinema play presentation.cast
265
276
  ```
266
277
 
278
+ ## Web Platform
279
+
280
+ Share your presentations online at [term-deck-web.vercel.app](https://term-deck-web.vercel.app).
281
+
282
+ ### Upload & Share
283
+
284
+ 1. Go to [term-deck-web.vercel.app/upload](https://term-deck-web.vercel.app/upload)
285
+ 2. Drag and drop your markdown slides
286
+ 3. Get a shareable URL like `term-deck-web.vercel.app/d/abc123`
287
+
288
+ ### Play from URL
289
+
290
+ Anyone can play a shared deck in their terminal:
291
+
292
+ ```bash
293
+ # Play a shared presentation
294
+ npx @pep/term-deck play https://term-deck-web.vercel.app/d/abc123
295
+
296
+ # With options
297
+ npx @pep/term-deck play https://term-deck-web.vercel.app/d/abc123 --notes --loop
298
+ ```
299
+
300
+ The web viewer includes:
301
+ - Matrix rain background
302
+ - Keyboard navigation (arrows, space, numbers)
303
+ - Fullscreen mode (F key)
304
+ - Slide list (L key)
305
+ - All transition effects (glitch, fade, typewriter)
306
+
267
307
  ## Themes
268
308
 
269
309
  term-deck includes built-in themes. Set via `themePreset` in config:
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { join } from 'path';
2
+ import path2, { join } from 'path';
3
3
  import { pathToFileURL } from 'url';
4
4
  import { z } from 'zod';
5
5
  import matter from 'gray-matter';
6
- import { mkdir, writeFile, access, rm, readFile, unlink } from 'fs/promises';
6
+ import fs, { mkdir, writeFile, access, rm, readFile, unlink } from 'fs/promises';
7
7
  import fg from 'fast-glob';
8
8
  import blessed from 'neo-blessed';
9
9
  import gradient2 from 'gradient-string';
@@ -14,7 +14,7 @@ import figlet from 'figlet';
14
14
  import { createWriteStream } from 'fs';
15
15
  import { Command } from 'commander';
16
16
  import { intro, log, outro, spinner } from '@clack/prompts';
17
- import { tmpdir } from 'os';
17
+ import os, { tmpdir } from 'os';
18
18
  import { execa } from 'execa';
19
19
  import pc from 'picocolors';
20
20
 
@@ -181,8 +181,8 @@ var init_config = __esm({
181
181
  // src/schemas/validation.ts
182
182
  function formatZodError(error, context) {
183
183
  const issues = error.issues.map((issue) => {
184
- const path2 = issue.path.join(".");
185
- return ` - ${path2 ? `${path2}: ` : ""}${issue.message}`;
184
+ const path3 = issue.path.join(".");
185
+ return ` - ${path3 ? `${path3}: ` : ""}${issue.message}`;
186
186
  });
187
187
  return `Invalid ${context}:
188
188
  ${issues.join("\n")}`;
@@ -568,10 +568,10 @@ var init_theme_errors = __esm({
568
568
  * @param themeName - Optional name of the theme that caused the error
569
569
  * @param path - Optional path to the theme file or package
570
570
  */
571
- constructor(message, themeName, path2) {
571
+ constructor(message, themeName, path3) {
572
572
  super(message);
573
573
  this.themeName = themeName;
574
- this.path = path2;
574
+ this.path = path3;
575
575
  this.name = "ThemeError";
576
576
  }
577
577
  };
@@ -1403,7 +1403,7 @@ var init_main = __esm({
1403
1403
  init_esm_shims();
1404
1404
 
1405
1405
  // package.json
1406
- var version = "1.0.32";
1406
+ var version = "1.1.0";
1407
1407
 
1408
1408
  // src/cli/commands/present.ts
1409
1409
  init_esm_shims();
@@ -1965,6 +1965,103 @@ term-deck export . -o ${name}.gif
1965
1965
  await writeFile(join(deckDir, "README.md"), readme);
1966
1966
  }
1967
1967
 
1968
+ // src/cli/commands/play.ts
1969
+ init_esm_shims();
1970
+ init_main();
1971
+ async function fetchDeck(url) {
1972
+ let apiUrl;
1973
+ try {
1974
+ const urlObj = new URL(url);
1975
+ if (urlObj.pathname.startsWith("/d/")) {
1976
+ const id = urlObj.pathname.split("/d/")[1];
1977
+ apiUrl = `${urlObj.origin}/api/deck/${id}/raw`;
1978
+ } else if (urlObj.pathname.startsWith("/api/deck/")) {
1979
+ apiUrl = url;
1980
+ } else {
1981
+ throw new Error("Invalid deck URL format");
1982
+ }
1983
+ } catch {
1984
+ apiUrl = `https://term-deck-web.vercel.app/api/deck/${url}/raw`;
1985
+ }
1986
+ const response = await fetch(apiUrl);
1987
+ if (!response.ok) {
1988
+ if (response.status === 404) {
1989
+ throw new Error("Deck not found");
1990
+ }
1991
+ throw new Error(`Failed to fetch deck: ${response.status} ${response.statusText}`);
1992
+ }
1993
+ return response.json();
1994
+ }
1995
+ async function writeTempSlides(deck) {
1996
+ const tempDir = await fs.mkdtemp(path2.join(os.tmpdir(), "term-deck-"));
1997
+ for (const slide of deck.slides) {
1998
+ const frontmatter = Object.entries(slide.frontmatter).filter(([, value]) => value !== void 0).map(([key, value]) => {
1999
+ if (typeof value === "string") {
2000
+ return `${key}: ${value}`;
2001
+ }
2002
+ if (Array.isArray(value)) {
2003
+ return `${key}:
2004
+ ${value.map((v) => ` - ${v}`).join("\n")}`;
2005
+ }
2006
+ return `${key}: ${JSON.stringify(value)}`;
2007
+ }).join("\n");
2008
+ let content = `---
2009
+ ${frontmatter}
2010
+ ---
2011
+
2012
+ ${slide.body}`;
2013
+ if (slide.notes) {
2014
+ content += `
2015
+
2016
+ <!-- notes -->
2017
+ ${slide.notes}
2018
+ <!-- /notes -->`;
2019
+ }
2020
+ const filename = `${String(slide.index + 1).padStart(2, "0")}-${slide.frontmatter.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
2021
+ await fs.writeFile(path2.join(tempDir, filename), content, "utf-8");
2022
+ }
2023
+ if (deck.config.theme) {
2024
+ const themeContent = `import { defineTheme } from '@pep/term-deck'
2025
+
2026
+ export default defineTheme(${JSON.stringify(deck.config.theme, null, 2)})
2027
+ `;
2028
+ await fs.writeFile(path2.join(tempDir, "theme.ts"), themeContent, "utf-8");
2029
+ }
2030
+ return tempDir;
2031
+ }
2032
+ async function cleanupTempDir(tempDir) {
2033
+ try {
2034
+ await fs.rm(tempDir, { recursive: true });
2035
+ } catch {
2036
+ }
2037
+ }
2038
+ var playCommand = new Command("play").description("Play a presentation from a term-deck web URL").argument("<url>", "Deck URL (e.g., https://termdeck.vercel.app/d/abc123 or just abc123)").option("-s, --start <n>", "Start at slide number", "0").option("-n, --notes", "Show presenter notes in separate terminal").option("--notes-tty <path>", "TTY device for notes window").option("-l, --loop", "Loop back to first slide after last").action(async (url, options) => {
2039
+ let tempDir = null;
2040
+ try {
2041
+ console.log("Fetching deck...");
2042
+ const deck = await fetchDeck(url);
2043
+ console.log(`Playing: ${deck.config.title || "Untitled Deck"}`);
2044
+ if (deck.config.author) {
2045
+ console.log(`By: ${deck.config.author}`);
2046
+ }
2047
+ console.log(`Slides: ${deck.slides.length}
2048
+ `);
2049
+ tempDir = await writeTempSlides(deck);
2050
+ await present(tempDir, {
2051
+ startSlide: Number.parseInt(options.start, 10),
2052
+ showNotes: options.notes,
2053
+ notesTty: options.notesTty,
2054
+ loop: options.loop
2055
+ });
2056
+ } catch (error) {
2057
+ handleError(error);
2058
+ } finally {
2059
+ if (tempDir) {
2060
+ await cleanupTempDir(tempDir);
2061
+ }
2062
+ }
2063
+ });
2064
+
1968
2065
  // src/cli/help.ts
1969
2066
  init_esm_shims();
1970
2067
  function showHelp() {
@@ -2008,6 +2105,11 @@ function showHelp() {
2008
2105
  console.log(pc.green(" init") + pc.dim(" <name> ") + pc.white("Create a new presentation deck"));
2009
2106
  console.log(pc.dim(" -t, --theme <name> ") + pc.white("Theme preset (default: matrix)"));
2010
2107
  console.log("");
2108
+ console.log(pc.green(" play") + pc.dim(" <url> ") + pc.white("Play a deck from term-deck web"));
2109
+ console.log(pc.dim(" -s, --start <n> ") + pc.white("Start at slide number"));
2110
+ console.log(pc.dim(" -n, --notes ") + pc.white("Show presenter notes"));
2111
+ console.log(pc.dim(" -l, --loop ") + pc.white("Loop back after last slide"));
2112
+ console.log("");
2011
2113
  console.log(pc.bold(pc.cyan("\u25B6 HOTKEYS:")));
2012
2114
  console.log("");
2013
2115
  console.log(pc.dim(" Space / \u2192 ") + pc.white("Next slide"));
@@ -2044,7 +2146,7 @@ function showVersion(version2) {
2044
2146
  // bin/term-deck.ts
2045
2147
  var args = process.argv.slice(2);
2046
2148
  if (args.includes("-h") || args.includes("--help") || args.length === 0) {
2047
- if (!args.some((arg) => ["present", "export", "init"].includes(arg))) {
2149
+ if (!args.some((arg) => ["present", "export", "init", "play"].includes(arg))) {
2048
2150
  showHelp();
2049
2151
  process.exit(0);
2050
2152
  }
@@ -2058,6 +2160,7 @@ program.name("term-deck").description("Terminal presentation tool with a cyberpu
2058
2160
  program.addCommand(presentCommand);
2059
2161
  program.addCommand(exportCommand);
2060
2162
  program.addCommand(initCommand);
2163
+ program.addCommand(playCommand);
2061
2164
  program.argument("[dir]", "Slides directory to present").action(async (dir) => {
2062
2165
  if (dir) {
2063
2166
  try {