@ytspar/sweetlink 1.18.0 → 1.19.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.
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Self-contained HTML player for asciicast v2 recordings.
3
+ *
4
+ * Inlines the cast events as JSON in the document — the player has no
5
+ * runtime dependencies, works offline, opens anywhere a browser does.
6
+ *
7
+ * UX: play/pause, 0.1×–4× speed control, seek bar, ANSI colour rendering.
8
+ * Carriage returns (\r) reposition the cursor to column 0 of the current
9
+ * line so progress bars overwrite cleanly. Form-feeds (\f) and bare
10
+ * cursor escapes are dropped — this is a flat scrollback, not a TUI grid.
11
+ *
12
+ * Rendering uses createElement + textContent (NOT innerHTML) — every
13
+ * character from the recorded stream lands as a text node, never as
14
+ * markup. Inline style attrs are built from a hardcoded colour palette
15
+ * with no user input.
16
+ */
17
+ import { promises as fs } from 'fs';
18
+ import * as path from 'path';
19
+ function escapeAttr(s) {
20
+ return s
21
+ .replace(/&/g, '&')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;')
24
+ .replace(/"/g, '&quot;');
25
+ }
26
+ export async function generatePlayer(options) {
27
+ const cast = await fs.readFile(options.castPath, 'utf-8');
28
+ const lines = cast.split(/\r?\n/).filter(Boolean);
29
+ if (lines.length === 0)
30
+ throw new Error(`Empty .cast file: ${options.castPath}`);
31
+ const header = JSON.parse(lines[0]);
32
+ const events = [];
33
+ for (let i = 1; i < lines.length; i++) {
34
+ try {
35
+ const e = JSON.parse(lines[i]);
36
+ if (Array.isArray(e) && e.length === 3)
37
+ events.push(e);
38
+ }
39
+ catch { /* skip malformed lines */ }
40
+ }
41
+ const totalDuration = header.duration
42
+ ?? (events.length ? events[events.length - 1][0] : 0);
43
+ const titleText = options.title ?? header.title ?? path.basename(options.castPath);
44
+ const outputPath = options.outputPath
45
+ ?? options.castPath.replace(/\.cast$/i, '') + '.html';
46
+ // Embed the events as an inline JSON string. The player JS reads it on
47
+ // load and renders incrementally with `setTimeout` driven by the speed
48
+ // slider — no live network deps, no asciinema runtime.
49
+ const dataJson = JSON.stringify({ width: header.width, height: header.height, events });
50
+ const html = `<!DOCTYPE html>
51
+ <html lang="en">
52
+ <head>
53
+ <meta charset="utf-8">
54
+ <title>${escapeAttr(titleText)}</title>
55
+ <style>
56
+ :root {
57
+ --bg: #1a1b26; --fg: #c0caf5; --muted: #565f89; --accent: #7aa2f7;
58
+ --panel: #16161e; --border: #2f334d;
59
+ }
60
+ * { box-sizing: border-box; }
61
+ body { margin: 0; background: var(--bg); color: var(--fg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
62
+ header { padding: 12px 16px; background: var(--panel); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
63
+ header h1 { font-size: 13px; font-weight: 500; margin: 0; color: var(--fg); flex: 1 1 auto; min-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
64
+ button { background: var(--bg); color: var(--fg); border: 1px solid var(--border); padding: 4px 12px; border-radius: 4px; font: inherit; font-size: 12px; cursor: pointer; }
65
+ button:hover { background: var(--border); }
66
+ button.primary { background: var(--accent); color: #16161e; border-color: var(--accent); font-weight: 600; }
67
+ .speed { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
68
+ .speed select { background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; padding: 3px 6px; font: inherit; font-size: 12px; }
69
+ .time { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 90px; text-align: right; }
70
+ main { padding: 16px; }
71
+ pre { margin: 0; padding: 16px; background: #0d0e14; border-radius: 6px; overflow: auto; line-height: 1.4; font-size: 13px; min-height: 480px; max-height: calc(100vh - 200px); white-space: pre-wrap; word-break: break-word; }
72
+ .scrubber { padding: 8px 16px; background: var(--panel); border-top: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
73
+ input[type=range] { flex: 1; }
74
+ .label { background: var(--accent); color: #16161e; padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <header>
79
+ <span class="label">term</span>
80
+ <h1>${escapeAttr(titleText)}</h1>
81
+ <div class="speed">
82
+ <span>speed</span>
83
+ <select id="speed">
84
+ <option value="0.1">0.1×</option>
85
+ <option value="0.25">0.25×</option>
86
+ <option value="0.5">0.5×</option>
87
+ <option value="1" selected>1×</option>
88
+ <option value="2">2×</option>
89
+ <option value="4">4×</option>
90
+ </select>
91
+ </div>
92
+ <button id="play" class="primary">▶ play</button>
93
+ <button id="restart">⟲ restart</button>
94
+ <span class="time"><span id="t">0.0</span>s / ${totalDuration.toFixed(1)}s</span>
95
+ </header>
96
+ <main>
97
+ <pre id="screen"></pre>
98
+ </main>
99
+ <div class="scrubber">
100
+ <input id="seek" type="range" min="0" max="${totalDuration}" step="0.01" value="0" />
101
+ </div>
102
+
103
+ <script>
104
+ const DATA = ${dataJson};
105
+ const TOTAL = ${totalDuration};
106
+
107
+ // --- ANSI parser (mirrors src/term/ansi.ts; minimal SGR + \\r) ---
108
+ const BASIC_FG = {30:'#000',31:'#cd3131',32:'#0dbc79',33:'#e5e510',34:'#2472c8',35:'#bc3fbc',36:'#11a8cd',37:'#e5e5e5',90:'#666',91:'#f14c4c',92:'#23d18b',93:'#f5f543',94:'#3b8eea',95:'#d670d6',96:'#29b8db',97:'#fff'};
109
+ const BASIC_BG = {40:'#000',41:'#cd3131',42:'#0dbc79',43:'#e5e510',44:'#2472c8',45:'#bc3fbc',46:'#11a8cd',47:'#e5e5e5',100:'#666',101:'#f14c4c',102:'#23d18b',103:'#f5f543',104:'#3b8eea',105:'#d670d6',106:'#29b8db',107:'#fff'};
110
+ function c256(n){if(n<16){return ['#000','#cd3131','#0dbc79','#e5e510','#2472c8','#bc3fbc','#11a8cd','#e5e5e5','#666','#f14c4c','#23d18b','#f5f543','#3b8eea','#d670d6','#29b8db','#fff'][n];}if(n<232){const i=n-16;return 'rgb('+(Math.floor(i/36)*51)+','+(Math.floor((i%36)/6)*51)+','+((i%6)*51)+')';}const v=(n-232)*10+8;return 'rgb('+v+','+v+','+v+')';}
111
+ function freshState(){return{fg:null,bg:null,bold:false,italic:false,underline:false,dim:false};}
112
+ function applyParams(s,p){for(let i=0;i<p.length;i++){const v=p[i];if(v===0)Object.assign(s,freshState());else if(v===1)s.bold=true;else if(v===2)s.dim=true;else if(v===3)s.italic=true;else if(v===4)s.underline=true;else if(v===22){s.bold=false;s.dim=false;}else if(v===23)s.italic=false;else if(v===24)s.underline=false;else if(v===39)s.fg=null;else if(v===49)s.bg=null;else if(BASIC_FG[v])s.fg=BASIC_FG[v];else if(BASIC_BG[v])s.bg=BASIC_BG[v];else if(v===38||v===48){const fg=v===38;const m=p[i+1];if(m===5&&p[i+2]!==undefined){const c=c256(p[i+2]);if(fg)s.fg=c;else s.bg=c;i+=2;}else if(m===2&&p[i+4]!==undefined){const c='rgb('+p[i+2]+','+p[i+3]+','+p[i+4]+')';if(fg)s.fg=c;else s.bg=c;i+=4;}}}}
113
+ function styleOf(s){const o=[];if(s.fg)o.push('color:'+s.fg);if(s.bg)o.push('background:'+s.bg);if(s.bold)o.push('font-weight:600');if(s.italic)o.push('font-style:italic');if(s.underline)o.push('text-decoration:underline');if(s.dim)o.push('opacity:0.7');return o.join(';');}
114
+
115
+ // --- Build the assembled scrollback up to time t ---
116
+ function assembleText(targetT) {
117
+ // Walk through events, building text. Treat \\r as "delete back to last \\n",
118
+ // so progress bars (e.g., "5%\\r10%\\r") overwrite cleanly.
119
+ let text = '';
120
+ for (const e of DATA.events) {
121
+ if (e[0] > targetT) break;
122
+ if (e[1] !== 'o') continue;
123
+ const chunk = e[2];
124
+ for (let i = 0; i < chunk.length; i++) {
125
+ const c = chunk[i];
126
+ if (c === '\\r' && chunk[i+1] !== '\\n') {
127
+ const lastNl = text.lastIndexOf('\\n');
128
+ text = lastNl === -1 ? '' : text.slice(0, lastNl + 1);
129
+ } else if (c === '\\r') {
130
+ // CRLF — let the LF do the work.
131
+ } else {
132
+ text += c;
133
+ }
134
+ }
135
+ }
136
+ return text;
137
+ }
138
+
139
+ // --- Parse + render: build segments, then DOM-construct safely with textContent ---
140
+ function parseSegments(text) {
141
+ const segments = [];
142
+ const state = freshState();
143
+ let buf = '';
144
+ const flush = () => { if (buf) { segments.push({ text: buf, style: styleOf(state) }); buf = ''; } };
145
+ let i = 0;
146
+ while (i < text.length) {
147
+ const ch = text[i];
148
+ if (ch === '\\u001b' && text[i+1] === '[') {
149
+ flush();
150
+ let j = i + 2;
151
+ while (j < text.length && !/[A-Za-z]/.test(text[j])) j++;
152
+ const final = text[j];
153
+ const body = text.slice(i + 2, j);
154
+ if (final === 'm') {
155
+ const params = body.split(';').map((x) => parseInt(x, 10) || 0);
156
+ applyParams(state, params.length === 0 ? [0] : params);
157
+ }
158
+ i = j + 1;
159
+ } else {
160
+ buf += ch;
161
+ i++;
162
+ }
163
+ }
164
+ flush();
165
+ return segments;
166
+ }
167
+
168
+ function renderUpTo(targetT) {
169
+ const text = assembleText(targetT);
170
+ const segments = parseSegments(text);
171
+ const screen = document.getElementById('screen');
172
+ // Clear and rebuild via DOM nodes so every byte from the cast lands as
173
+ // text content, not parsed markup. Spans only carry our hardcoded
174
+ // inline-style palette — no user input flows into attributes.
175
+ screen.textContent = '';
176
+ for (const seg of segments) {
177
+ if (seg.style) {
178
+ const span = document.createElement('span');
179
+ span.setAttribute('style', seg.style);
180
+ span.textContent = seg.text;
181
+ screen.appendChild(span);
182
+ } else {
183
+ screen.appendChild(document.createTextNode(seg.text));
184
+ }
185
+ }
186
+ }
187
+
188
+ // --- Playback controller ---
189
+ let currentT = 0;
190
+ let playing = false;
191
+ let speed = 1;
192
+ let lastFrame = 0;
193
+ function tick(ts) {
194
+ if (!playing) return;
195
+ if (lastFrame) {
196
+ const dt = (ts - lastFrame) / 1000;
197
+ currentT = Math.min(TOTAL, currentT + dt * speed);
198
+ document.getElementById('seek').value = currentT;
199
+ document.getElementById('t').textContent = currentT.toFixed(1);
200
+ renderUpTo(currentT);
201
+ if (currentT >= TOTAL) {
202
+ playing = false;
203
+ document.getElementById('play').textContent = '▶ play';
204
+ }
205
+ }
206
+ lastFrame = ts;
207
+ if (playing) requestAnimationFrame(tick);
208
+ }
209
+ function play() {
210
+ if (currentT >= TOTAL) currentT = 0;
211
+ playing = true;
212
+ lastFrame = 0;
213
+ document.getElementById('play').textContent = '❚❚ pause';
214
+ requestAnimationFrame(tick);
215
+ }
216
+ function pause() {
217
+ playing = false;
218
+ document.getElementById('play').textContent = '▶ play';
219
+ }
220
+ document.getElementById('play').addEventListener('click', () => playing ? pause() : play());
221
+ document.getElementById('restart').addEventListener('click', () => { currentT = 0; renderUpTo(0); document.getElementById('seek').value = 0; document.getElementById('t').textContent = '0.0'; if (!playing) play(); });
222
+ document.getElementById('seek').addEventListener('input', (e) => { pause(); currentT = parseFloat(e.target.value); document.getElementById('t').textContent = currentT.toFixed(1); renderUpTo(currentT); });
223
+ document.getElementById('speed').addEventListener('change', (e) => { speed = parseFloat(e.target.value); });
224
+
225
+ // --- Pick a sensible default speed for long recordings ---
226
+ if (TOTAL > 180) {
227
+ document.getElementById('speed').value = '4';
228
+ speed = 4;
229
+ } else if (TOTAL > 60) {
230
+ document.getElementById('speed').value = '2';
231
+ speed = 2;
232
+ }
233
+
234
+ // Render a small head end so the user sees something even before play.
235
+ renderUpTo(Math.min(0.5, TOTAL));
236
+ </script>
237
+ </body>
238
+ </html>
239
+ `;
240
+ await fs.writeFile(outputPath, html);
241
+ return outputPath;
242
+ }
243
+ //# sourceMappingURL=player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.js","sourceRoot":"","sources":["../../src/term/player.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Terminal Recorder
3
+ *
4
+ * Spawns a child process and captures stdout/stderr chunks with timestamps,
5
+ * emitting an asciicast v2 file (https://docs.asciinema.org/manual/asciicast/v2/).
6
+ *
7
+ * Notes on PTY-vs-pipe: we don't ship a native node-pty dep, so we set
8
+ * `FORCE_COLOR`, `TERM=xterm-256color`, `CI=` env vars to coax most CLIs
9
+ * into emitting ANSI colour. Programs that strictly require a TTY (less,
10
+ * top, vim) won't render correctly — that's a documented limitation.
11
+ */
12
+ export interface TerminalCaptureOptions {
13
+ command: string;
14
+ /** Output path for the .cast file. The .html player is written next to it. */
15
+ output: string;
16
+ /** Shell to run the command in. Defaults to /bin/sh. */
17
+ shell?: string;
18
+ cols?: number;
19
+ rows?: number;
20
+ cwd?: string;
21
+ env?: Record<string, string>;
22
+ /** Optional human-readable label embedded in the .cast title field. */
23
+ label?: string;
24
+ }
25
+ export interface TerminalCaptureResult {
26
+ durationSec: number;
27
+ bytes: number;
28
+ exitCode: number;
29
+ events: number;
30
+ castPath: string;
31
+ }
32
+ export declare function captureTerminal(options: TerminalCaptureOptions): Promise<TerminalCaptureResult>;
33
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/term/recorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAiEhC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Terminal Recorder
3
+ *
4
+ * Spawns a child process and captures stdout/stderr chunks with timestamps,
5
+ * emitting an asciicast v2 file (https://docs.asciinema.org/manual/asciicast/v2/).
6
+ *
7
+ * Notes on PTY-vs-pipe: we don't ship a native node-pty dep, so we set
8
+ * `FORCE_COLOR`, `TERM=xterm-256color`, `CI=` env vars to coax most CLIs
9
+ * into emitting ANSI colour. Programs that strictly require a TTY (less,
10
+ * top, vim) won't render correctly — that's a documented limitation.
11
+ */
12
+ import { spawn } from 'child_process';
13
+ import { promises as fs } from 'fs';
14
+ export async function captureTerminal(options) {
15
+ const startTime = Date.now();
16
+ const events = [];
17
+ const cols = options.cols ?? 120;
18
+ const rows = options.rows ?? 30;
19
+ // Coax common CLIs into emitting ANSI colour even though stdout isn't
20
+ // a TTY. We don't drop a real PTY in front of them — the trade-off is
21
+ // simpler installation (no native build) at the cost of TUI fidelity.
22
+ const env = {
23
+ ...process.env,
24
+ ...options.env,
25
+ FORCE_COLOR: '3',
26
+ CLICOLOR_FORCE: '1',
27
+ TERM: 'xterm-256color',
28
+ COLUMNS: String(cols),
29
+ LINES: String(rows),
30
+ };
31
+ return new Promise((resolve, reject) => {
32
+ const child = spawn(options.shell ?? '/bin/sh', ['-c', options.command], {
33
+ cwd: options.cwd,
34
+ env,
35
+ stdio: ['ignore', 'pipe', 'pipe'],
36
+ });
37
+ let totalBytes = 0;
38
+ const handleData = (data) => {
39
+ const t = (Date.now() - startTime) / 1000;
40
+ const text = data.toString('utf-8');
41
+ events.push([t, 'o', text]);
42
+ totalBytes += data.length;
43
+ };
44
+ child.stdout?.on('data', handleData);
45
+ child.stderr?.on('data', handleData);
46
+ child.on('error', reject);
47
+ child.on('close', async (code) => {
48
+ try {
49
+ const durationSec = (Date.now() - startTime) / 1000;
50
+ const header = {
51
+ version: 2,
52
+ width: cols,
53
+ height: rows,
54
+ timestamp: Math.floor(startTime / 1000),
55
+ duration: durationSec,
56
+ title: options.label ?? options.command.slice(0, 100),
57
+ env: { TERM: 'xterm-256color', SHELL: options.shell ?? '/bin/sh' },
58
+ };
59
+ const lines = [JSON.stringify(header)];
60
+ for (const e of events)
61
+ lines.push(JSON.stringify(e));
62
+ await fs.writeFile(options.output, lines.join('\n') + '\n');
63
+ resolve({
64
+ durationSec,
65
+ bytes: totalBytes,
66
+ exitCode: code ?? 0,
67
+ events: events.length,
68
+ castPath: options.output,
69
+ });
70
+ }
71
+ catch (err) {
72
+ reject(err);
73
+ }
74
+ });
75
+ });
76
+ }
77
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/term/recorder.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ytspar/sweetlink",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Autonomous development toolkit for AI agents - screenshots, DOM queries, console logs, and JavaScript execution via WebSocket and Chrome DevTools Protocol",
5
5
  "keywords": [
6
6
  "autonomous-development",