@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 +41 -1
- package/dist/bin/term-deck.js +112 -9
- package/dist/bin/term-deck.js.map +1 -1
- package/package.json +2 -1
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
|

|
|
6
6
|

|
|
@@ -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:
|
package/dist/bin/term-deck.js
CHANGED
|
@@ -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
|
|
185
|
-
return ` - ${
|
|
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,
|
|
571
|
+
constructor(message, themeName, path3) {
|
|
572
572
|
super(message);
|
|
573
573
|
this.themeName = themeName;
|
|
574
|
-
this.path =
|
|
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
|
|
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 {
|