@ytspar/sweetlink 1.20.0 → 1.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.
Files changed (89) hide show
  1. package/dist/browser/consoleCapture.js +1 -1
  2. package/dist/browser/consoleCapture.js.map +1 -1
  3. package/dist/browser/earlyConsoleCapture.d.ts +1 -1
  4. package/dist/browser/earlyConsoleCapture.d.ts.map +1 -1
  5. package/dist/cli/sweetlink.js +154 -68
  6. package/dist/cli/sweetlink.js.map +1 -1
  7. package/dist/daemon/demo.d.ts.map +1 -1
  8. package/dist/daemon/demo.js +3 -19
  9. package/dist/daemon/demo.js.map +1 -1
  10. package/dist/daemon/evidence.js +1 -1
  11. package/dist/daemon/evidence.js.map +1 -1
  12. package/dist/daemon/listeners.js +1 -1
  13. package/dist/daemon/listeners.js.map +1 -1
  14. package/dist/daemon/recording.d.ts +1 -1
  15. package/dist/daemon/recording.d.ts.map +1 -1
  16. package/dist/daemon/recording.js +5 -23
  17. package/dist/daemon/recording.js.map +1 -1
  18. package/dist/daemon/refs.js +1 -1
  19. package/dist/daemon/refs.js.map +1 -1
  20. package/dist/daemon/ringBuffer.d.ts +8 -0
  21. package/dist/daemon/ringBuffer.d.ts.map +1 -1
  22. package/dist/daemon/ringBuffer.js +17 -0
  23. package/dist/daemon/ringBuffer.js.map +1 -1
  24. package/dist/daemon/server.d.ts.map +1 -1
  25. package/dist/daemon/server.js +84 -23
  26. package/dist/daemon/server.js.map +1 -1
  27. package/dist/daemon/session.d.ts +8 -1
  28. package/dist/daemon/session.d.ts.map +1 -1
  29. package/dist/daemon/stateFile.js +2 -2
  30. package/dist/daemon/stateFile.js.map +1 -1
  31. package/dist/daemon/summary.js +2 -2
  32. package/dist/daemon/summary.js.map +1 -1
  33. package/dist/daemon/types.d.ts +7 -2
  34. package/dist/daemon/types.d.ts.map +1 -1
  35. package/dist/daemon/types.js.map +1 -1
  36. package/dist/daemon/utils.d.ts +22 -0
  37. package/dist/daemon/utils.d.ts.map +1 -0
  38. package/dist/daemon/utils.js +44 -0
  39. package/dist/daemon/utils.js.map +1 -0
  40. package/dist/daemon/viewer.d.ts.map +1 -1
  41. package/dist/daemon/viewer.js +5 -11
  42. package/dist/daemon/viewer.js.map +1 -1
  43. package/dist/daemon/visualDiff.d.ts +9 -0
  44. package/dist/daemon/visualDiff.d.ts.map +1 -1
  45. package/dist/daemon/visualDiff.js +13 -2
  46. package/dist/daemon/visualDiff.js.map +1 -1
  47. package/dist/next.js +3 -3
  48. package/dist/next.js.map +1 -1
  49. package/dist/runs.d.ts +15 -3
  50. package/dist/runs.d.ts.map +1 -1
  51. package/dist/runs.js +29 -13
  52. package/dist/runs.js.map +1 -1
  53. package/dist/server/index.d.ts +1 -1
  54. package/dist/server/index.d.ts.map +1 -1
  55. package/dist/simulator/android.d.ts +19 -0
  56. package/dist/simulator/android.d.ts.map +1 -1
  57. package/dist/simulator/android.js +141 -2
  58. package/dist/simulator/android.js.map +1 -1
  59. package/dist/simulator/androidTaps.d.ts +109 -0
  60. package/dist/simulator/androidTaps.d.ts.map +1 -0
  61. package/dist/simulator/androidTaps.js +208 -0
  62. package/dist/simulator/androidTaps.js.map +1 -0
  63. package/dist/simulator/ios.d.ts +7 -0
  64. package/dist/simulator/ios.d.ts.map +1 -1
  65. package/dist/simulator/ios.js +70 -25
  66. package/dist/simulator/ios.js.map +1 -1
  67. package/dist/simulator/overlay.d.ts +41 -0
  68. package/dist/simulator/overlay.d.ts.map +1 -0
  69. package/dist/simulator/overlay.js +78 -0
  70. package/dist/simulator/overlay.js.map +1 -0
  71. package/dist/term/cast.d.ts +24 -0
  72. package/dist/term/cast.d.ts.map +1 -0
  73. package/dist/term/cast.js +24 -0
  74. package/dist/term/cast.js.map +1 -0
  75. package/dist/term/player.d.ts.map +1 -1
  76. package/dist/term/player.js +37 -15
  77. package/dist/term/player.js.map +1 -1
  78. package/dist/term/recorder.d.ts +7 -0
  79. package/dist/term/recorder.d.ts.map +1 -1
  80. package/dist/term/recorder.js +36 -2
  81. package/dist/term/recorder.js.map +1 -1
  82. package/dist/types.d.ts +2 -1
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/package.json +1 -1
  86. package/dist/term/ansi.d.ts +0 -37
  87. package/dist/term/ansi.d.ts.map +0 -1
  88. package/dist/term/ansi.js +0 -205
  89. package/dist/term/ansi.js.map +0 -1
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Android tap-event capture via `adb shell getevent -l`.
3
+ *
4
+ * We stream input events from the connected emulator/device while a screen
5
+ * recording is in progress, parse touch events into (x, y, t) tuples, and
6
+ * write them as a sidecar JSON. A separate post-process step (overlay.ts)
7
+ * renders rings on the recording at each tap timestamp.
8
+ *
9
+ * `getevent -p` is used once at startup to discover the touchscreen input
10
+ * device and its ABS coordinate range — coordinates from `getevent` are in
11
+ * raw input-device units, which we scale to screen pixels using the ratio
12
+ * of `wm size` vs. the input device's max X/Y.
13
+ */
14
+ import { execFile, spawn } from 'child_process';
15
+ function adbExec(args) {
16
+ return new Promise((resolve, reject) => {
17
+ execFile('adb', args, { encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
18
+ if (err)
19
+ reject(err);
20
+ else
21
+ resolve(stdout);
22
+ });
23
+ });
24
+ }
25
+ /**
26
+ * Parse `adb shell wm size` output:
27
+ * Physical size: 1080x2400
28
+ * Override size: 720x1600 (optional)
29
+ *
30
+ * Override takes precedence when present (it's what apps actually render at).
31
+ */
32
+ export function parseWmSize(out) {
33
+ const override = out.match(/Override size:\s*(\d+)x(\d+)/);
34
+ const physical = out.match(/Physical size:\s*(\d+)x(\d+)/);
35
+ const m = override ?? physical;
36
+ if (!m)
37
+ return null;
38
+ return { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
39
+ }
40
+ /**
41
+ * Parse `adb shell getevent -p` output and find the first device that
42
+ * declares BTN_TOUCH (014a) and ABS_MT_POSITION_X/Y. Returns the device
43
+ * path plus the raw coordinate ranges.
44
+ *
45
+ * Layout (one block per device):
46
+ *
47
+ * add device 3: /dev/input/event3
48
+ * name: "Touchscreen"
49
+ * events:
50
+ * KEY (0001): 014a
51
+ * ABS (0003): 0030 0031 0035 0036 ...
52
+ * input props:
53
+ * ...
54
+ * ABS_MT_POSITION_X : value 0, min 0, max 1080, fuzz 0, flat 0, resolution 0
55
+ * ABS_MT_POSITION_Y : value 0, min 0, max 2400, fuzz 0, flat 0, resolution 0
56
+ */
57
+ export function parseGetEventProbe(out) {
58
+ const blocks = out.split(/^add device /m).slice(1);
59
+ const devices = [];
60
+ for (const block of blocks) {
61
+ const pathMatch = block.match(/^\d+:\s+(\/dev\/input\/event\d+)/);
62
+ if (!pathMatch)
63
+ continue;
64
+ const path = pathMatch[1];
65
+ // BTN_TOUCH = 014a appears in the KEY (0001) line OR is mentioned by name.
66
+ const hasBtnTouch = /KEY\b[^\n]*\b014a\b/i.test(block) || /BTN_TOUCH/.test(block);
67
+ const xMatch = block.match(/ABS_MT_POSITION_X[^\n]*max\s+(\d+)/);
68
+ const yMatch = block.match(/ABS_MT_POSITION_Y[^\n]*max\s+(\d+)/);
69
+ const maxX = xMatch ? parseInt(xMatch[1], 10) : 0;
70
+ const maxY = yMatch ? parseInt(yMatch[1], 10) : 0;
71
+ devices.push({ path, maxX, maxY, hasBtnTouch });
72
+ }
73
+ return devices;
74
+ }
75
+ /**
76
+ * Run the necessary adb queries to discover the touchscreen device + scale.
77
+ * Throws when no touchscreen device is found.
78
+ */
79
+ export async function findTouchDevice(deviceSerial) {
80
+ const probe = await adbExec(['-s', deviceSerial, 'shell', 'getevent', '-p']);
81
+ const wm = await adbExec(['-s', deviceSerial, 'shell', 'wm', 'size']);
82
+ const screen = parseWmSize(wm);
83
+ if (!screen) {
84
+ throw new Error(`Could not parse screen size from \`wm size\`: ${wm.slice(0, 100)}`);
85
+ }
86
+ const candidates = parseGetEventProbe(probe).filter((d) => d.hasBtnTouch && d.maxX > 0 && d.maxY > 0);
87
+ if (candidates.length === 0) {
88
+ throw new Error('No touchscreen input device found on the emulator.');
89
+ }
90
+ const dev = candidates[0];
91
+ return {
92
+ path: dev.path,
93
+ maxX: dev.maxX,
94
+ maxY: dev.maxY,
95
+ screenWidth: screen.width,
96
+ screenHeight: screen.height,
97
+ };
98
+ }
99
+ export function freshParseState() {
100
+ return { curX: null, curY: null, downAtMs: null };
101
+ }
102
+ /**
103
+ * Parse a hex value from `getevent` output. A hostile / unusual adb daemon
104
+ * (manufacturer firmware, network adb at 5555) can emit non-hex tokens or
105
+ * extra-large values; reject those rather than letting NaN/huge numbers
106
+ * propagate into the overlay filter and silently break ffmpeg.
107
+ */
108
+ function safeHex(value, max) {
109
+ if (!value)
110
+ return null;
111
+ if (!/^[0-9a-fA-F]+$/.test(value))
112
+ return null;
113
+ const n = parseInt(value, 16);
114
+ if (!Number.isFinite(n) || n < 0 || n > max)
115
+ return null;
116
+ return n;
117
+ }
118
+ export function processGetEventChunk(text, state, scaleX, scaleY, startMs, nowMs, bounds) {
119
+ const out = [];
120
+ for (const line of text.split('\n')) {
121
+ const m = line.match(/^\/dev\/input\/\S+:\s+(EV_\w+)\s+(\w+)\s+(\w+)/);
122
+ if (!m)
123
+ continue;
124
+ const [, evType, evCode, valueStr] = m;
125
+ if (evType === 'EV_ABS' && evCode === 'ABS_MT_POSITION_X') {
126
+ const v = safeHex(valueStr, bounds?.maxX ?? Number.MAX_SAFE_INTEGER);
127
+ if (v !== null)
128
+ state.curX = v;
129
+ }
130
+ else if (evType === 'EV_ABS' && evCode === 'ABS_MT_POSITION_Y') {
131
+ const v = safeHex(valueStr, bounds?.maxY ?? Number.MAX_SAFE_INTEGER);
132
+ if (v !== null)
133
+ state.curY = v;
134
+ }
135
+ else if (evType === 'EV_KEY' && evCode === 'BTN_TOUCH') {
136
+ if (valueStr === 'DOWN') {
137
+ state.downAtMs = nowMs;
138
+ }
139
+ else if (valueStr === 'UP') {
140
+ if (state.curX !== null && state.curY !== null && state.downAtMs !== null) {
141
+ const px = Math.round(state.curX * scaleX);
142
+ const py = Math.round(state.curY * scaleY);
143
+ const sw = bounds?.screenWidth;
144
+ const sh = bounds?.screenHeight;
145
+ const x = sw ? Math.max(0, Math.min(sw, px)) : px;
146
+ const y = sh ? Math.max(0, Math.min(sh, py)) : py;
147
+ if (Number.isFinite(x) && Number.isFinite(y)) {
148
+ out.push({ x, y, t: (state.downAtMs - startMs) / 1000 });
149
+ }
150
+ }
151
+ state.curX = null;
152
+ state.curY = null;
153
+ state.downAtMs = null;
154
+ }
155
+ }
156
+ }
157
+ return out;
158
+ }
159
+ /**
160
+ * Spawn a `getevent -l` listener for the given input path. Returns the
161
+ * child process plus a live array of tap events that the caller can read
162
+ * after they await `done` (or kill the process and await close).
163
+ *
164
+ * `done` resolves once the proc 'close' event fires AND the trailing
165
+ * buffered partial line has been processed — without it, taps in the final
166
+ * milliseconds before SIGTERM are silently dropped.
167
+ */
168
+ export function captureTapsLive(deviceSerial, info, startMs) {
169
+ const taps = [];
170
+ const state = freshParseState();
171
+ const scaleX = info.screenWidth / info.maxX;
172
+ const scaleY = info.screenHeight / info.maxY;
173
+ const bounds = {
174
+ maxX: info.maxX,
175
+ maxY: info.maxY,
176
+ screenWidth: info.screenWidth,
177
+ screenHeight: info.screenHeight,
178
+ };
179
+ const proc = spawn('adb', ['-s', deviceSerial, 'shell', 'getevent', '-l', info.path], {
180
+ stdio: ['ignore', 'pipe', 'pipe'],
181
+ });
182
+ proc.unref(); // don't keep the parent process alive past its main work
183
+ let buffer = '';
184
+ proc.stdout?.on('data', (chunk) => {
185
+ buffer += chunk.toString();
186
+ // Process complete lines; keep the trailing partial line for next chunk.
187
+ const lastNl = buffer.lastIndexOf('\n');
188
+ if (lastNl === -1)
189
+ return;
190
+ const ready = buffer.slice(0, lastNl);
191
+ buffer = buffer.slice(lastNl + 1);
192
+ const newTaps = processGetEventChunk(ready, state, scaleX, scaleY, startMs, Date.now(), bounds);
193
+ taps.push(...newTaps);
194
+ });
195
+ const done = new Promise((resolve) => {
196
+ proc.on('close', () => {
197
+ // Flush trailing buffer — anything that didn't end with \n.
198
+ if (buffer.length > 0) {
199
+ const newTaps = processGetEventChunk(buffer, state, scaleX, scaleY, startMs, Date.now(), bounds);
200
+ taps.push(...newTaps);
201
+ buffer = '';
202
+ }
203
+ resolve();
204
+ });
205
+ });
206
+ return { proc, taps, done };
207
+ }
208
+ //# sourceMappingURL=androidTaps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"androidTaps.js","sourceRoot":"","sources":["../../src/simulator/androidTaps.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,13 @@ export interface IosRecordOptions {
18
18
  cwd?: string;
19
19
  /** Override shell. */
20
20
  shell?: string;
21
+ /**
22
+ * When true, the spawned command sees the full process.env. Default false:
23
+ * only a small allowlist (PATH/HOME/USER/LANG/SHELL/etc.) is forwarded so
24
+ * secrets in env (NPM_TOKEN, GH_TOKEN, ANTHROPIC_API_KEY, ...) cannot leak
25
+ * into the recorded .mp4 via stack traces or test logs.
26
+ */
27
+ inheritEnv?: boolean;
21
28
  }
22
29
  export interface IosRecordResult {
23
30
  output: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAoBD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAqBhD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkF5F"}
1
+ {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAyBD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAoBD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAqBhD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuG5F"}
@@ -10,6 +10,28 @@
10
10
  import { execFile, spawn } from 'child_process';
11
11
  import { promises as fs } from 'fs';
12
12
  import * as path from 'path';
13
+ const MINIMAL_ENV_KEYS = [
14
+ 'PATH',
15
+ 'HOME',
16
+ 'USER',
17
+ 'LANG',
18
+ 'LC_ALL',
19
+ 'LC_CTYPE',
20
+ 'TERM',
21
+ 'SHELL',
22
+ 'TZ',
23
+ 'TMPDIR',
24
+ 'PWD',
25
+ ];
26
+ function pickMinimalEnv() {
27
+ const out = {};
28
+ for (const key of MINIMAL_ENV_KEYS) {
29
+ const v = process.env[key];
30
+ if (typeof v === 'string')
31
+ out[key] = v;
32
+ }
33
+ return out;
34
+ }
13
35
  /** Run `xcrun simctl <args>` and return parsed JSON or text output. */
14
36
  function simctl(args, json = false) {
15
37
  return new Promise((resolve, reject) => {
@@ -71,47 +93,70 @@ export async function recordIosSimulator(options) {
71
93
  if (recProc.exitCode !== null) {
72
94
  throw new Error(`recordVideo failed to start on ${device.name}: ${recStderr.trim() || 'unknown error'}`);
73
95
  }
74
- // Run the user's command and capture its exit code.
96
+ // Run the user's command and capture its exit code. Add an `error`
97
+ // handler so a missing shell or bad cwd doesn't crash the parent process
98
+ // before we get a chance to clean up the orphaned recordVideo child.
75
99
  const startedAt = Date.now();
76
100
  const cmdResult = await new Promise((resolve) => {
77
101
  const child = spawn(options.shell ?? '/bin/sh', ['-c', options.command], {
78
102
  cwd: options.cwd,
79
103
  stdio: 'inherit',
104
+ env: options.inheritEnv
105
+ ? process.env
106
+ : pickMinimalEnv(),
107
+ });
108
+ child.on('error', (err) => {
109
+ console.error(`[Sweetlink] Failed to spawn user command: ${err.message}`);
110
+ resolve({ exitCode: 127 });
80
111
  });
81
112
  child.on('close', (code) => resolve({ exitCode: code ?? 0 }));
82
113
  });
83
114
  const durationSec = (Date.now() - startedAt) / 1000;
84
115
  // Stop recording: SIGINT triggers simctl's flush-and-exit path. SIGTERM
85
- // would also work but truncates the trailing buffer.
86
- const closed = await new Promise((resolve) => {
87
- let resolved = false;
88
- const timer = setTimeout(() => {
89
- if (!resolved) {
90
- resolved = true;
91
- // Recording didn't shut down within 5s — kill it forcefully.
92
- try {
93
- recProc.kill('SIGKILL');
116
+ // would also work but truncates the trailing buffer. Wrapped in
117
+ // try/finally so orphaned recProc gets killed on any later failure.
118
+ let closed = false;
119
+ try {
120
+ closed = await new Promise((resolve) => {
121
+ let resolved = false;
122
+ const timer = setTimeout(() => {
123
+ if (!resolved) {
124
+ resolved = true;
125
+ // Recording didn't shut down within 5s — kill it forcefully.
126
+ try {
127
+ recProc.kill('SIGKILL');
128
+ }
129
+ catch {
130
+ /* ignore */
131
+ }
132
+ resolve(false);
94
133
  }
95
- catch {
96
- /* ignore */
134
+ }, 5_000);
135
+ recProc.on('close', () => {
136
+ if (!resolved) {
137
+ resolved = true;
138
+ clearTimeout(timer);
139
+ resolve(true);
97
140
  }
98
- resolve(false);
141
+ });
142
+ try {
143
+ recProc.kill('SIGINT');
99
144
  }
100
- }, 5_000);
101
- recProc.on('close', () => {
102
- if (!resolved) {
103
- resolved = true;
104
- clearTimeout(timer);
105
- resolve(true);
145
+ catch {
146
+ /* ignore */
106
147
  }
107
148
  });
108
- try {
109
- recProc.kill('SIGINT');
110
- }
111
- catch {
112
- /* ignore */
149
+ }
150
+ finally {
151
+ if (recProc.exitCode === null) {
152
+ try {
153
+ recProc.kill('SIGKILL');
154
+ }
155
+ catch {
156
+ /* ignore */
157
+ }
113
158
  }
114
- });
159
+ }
115
160
  return {
116
161
  output: options.output,
117
162
  device: device.name,
@@ -1 +1 @@
1
- {"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Tap overlay renderer.
3
+ *
4
+ * Takes an mp4 + a list of tap events and renders red rings at each tap
5
+ * position via ffmpeg's drawbox filter. drawbox doesn't draw circles, so
6
+ * we use a hollow square (the standard "tap target" idiom in iOS' built-
7
+ * in "show touches" mode is a circle, but a 4-px-thick red square reads
8
+ * just as well at video scale and avoids needing a generated PNG asset).
9
+ */
10
+ export interface OverlayOptions {
11
+ inputPath: string;
12
+ outputPath: string;
13
+ taps: Array<{
14
+ x: number;
15
+ y: number;
16
+ t: number;
17
+ }>;
18
+ /** How long each tap indicator stays on screen in seconds. Default 0.6. */
19
+ durationSec?: number;
20
+ /** Half-side of the indicator square in px. Default 40. */
21
+ radius?: number;
22
+ /** Optional override for the ffmpeg binary; defaults to "ffmpeg" on PATH. */
23
+ ffmpeg?: string;
24
+ }
25
+ /** Returns true if `ffmpeg` is on PATH. */
26
+ export declare function hasFfmpeg(binary?: string): Promise<boolean>;
27
+ /**
28
+ * Build the ffmpeg `-vf` filter chain for the given tap events.
29
+ * Pure helper exposed for unit testing.
30
+ */
31
+ export declare function buildOverlayFilter(taps: Array<{
32
+ x: number;
33
+ y: number;
34
+ t: number;
35
+ }>, durationSec?: number, radius?: number): string;
36
+ /**
37
+ * Render `inputPath` → `outputPath` with tap overlays composited at each
38
+ * (x, y, t) coordinate. Throws if ffmpeg fails.
39
+ */
40
+ export declare function applyTapOverlays(options: OverlayOptions): Promise<void>;
41
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/simulator/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,KAAK,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,wBAAgB,SAAS,CAAC,MAAM,SAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAI7D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAChD,WAAW,SAAM,EACjB,MAAM,SAAK,GACV,MAAM,CAoBR;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC7E"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Tap overlay renderer.
3
+ *
4
+ * Takes an mp4 + a list of tap events and renders red rings at each tap
5
+ * position via ffmpeg's drawbox filter. drawbox doesn't draw circles, so
6
+ * we use a hollow square (the standard "tap target" idiom in iOS' built-
7
+ * in "show touches" mode is a circle, but a 4-px-thick red square reads
8
+ * just as well at video scale and avoids needing a generated PNG asset).
9
+ */
10
+ import { execFile, spawn } from 'child_process';
11
+ import { promises as fs } from 'fs';
12
+ /** Returns true if `ffmpeg` is on PATH. */
13
+ export function hasFfmpeg(binary = 'ffmpeg') {
14
+ return new Promise((resolve) => {
15
+ execFile(binary, ['-version'], (err) => resolve(!err));
16
+ });
17
+ }
18
+ /**
19
+ * Build the ffmpeg `-vf` filter chain for the given tap events.
20
+ * Pure helper exposed for unit testing.
21
+ */
22
+ export function buildOverlayFilter(taps, durationSec = 0.6, radius = 40) {
23
+ if (taps.length === 0)
24
+ return '';
25
+ // Two boxes per tap: an outer ring (high-vis) and a smaller solid pulse.
26
+ const segments = [];
27
+ for (const tap of taps) {
28
+ const x = Math.round(tap.x - radius);
29
+ const y = Math.round(tap.y - radius);
30
+ const t0 = tap.t.toFixed(3);
31
+ const t1 = (tap.t + durationSec).toFixed(3);
32
+ // Hollow ring (4-px stroke), bright red, fades by enable window.
33
+ segments.push(`drawbox=x=${x}:y=${y}:w=${radius * 2}:h=${radius * 2}:color=red@0.85:t=4:enable='between(t,${t0},${t1})'`);
34
+ // Inner solid dot (small) so the centre of the tap is unmistakable.
35
+ const innerR = Math.max(4, Math.round(radius / 4));
36
+ segments.push(`drawbox=x=${x + radius - innerR}:y=${y + radius - innerR}:w=${innerR * 2}:h=${innerR * 2}:color=red@0.95:t=fill:enable='between(t,${t0},${t1})'`);
37
+ }
38
+ return segments.join(',');
39
+ }
40
+ /**
41
+ * Render `inputPath` → `outputPath` with tap overlays composited at each
42
+ * (x, y, t) coordinate. Throws if ffmpeg fails.
43
+ */
44
+ export async function applyTapOverlays(options) {
45
+ const ffmpeg = options.ffmpeg ?? 'ffmpeg';
46
+ if (options.taps.length === 0) {
47
+ // No taps to draw — just copy the file unchanged.
48
+ await fs.copyFile(options.inputPath, options.outputPath);
49
+ return;
50
+ }
51
+ const filter = buildOverlayFilter(options.taps, options.durationSec, options.radius);
52
+ return new Promise((resolve, reject) => {
53
+ const proc = spawn(ffmpeg, [
54
+ '-i',
55
+ options.inputPath,
56
+ '-vf',
57
+ filter,
58
+ '-codec:v',
59
+ 'libx264',
60
+ '-preset',
61
+ 'fast',
62
+ '-y',
63
+ options.outputPath,
64
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
65
+ let stderr = '';
66
+ proc.stderr?.on('data', (d) => {
67
+ stderr += d.toString();
68
+ });
69
+ proc.on('error', reject);
70
+ proc.on('close', (code) => {
71
+ if (code === 0)
72
+ resolve();
73
+ else
74
+ reject(new Error(`ffmpeg exited ${code}. stderr tail:\n${stderr.slice(-800)}`));
75
+ });
76
+ });
77
+ }
78
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/simulator/overlay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared types for the asciicast v2 format.
3
+ * Recorder and player both import these so the on-disk schema stays in sync.
4
+ */
5
+ export type CastEventKind = 'o' | 'i';
6
+ export type CastEvent = [time: number, kind: CastEventKind, data: string];
7
+ export interface CastHeader {
8
+ version: 2;
9
+ width: number;
10
+ height: number;
11
+ timestamp?: number;
12
+ duration?: number;
13
+ title?: string;
14
+ env?: Record<string, string>;
15
+ }
16
+ /**
17
+ * Escape a JSON string for safe interpolation inside an HTML <script> block.
18
+ * JSON.stringify alone does NOT escape `</script>`, `<!--`, or U+2028/U+2029,
19
+ * so attacker-controlled bytes inside event data could break out of the
20
+ * script element and execute. The .html player is designed for sharing in
21
+ * PR comments, so this matters.
22
+ */
23
+ export declare function escapeJsonForScript(json: string): string;
24
+ //# sourceMappingURL=cast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../../src/term/cast.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,CAAC;AAEtC,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAKD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASxD"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared types for the asciicast v2 format.
3
+ * Recorder and player both import these so the on-disk schema stays in sync.
4
+ */
5
+ const LINE_SEP = String.fromCharCode(0x2028);
6
+ const PARA_SEP = String.fromCharCode(0x2029);
7
+ /**
8
+ * Escape a JSON string for safe interpolation inside an HTML <script> block.
9
+ * JSON.stringify alone does NOT escape `</script>`, `<!--`, or U+2028/U+2029,
10
+ * so attacker-controlled bytes inside event data could break out of the
11
+ * script element and execute. The .html player is designed for sharing in
12
+ * PR comments, so this matters.
13
+ */
14
+ export function escapeJsonForScript(json) {
15
+ return json
16
+ .replace(/</g, '\\u003c')
17
+ .replace(/>/g, '\\u003e')
18
+ .replace(/&/g, '\\u0026')
19
+ .split(LINE_SEP)
20
+ .join('\\u2028')
21
+ .split(PARA_SEP)
22
+ .join('\\u2029');
23
+ }
24
+ //# sourceMappingURL=cast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.js","sourceRoot":"","sources":["../../src/term/cast.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,KAAK,CAAC,QAAQ,CAAC;SACf,IAAI,CAAC,SAAS,CAAC;SACf,KAAK,CAAC,QAAQ,CAAC;SACf,IAAI,CAAC,SAAS,CAAC,CAAC;AACrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/term/player.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAsB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAgO5E"}
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/term/player.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAiQ5E"}