termcut 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +3 -2
- package/src/browser.ts +122 -0
- package/src/cast.ts +2 -0
- package/src/cli.ts +45 -2
- package/src/config.ts +11 -1
- package/src/diff.ts +91 -0
- package/src/errors.ts +15 -0
- package/src/index.ts +5 -0
- package/src/keylabels.ts +81 -0
- package/src/live.ts +5 -1
- package/src/presets.ts +24 -0
- package/src/recorder.ts +41 -113
- package/src/renderer/encoder.ts +29 -4
- package/src/renderer/generated/page.js +1 -1
- package/src/renderer/page-entry.ts +28 -0
- package/src/renderer/page.ts +14 -1
- package/src/renderer/webview.ts +86 -5
- package/src/timeline.ts +29 -10
- package/src/types.ts +40 -0
package/src/timeline.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { MARKER } from "./cast";
|
|
|
2
2
|
import type { CastEvent } from "./types";
|
|
3
3
|
|
|
4
4
|
export interface TimedEvent {
|
|
5
|
-
/** Time on the visible (hide-collapsed, speed-adjusted) timeline, seconds. */
|
|
5
|
+
/** Time on the visible (hide-collapsed, speed-adjusted, idle-compressed) timeline, seconds. */
|
|
6
6
|
vt: number;
|
|
7
7
|
type: CastEvent[1];
|
|
8
8
|
data: string;
|
|
@@ -13,16 +13,22 @@ export interface Timeline {
|
|
|
13
13
|
duration: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface TimelineOptions {
|
|
17
|
+
/** Keep `i` (input) events; the renderer needs them for the key overlay. Default false. */
|
|
18
|
+
keepInput?: boolean;
|
|
19
|
+
/** Cap any gap between consecutive events to this many seconds (idle compression). */
|
|
20
|
+
maxPause?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
|
-
* Collapse hidden intervals
|
|
18
|
-
* on the instant the hide started, so the first visible frame after `show` reflects their
|
|
19
|
-
* Input (`i`) events are dropped
|
|
24
|
+
* Collapse hidden intervals, apply playback speed, optionally cap idle gaps. Hidden events keep their relative
|
|
25
|
+
* order but all land on the instant the hide started, so the first visible frame after `show` reflects their
|
|
26
|
+
* combined effect. Input (`i`) events are dropped unless `keepInput`: the PTY already echoed them.
|
|
20
27
|
*/
|
|
21
|
-
export function buildTimeline(events: CastEvent[], playbackSpeed: number): Timeline {
|
|
28
|
+
export function buildTimeline(events: CastEvent[], playbackSpeed: number, opts: TimelineOptions = {}): Timeline {
|
|
22
29
|
const out: TimedEvent[] = [];
|
|
23
30
|
let hiddenSince: number | null = null;
|
|
24
31
|
let removed = 0;
|
|
25
|
-
let duration = 0;
|
|
26
32
|
|
|
27
33
|
for (const [t, type, data] of events) {
|
|
28
34
|
if (type === "m" && data === MARKER.hide) {
|
|
@@ -36,12 +42,25 @@ export function buildTimeline(events: CastEvent[], playbackSpeed: number): Timel
|
|
|
36
42
|
}
|
|
37
43
|
continue;
|
|
38
44
|
}
|
|
39
|
-
if (type === "i") continue;
|
|
45
|
+
if (type === "i" && !opts.keepInput) continue;
|
|
40
46
|
const visible = hiddenSince === null ? t - removed : hiddenSince - removed;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
out.push({ vt: visible / playbackSpeed, type, data });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (opts.maxPause !== undefined && opts.maxPause >= 0) {
|
|
51
|
+
// Walk forward; whenever the next event is further away than maxPause, pull everything after it closer.
|
|
52
|
+
let shift = 0;
|
|
53
|
+
let prev: number | null = null;
|
|
54
|
+
for (const e of out) {
|
|
55
|
+
const original = e.vt;
|
|
56
|
+
if (prev !== null && original - prev > opts.maxPause) shift += original - prev - opts.maxPause;
|
|
57
|
+
prev = original;
|
|
58
|
+
e.vt = original - shift;
|
|
59
|
+
}
|
|
44
60
|
}
|
|
61
|
+
|
|
62
|
+
let duration = 0;
|
|
63
|
+
for (const e of out) if (e.vt > duration) duration = e.vt;
|
|
45
64
|
return { events: out, duration };
|
|
46
65
|
}
|
|
47
66
|
|
package/src/types.ts
CHANGED
|
@@ -48,6 +48,26 @@ export interface FontConfig {
|
|
|
48
48
|
letterSpacing?: number;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface KeysConfig {
|
|
52
|
+
position?: "bottom" | "top";
|
|
53
|
+
/** How long a chip stays visible, e.g. "1.2s". */
|
|
54
|
+
ttl?: Duration;
|
|
55
|
+
/** Printable keys pressed within this window merge into one chip. Default "350ms". */
|
|
56
|
+
merge?: Duration;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A region of the terminal grid to magnify. */
|
|
60
|
+
export interface ZoomRegion {
|
|
61
|
+
/** Inclusive row range, 0-based. Default: all rows. */
|
|
62
|
+
rows?: [number, number];
|
|
63
|
+
/** Inclusive column range, 0-based. Default: all columns. */
|
|
64
|
+
cols?: [number, number];
|
|
65
|
+
/** Animation length on the render clock. Default "400ms". */
|
|
66
|
+
duration?: Duration;
|
|
67
|
+
/** Inner padding around the region, in cells. Default 1. */
|
|
68
|
+
padding?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
/** A browser window recorded next to the terminal (Bun.WebView). */
|
|
52
72
|
export interface BrowserConfig {
|
|
53
73
|
/** Page to open when recording starts (may also be opened later with `t.browser.goto`). */
|
|
@@ -115,6 +135,12 @@ export interface VideoConfig {
|
|
|
115
135
|
height?: number;
|
|
116
136
|
/** Where looping outputs (GIF, WebP) start: a frame number or a percentage like "50%". */
|
|
117
137
|
loopOffset?: number | string;
|
|
138
|
+
/** Idle compression: at render time, gaps between events longer than this are shortened to this. */
|
|
139
|
+
maxPause?: Duration;
|
|
140
|
+
/** Show recent key presses as chips. `true` = bottom centre, 1.2 s. */
|
|
141
|
+
keys?: boolean | KeysConfig;
|
|
142
|
+
/** A named bundle of defaults applied under explicit settings: readme | x | youtube | square. */
|
|
143
|
+
preset?: "readme" | "x" | "youtube" | "square";
|
|
118
144
|
|
|
119
145
|
/** Frames per second of the output. Default 60. */
|
|
120
146
|
fps?: number;
|
|
@@ -169,6 +195,8 @@ export interface ResolvedConfig {
|
|
|
169
195
|
width?: number;
|
|
170
196
|
height?: number;
|
|
171
197
|
loopOffset?: number | string;
|
|
198
|
+
maxPause?: number;
|
|
199
|
+
keys?: Required<KeysConfig> & { ttl: number; merge: number };
|
|
172
200
|
fps: number;
|
|
173
201
|
typingSpeed: number;
|
|
174
202
|
typingJitter: number;
|
|
@@ -278,6 +306,18 @@ export interface TerminalSession {
|
|
|
278
306
|
resize(cols: number, rows: number): Promise<void>;
|
|
279
307
|
/** Shorthand for `run("clear")`. */
|
|
280
308
|
clear(): Promise<void>;
|
|
309
|
+
/**
|
|
310
|
+
* Show Markdown in the terminal as a caption (rendered to ANSI by @wterm/markdown). It is written into the
|
|
311
|
+
* recording and the screen model only, never sent to the shell; afterwards the shell is asked for a fresh prompt.
|
|
312
|
+
* Use at a shell prompt, not inside a full-screen program.
|
|
313
|
+
*/
|
|
314
|
+
print(markdown: string): Promise<void>;
|
|
315
|
+
/** A title card: big heading + rule, then a pause (default "1.5s"). */
|
|
316
|
+
title(text: string, opts?: { pause?: Duration }): Promise<void>;
|
|
317
|
+
/** Magnify a region of the terminal (animated at render time); `zoom(null)` resets. */
|
|
318
|
+
zoom(region: ZoomRegion | null): Promise<void>;
|
|
319
|
+
/** Named chapter: becomes mp4 chapter metadata and shows up in `--json` output. */
|
|
320
|
+
chapter(name: string): Promise<void>;
|
|
281
321
|
/** The recorded browser window; throws if `browser` is not configured. */
|
|
282
322
|
readonly browser: BrowserSession;
|
|
283
323
|
/** Overlay layout: bring the terminal or the browser window to the front (recorded as a marker). */
|