repoburg 1.3.255 → 1.3.256

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 (75) hide show
  1. package/daemon/dist/index.js +61 -3
  2. package/daemon/dist/staticServing.js +2 -2
  3. package/daemon/dist/tabtape/api/follow.js +174 -0
  4. package/daemon/dist/tabtape/api/http.js +221 -0
  5. package/daemon/dist/tabtape/api/record.js +149 -0
  6. package/daemon/dist/tabtape/api/router.js +519 -0
  7. package/daemon/dist/tabtape/api/router.spec.js +604 -0
  8. package/daemon/dist/tabtape/api/static.js +70 -0
  9. package/daemon/dist/tabtape/api/ws.js +166 -0
  10. package/daemon/dist/tabtape/buffer/buffer.js +347 -0
  11. package/daemon/dist/tabtape/buffer/buffer.spec.js +137 -0
  12. package/daemon/dist/tabtape/config.js +108 -0
  13. package/daemon/dist/tabtape/e2e-server.js +96 -0
  14. package/daemon/dist/tabtape/errors.js +77 -0
  15. package/daemon/dist/tabtape/export/export.js +331 -0
  16. package/daemon/dist/tabtape/export/export.spec.js +262 -0
  17. package/daemon/dist/tabtape/har/gostd.js +395 -0
  18. package/daemon/dist/tabtape/har/gostd.spec.js +139 -0
  19. package/daemon/dist/tabtape/har/har.js +582 -0
  20. package/daemon/dist/tabtape/har/har.spec.js +325 -0
  21. package/daemon/dist/tabtape/index.js +134 -0
  22. package/daemon/dist/tabtape/index.spec.js +104 -0
  23. package/daemon/dist/tabtape/lock.js +67 -0
  24. package/daemon/dist/tabtape/log.js +32 -0
  25. package/daemon/dist/tabtape/mcp/definitions.js +362 -0
  26. package/daemon/dist/tabtape/mcp/gofmt.js +227 -0
  27. package/daemon/dist/tabtape/mcp/params.js +345 -0
  28. package/daemon/dist/tabtape/mcp/params.spec.js +172 -0
  29. package/daemon/dist/tabtape/mcp/recordLink.js +122 -0
  30. package/daemon/dist/tabtape/mcp/server.js +411 -0
  31. package/daemon/dist/tabtape/mcp/server.spec.js +722 -0
  32. package/daemon/dist/tabtape/mcp/tools.js +1085 -0
  33. package/daemon/dist/tabtape/query/format.js +195 -0
  34. package/daemon/dist/tabtape/query/network.js +133 -0
  35. package/daemon/dist/tabtape/query/perf.js +93 -0
  36. package/daemon/dist/tabtape/query/query.spec.js +233 -0
  37. package/daemon/dist/tabtape/query/reader.js +602 -0
  38. package/daemon/dist/tabtape/query/types.js +274 -0
  39. package/daemon/dist/tabtape/redact.js +37 -0
  40. package/daemon/dist/tabtape/repro/lang.js +95 -0
  41. package/daemon/dist/tabtape/repro/repro.js +422 -0
  42. package/daemon/dist/tabtape/repro/repro.spec.js +210 -0
  43. package/daemon/dist/tabtape/sink.js +24 -0
  44. package/daemon/dist/tabtape/store/record.js +99 -0
  45. package/daemon/dist/tabtape/store/segment.js +267 -0
  46. package/daemon/dist/tabtape/store/store.js +846 -0
  47. package/daemon/dist/tabtape/store/store.spec.js +485 -0
  48. package/daemon/dist/tabtape/testing/querytest.js +119 -0
  49. package/daemon/dist/tabtape/testing/tmp.js +33 -0
  50. package/package.json +3 -2
  51. package/tabtape/extension/dist/background.js +8 -0
  52. package/tabtape/extension/dist/background.js.map +7 -0
  53. package/tabtape/extension/dist/bridge.js +3 -0
  54. package/tabtape/extension/dist/bridge.js.map +7 -0
  55. package/tabtape/extension/dist/icons/128.png +0 -0
  56. package/tabtape/extension/dist/icons/16.png +0 -0
  57. package/tabtape/extension/dist/icons/32.png +0 -0
  58. package/tabtape/extension/dist/icons/48.png +0 -0
  59. package/tabtape/extension/dist/manifest.json +28 -0
  60. package/tabtape/extension/dist/popup.css +62 -0
  61. package/tabtape/extension/dist/popup.html +34 -0
  62. package/tabtape/extension/dist/popup.js +4 -0
  63. package/tabtape/extension/dist/popup.js.map +7 -0
  64. package/tabtape/extension/dist/recorder.js +133 -0
  65. package/tabtape/extension/dist/recorder.js.map +1 -0
  66. package/tabtape/extension/dist/state.js +2 -0
  67. package/tabtape/extension/dist/state.js.map +1 -0
  68. package/tabtape/playwright/mcp.mjs +58 -0
  69. package/tabtape/playwright/tabtape.d.mts +37 -0
  70. package/tabtape/playwright/tabtape.mjs +146 -0
  71. package/tabtape/ui/dist/app/assets/index-Canv3Ljk.css +1 -0
  72. package/tabtape/ui/dist/app/assets/index-iWNSSrAE.js +145 -0
  73. package/tabtape/ui/dist/app/index.html +14 -0
  74. package/tabtape/ui/dist/export/viewer.css +2 -0
  75. package/tabtape/ui/dist/export/viewer.js +143 -0
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RRWEB_META = exports.RRWEB_FULL_SNAPSHOT = void 0;
4
+ exports.rrwebTypeOf = rrwebTypeOf;
5
+ exports.parseRecord = parseRecord;
6
+ exports.readRecords = readRecords;
7
+ const errors_1 = require("../errors");
8
+ /** rrweb's EventType.FullSnapshot. */
9
+ exports.RRWEB_FULL_SNAPSHOT = 2;
10
+ /** rrweb's EventType.Meta. */
11
+ exports.RRWEB_META = 4;
12
+ /** The rrweb event type of an rrweb record (2 = full snapshot, 4 = meta), or 0. */
13
+ function rrwebTypeOf(r) {
14
+ return r.type === 'rrweb' ? r.sum.rrwebType : 0;
15
+ }
16
+ function isPlainObject(v) {
17
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
18
+ }
19
+ /** Go's math.Round: halves away from zero. */
20
+ function roundHalfAway(n) {
21
+ return Math.sign(n) * Math.round(Math.abs(n));
22
+ }
23
+ // Non-strings and non-numbers deliberately count as absent.
24
+ const asString = (v) => (typeof v === 'string' ? v : '');
25
+ const asInt = (v) => (typeof v === 'number' && Number.isFinite(v) ? Math.trunc(v) : 0);
26
+ /**
27
+ * Extracts index fields from a record's data. Best effort: data that does not
28
+ * match the expected shape is still stored, it just does not contribute to
29
+ * the counters.
30
+ */
31
+ function summarize(data) {
32
+ if (!isPlainObject(data)) {
33
+ return { rrwebType: 0, name: '', url: '', title: '', level: '', status: 0, err: '', userId: '', kind: '', rating: '' };
34
+ }
35
+ return {
36
+ rrwebType: asInt(data.type),
37
+ name: asString(data.name),
38
+ url: asString(data.url),
39
+ title: asString(data.title),
40
+ level: asString(data.level),
41
+ status: asInt(data.status),
42
+ err: asString(data.error),
43
+ userId: asString(data.userId),
44
+ kind: asString(data.kind),
45
+ rating: asString(data.rating),
46
+ };
47
+ }
48
+ /** Validates a single NDJSON line. */
49
+ function parseRecord(input) {
50
+ const line = (typeof input === 'string' ? input : input.toString('utf8')).trim();
51
+ if (line === '' || line[0] !== '{')
52
+ throw new errors_1.RecordError('record must be a JSON object');
53
+ let v;
54
+ try {
55
+ v = JSON.parse(line);
56
+ }
57
+ catch (err) {
58
+ throw new errors_1.RecordError(`invalid JSON: ${err.message}`);
59
+ }
60
+ if (!isPlainObject(v))
61
+ throw new errors_1.RecordError('record must be a JSON object');
62
+ if (v.ts === undefined || v.ts === null)
63
+ throw new errors_1.RecordError('record has no "ts"');
64
+ if (typeof v.ts !== 'number' || !Number.isFinite(v.ts))
65
+ throw new errors_1.RecordError('decode record: "ts" must be a number');
66
+ if (v.type !== undefined && v.type !== null && typeof v.type !== 'string') {
67
+ throw new errors_1.RecordError('decode record: "type" must be a string');
68
+ }
69
+ if (!v.type)
70
+ throw new errors_1.RecordError('record has no "type"');
71
+ if (Object.prototype.hasOwnProperty.call(v, 'sid'))
72
+ throw new errors_1.RecordError('"sid" is set by the server');
73
+ return {
74
+ ts: roundHalfAway(v.ts),
75
+ type: v.type,
76
+ raw: JSON.stringify(v),
77
+ sum: summarize(v.data),
78
+ };
79
+ }
80
+ /**
81
+ * Parses an NDJSON body. Blank lines are ignored; any invalid line rejects
82
+ * the whole batch.
83
+ */
84
+ function readRecords(body) {
85
+ const text = typeof body === 'string' ? body : body.toString('utf8');
86
+ const recs = [];
87
+ const lines = text.split('\n');
88
+ for (let i = 0; i < lines.length; i++) {
89
+ if (lines[i].trim() === '')
90
+ continue;
91
+ try {
92
+ recs.push(parseRecord(lines[i]));
93
+ }
94
+ catch (err) {
95
+ throw new errors_1.RecordError(`line ${i + 1}: ${err.message}`);
96
+ }
97
+ }
98
+ return recs;
99
+ }
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AbortedError = exports.TMP_EXT = exports.GZ_EXT = exports.SEG_EXT = void 0;
7
+ exports.segmentName = segmentName;
8
+ exports.parseSegmentName = parseSegmentName;
9
+ exports.readSegment = readSegment;
10
+ exports.readSegmentBytes = readSegmentBytes;
11
+ exports.gzipSizes = gzipSizes;
12
+ exports.compressFile = compressFile;
13
+ exports.syncDir = syncDir;
14
+ exports.removeQuiet = removeQuiet;
15
+ exports.repairTail = repairTail;
16
+ /**
17
+ * Segment files. A segment is written as plain JSONL; once closed it may be
18
+ * compressed to <seq>.jsonl.gz through <seq>.jsonl.gz.tmp. Readers address
19
+ * data by uncompressed offsets, so both forms read the same.
20
+ */
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const promises_1 = __importDefault(require("fs/promises"));
23
+ const stream_1 = require("stream");
24
+ const promises_2 = require("stream/promises");
25
+ const zlib_1 = __importDefault(require("zlib"));
26
+ const errors_1 = require("../errors");
27
+ exports.SEG_EXT = '.jsonl';
28
+ exports.GZ_EXT = '.gz';
29
+ exports.TMP_EXT = '.tmp';
30
+ const READ_CHUNK = 256 * 1024;
31
+ function segmentName(seq) {
32
+ return String(seq).padStart(16, '0') + exports.SEG_EXT;
33
+ }
34
+ /** Recognizes "<seq>.jsonl", "<seq>.jsonl.gz" and "<seq>.jsonl.gz.tmp". */
35
+ function parseSegmentName(name) {
36
+ let base;
37
+ let kind;
38
+ if (name.endsWith(exports.SEG_EXT + exports.GZ_EXT + exports.TMP_EXT)) {
39
+ base = name.slice(0, -(exports.SEG_EXT + exports.GZ_EXT + exports.TMP_EXT).length);
40
+ kind = 'temp';
41
+ }
42
+ else if (name.endsWith(exports.SEG_EXT + exports.GZ_EXT)) {
43
+ base = name.slice(0, -(exports.SEG_EXT + exports.GZ_EXT).length);
44
+ kind = 'gzip';
45
+ }
46
+ else if (name.endsWith(exports.SEG_EXT)) {
47
+ base = name.slice(0, -exports.SEG_EXT.length);
48
+ kind = 'plain';
49
+ }
50
+ else {
51
+ return null;
52
+ }
53
+ if (!/^\d+$/.test(base))
54
+ return null;
55
+ const seq = Number(base);
56
+ return Number.isSafeInteger(seq) ? { seq, kind } : null;
57
+ }
58
+ /**
59
+ * Reads up to `limit` uncompressed bytes of a segment, starting at the
60
+ * uncompressed offset `off`. `path` is a segment path as reported by a store
61
+ * snapshot: a segment compressed since then is found under its new name,
62
+ * because the .gz file is renamed into place before the plain file is
63
+ * removed. A reader that already opened the plain file keeps reading it.
64
+ * Throws an ENOENT error (see isNotExist) when the segment was deleted.
65
+ */
66
+ async function* readSegment(path, off, limit) {
67
+ if (limit <= 0)
68
+ return;
69
+ if (!path.endsWith(exports.GZ_EXT)) {
70
+ let fh = null;
71
+ try {
72
+ fh = await promises_1.default.open(path, 'r');
73
+ }
74
+ catch (err) {
75
+ if (!(0, errors_1.isNotExist)(err))
76
+ throw err;
77
+ path += exports.GZ_EXT;
78
+ }
79
+ if (fh) {
80
+ try {
81
+ let pos = off;
82
+ let left = limit;
83
+ while (left > 0) {
84
+ const n = Math.min(left, READ_CHUNK);
85
+ const buf = Buffer.allocUnsafe(n);
86
+ const { bytesRead } = await fh.read(buf, 0, n, pos);
87
+ if (bytesRead === 0)
88
+ break;
89
+ pos += bytesRead;
90
+ left -= bytesRead;
91
+ yield bytesRead === n ? buf : buf.subarray(0, bytesRead);
92
+ }
93
+ }
94
+ finally {
95
+ await fh.close();
96
+ }
97
+ return;
98
+ }
99
+ }
100
+ yield* readGzip(path, off, limit);
101
+ }
102
+ /** Opens a read stream, rejecting (ENOENT: deleted) before any data flows. */
103
+ function openReadStream(path, opts) {
104
+ return new Promise((resolve, reject) => {
105
+ const s = fs_1.default.createReadStream(path, opts);
106
+ const onError = (err) => reject(err);
107
+ s.once('error', onError);
108
+ s.once('open', () => {
109
+ s.off('error', onError);
110
+ resolve(s);
111
+ });
112
+ });
113
+ }
114
+ async function* readGzip(path, off, limit) {
115
+ const src = await openReadStream(path, { highWaterMark: 64 * 1024 });
116
+ const gunzip = zlib_1.default.createGunzip();
117
+ src.on('error', (err) => gunzip.destroy(err));
118
+ src.pipe(gunzip);
119
+ let skip = off;
120
+ let left = limit;
121
+ try {
122
+ for await (const piece of gunzip) {
123
+ let chunk = piece;
124
+ if (skip > 0) {
125
+ if (chunk.length <= skip) {
126
+ skip -= chunk.length;
127
+ continue;
128
+ }
129
+ chunk = chunk.subarray(skip);
130
+ skip = 0;
131
+ }
132
+ if (chunk.length > left)
133
+ chunk = chunk.subarray(0, left);
134
+ left -= chunk.length;
135
+ yield chunk;
136
+ if (left <= 0)
137
+ break;
138
+ }
139
+ }
140
+ finally {
141
+ src.destroy();
142
+ gunzip.destroy();
143
+ }
144
+ }
145
+ /** The first `limit` uncompressed bytes of a segment. */
146
+ async function readSegmentBytes(path, limit) {
147
+ const parts = [];
148
+ for await (const chunk of readSegment(path, 0, limit))
149
+ parts.push(chunk);
150
+ return Buffer.concat(parts);
151
+ }
152
+ /**
153
+ * A compressed segment's size on disk and its uncompressed size, read from
154
+ * the gzip trailer (ISIZE, the size modulo 2^32; segments are far smaller).
155
+ */
156
+ function gzipSizes(path) {
157
+ const fd = fs_1.default.openSync(path, 'r');
158
+ try {
159
+ const disk = fs_1.default.fstatSync(fd).size;
160
+ const MIN_GZIP = 18; // 10-byte header + 8-byte trailer
161
+ if (disk < MIN_GZIP)
162
+ throw new Error('not a gzip file: too short');
163
+ const head = Buffer.alloc(2);
164
+ fs_1.default.readSync(fd, head, 0, 2, 0);
165
+ if (head[0] !== 0x1f || head[1] !== 0x8b)
166
+ throw new Error('not a gzip file');
167
+ const tail = Buffer.alloc(4);
168
+ fs_1.default.readSync(fd, tail, 0, 4, disk - 4);
169
+ return { disk, raw: tail.readUInt32LE(0) };
170
+ }
171
+ finally {
172
+ fs_1.default.closeSync(fd);
173
+ }
174
+ }
175
+ /** Stops a compression whose segment was deleted or whose store is closing. */
176
+ class AbortedError extends Error {
177
+ constructor() {
178
+ super('compression aborted');
179
+ this.name = 'AbortedError';
180
+ }
181
+ }
182
+ exports.AbortedError = AbortedError;
183
+ /**
184
+ * Writes the first `size` bytes of src to dst as gzip, syncs dst to disk and
185
+ * returns its size. `stop` is checked before every chunk.
186
+ */
187
+ async function compressFile(src, dst, size, stop) {
188
+ const reader = size > 0 ? await openReadStream(src, { start: 0, end: size - 1, highWaterMark: READ_CHUNK }) : stream_1.Readable.from([]);
189
+ let copied = 0;
190
+ const guard = new stream_1.Transform({
191
+ transform(chunk, _enc, cb) {
192
+ if (stop()) {
193
+ cb(new AbortedError());
194
+ return;
195
+ }
196
+ copied += chunk.length;
197
+ cb(null, chunk);
198
+ },
199
+ });
200
+ await (0, promises_2.pipeline)(reader, guard, zlib_1.default.createGzip(), fs_1.default.createWriteStream(dst, { mode: 0o644 }));
201
+ if (copied !== size)
202
+ throw new Error(`segment is ${copied} bytes, expected ${size}`);
203
+ const out = await promises_1.default.open(dst, 'r+');
204
+ try {
205
+ await out.sync();
206
+ return (await out.stat()).size;
207
+ }
208
+ finally {
209
+ await out.close();
210
+ }
211
+ }
212
+ /** Makes a rename in dir durable. Best effort: not every platform can sync a directory. */
213
+ function syncDir(dir) {
214
+ try {
215
+ const fd = fs_1.default.openSync(dir, 'r');
216
+ try {
217
+ fs_1.default.fsyncSync(fd);
218
+ }
219
+ finally {
220
+ fs_1.default.closeSync(fd);
221
+ }
222
+ }
223
+ catch {
224
+ // best effort, see above
225
+ }
226
+ }
227
+ /** Removes a file that may not exist. */
228
+ function removeQuiet(path) {
229
+ try {
230
+ fs_1.default.unlinkSync(path);
231
+ }
232
+ catch {
233
+ // cleanup of a file that may not exist
234
+ }
235
+ }
236
+ /** Truncates a partial last line left by an interrupted write and returns the resulting size. */
237
+ function repairTail(path) {
238
+ const fd = fs_1.default.openSync(path, 'r+');
239
+ try {
240
+ const size = fs_1.default.fstatSync(fd).size;
241
+ if (size === 0)
242
+ return 0;
243
+ const last = Buffer.alloc(1);
244
+ fs_1.default.readSync(fd, last, 0, 1, size - 1);
245
+ if (last[0] === 0x0a)
246
+ return size;
247
+ const CHUNK = 64 * 1024;
248
+ const buf = Buffer.alloc(CHUNK);
249
+ for (let end = size; end > 0;) {
250
+ const start = Math.max(0, end - CHUNK);
251
+ const n = end - start;
252
+ fs_1.default.readSync(fd, buf, 0, n, start);
253
+ const i = buf.subarray(0, n).lastIndexOf(0x0a);
254
+ if (i >= 0) {
255
+ const repaired = start + i + 1;
256
+ fs_1.default.ftruncateSync(fd, repaired);
257
+ return repaired;
258
+ }
259
+ end = start;
260
+ }
261
+ fs_1.default.ftruncateSync(fd, 0);
262
+ return 0;
263
+ }
264
+ finally {
265
+ fs_1.default.closeSync(fd);
266
+ }
267
+ }