@humanjs/generator 0.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.
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>HumanJS Generator</title>
7
+ <script type="module" crossorigin src="./assets/index-DAIzvz09.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-BA_iJGjh.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/dist/index.cjs ADDED
@@ -0,0 +1,503 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var promises = require('fs/promises');
5
+ var path = require('path');
6
+ var playwright = require('playwright');
7
+ var fs = require('fs');
8
+ var url = require('url');
9
+ var playwright$1 = require('@humanjs/playwright');
10
+ var child_process = require('child_process');
11
+ var http = require('http');
12
+ var ws = require('ws');
13
+
14
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
+ var injectedSource = null;
16
+ function loadInjectedScript() {
17
+ if (injectedSource === null) {
18
+ const dir = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
19
+ injectedSource = fs.readFileSync(path.join(dir, "injected.js"), "utf8");
20
+ }
21
+ return injectedSource;
22
+ }
23
+ function isCapturedAction(value) {
24
+ return typeof value === "object" && value !== null && typeof value.type === "string" && typeof value.params === "object";
25
+ }
26
+ var NAV_AFTER_GESTURE_MS = 2500;
27
+ var NAV_INTENT = "__navIntent";
28
+ async function attachCapture(context, onAction) {
29
+ let lastGestureAt = 0;
30
+ await context.exposeBinding("__humanjsEmit", (_source, action) => {
31
+ if (!isCapturedAction(action)) return;
32
+ lastGestureAt = Date.now();
33
+ if (action.type === NAV_INTENT) return;
34
+ onAction(action);
35
+ });
36
+ await context.addInitScript({ content: loadInjectedScript() });
37
+ let lastUrl = "";
38
+ context.on("page", (page) => {
39
+ page.on("framenavigated", (frame) => {
40
+ if (frame !== page.mainFrame()) return;
41
+ const url = frame.url();
42
+ if (!url || url === "about:blank" || url === lastUrl) return;
43
+ lastUrl = url;
44
+ if (Date.now() - lastGestureAt < NAV_AFTER_GESTURE_MS) return;
45
+ onAction({ type: "goto", params: { url } });
46
+ });
47
+ });
48
+ }
49
+ var ENV_PREFIX = "__HUMANJS_ENV__";
50
+ var ENV_SENTINEL_RE = /['"]__HUMANJS_ENV__([A-Za-z0-9_]+)__['"]/g;
51
+ function sanitizeEnvName(name) {
52
+ return name.replace(/[^A-Za-z0-9_]/g, "_");
53
+ }
54
+ function applySecrets(events) {
55
+ return events.map((event) => {
56
+ const secret = event.params.secret;
57
+ if ((event.type === "type" || event.type === "paste") && typeof secret === "string" && secret) {
58
+ return { ...event, inputValue: `${ENV_PREFIX}${sanitizeEnvName(secret)}__` };
59
+ }
60
+ return event;
61
+ });
62
+ }
63
+ function restoreSecrets(code) {
64
+ return code.replace(ENV_SENTINEL_RE, "process.env.$1");
65
+ }
66
+ function generateCode(events, options) {
67
+ const timeline = {
68
+ version: 1,
69
+ name: options.title,
70
+ personality: options.personality ?? "careful",
71
+ seed: options.seed ?? null,
72
+ speed: options.speed ?? "human",
73
+ durationMs: 0,
74
+ events: applySecrets(events)
75
+ };
76
+ const code = options.format === "script" ? playwright$1.generateHumanJS(timeline) : playwright$1.generatePlaywrightTest(timeline, options.playwright);
77
+ return restoreSecrets(code);
78
+ }
79
+ function openInBrowser(url) {
80
+ let command;
81
+ let args;
82
+ if (process.platform === "darwin") {
83
+ command = "open";
84
+ args = [url];
85
+ } else if (process.platform === "win32") {
86
+ command = "cmd";
87
+ args = ["/c", "start", "", url];
88
+ } else {
89
+ command = "xdg-open";
90
+ args = [url];
91
+ }
92
+ try {
93
+ const child = child_process.spawn(command, args, {
94
+ stdio: "ignore",
95
+ detached: true
96
+ });
97
+ child.on("error", () => {
98
+ });
99
+ child.unref();
100
+ } catch {
101
+ }
102
+ }
103
+
104
+ // src/dashboard-placeholder.ts
105
+ var PLACEHOLDER_HTML = `<!doctype html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="utf-8" />
109
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
110
+ <title>HumanJS Generator</title>
111
+ <style>
112
+ :root { color-scheme: dark; }
113
+ * { box-sizing: border-box; }
114
+ body {
115
+ margin: 0; min-height: 100vh; display: grid; place-items: center;
116
+ background: #060604; color: #f0ece5;
117
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
118
+ }
119
+ main { width: min(620px, 92vw); padding: 2rem; }
120
+ h1 { font-size: 1.25rem; margin: 0 0 0.25rem; letter-spacing: 0.02em; }
121
+ .accent { color: #f5a55c; }
122
+ .muted { color: #8a857c; }
123
+ .row { display: flex; align-items: center; gap: 0.6rem; margin: 1.25rem 0 0.5rem; }
124
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: #555; transition: background 0.2s; }
125
+ .dot.on { background: #f5a55c; box-shadow: 0 0 10px #f5a55c; }
126
+ .target { margin-top: 0.5rem; word-break: break-all; }
127
+ ol { margin: 1rem 0 0; padding-left: 0; list-style: none; font-size: 0.85rem; }
128
+ ol li { padding: 0.35rem 0; border-top: 1px solid #1a1a1a; display: flex; gap: 0.6rem; }
129
+ ol li .kind { color: #f5a55c; min-width: 5.5rem; }
130
+ ol li .detail { color: #c9c9c9; word-break: break-all; }
131
+ .empty { color: #6a6a6a; }
132
+ .bar { display: flex; align-items: center; gap: 0.6rem; margin-top: 1.5rem; flex-wrap: wrap; }
133
+ button {
134
+ font: inherit; font-size: 0.8rem; color: #060604; background: #f5a55c;
135
+ border: 0; border-radius: 6px; padding: 0.45rem 0.8rem; cursor: pointer;
136
+ }
137
+ button:hover { background: #ffb87a; }
138
+ .saved { color: #f5a55c; font-size: 0.8rem; }
139
+ pre.code {
140
+ margin-top: 1rem; padding: 1rem; background: #0c0b0a; border: 1px solid #1a1a1a;
141
+ border-radius: 8px; overflow: auto; max-height: 320px; font-size: 0.8rem;
142
+ color: #cfcfcf; white-space: pre; line-height: 1.5;
143
+ }
144
+ footer { margin-top: 1.75rem; font-size: 0.8rem; }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <main>
149
+ <h1><span class="accent">HumanJS</span> Generator</h1>
150
+ <p class="muted">Local dashboard &mdash; read-only fallback view.</p>
151
+ <div class="row"><span id="dot" class="dot"></span><span id="status">connecting&hellip;</span></div>
152
+ <p class="muted target" id="target"></p>
153
+ <ol id="events"><li class="empty">No steps captured yet &mdash; interact with the Chromium window.</li></ol>
154
+ <div class="bar">
155
+ <button id="exp-spec" type="button">Export .spec.ts</button>
156
+ <button id="exp-script" type="button">Export .ts</button>
157
+ <span id="saved" class="saved"></span>
158
+ </div>
159
+ <pre class="code" id="code"></pre>
160
+ <footer class="muted">The full editor (drag, relabel, selector picker, assertions) loads here once built.</footer>
161
+ </main>
162
+ <script>
163
+ var dot = document.getElementById('dot');
164
+ var status = document.getElementById('status');
165
+ var target = document.getElementById('target');
166
+ var events = document.getElementById('events');
167
+ var code = document.getElementById('code');
168
+ var saved = document.getElementById('saved');
169
+
170
+ function detailFor(ev) {
171
+ var p = ev.params || {};
172
+ if (typeof ev.inputValue === 'string') return (p.target || '') + ' = ' + JSON.stringify(ev.inputValue);
173
+ if (p.from) return p.from + ' -> ' + p.to;
174
+ if (p.key) return String(p.key);
175
+ if (p.url) return String(p.url);
176
+ if (p.kind) return p.kind + (p.target ? ' ' + p.target : '') + (p.value ? ' ' + JSON.stringify(p.value) : '');
177
+ if (typeof p.values !== 'undefined') return (p.target || '') + ' -> ' + JSON.stringify(p.values);
178
+ return String(p.target || '');
179
+ }
180
+
181
+ function renderSteps(steps) {
182
+ events.innerHTML = '';
183
+ if (!steps.length) {
184
+ events.innerHTML = '<li class="empty">No steps captured yet &mdash; interact with the Chromium window.</li>';
185
+ return;
186
+ }
187
+ for (var i = 0; i < steps.length; i++) {
188
+ var ev = steps[i];
189
+ var li = document.createElement('li');
190
+ var kind = document.createElement('span');
191
+ kind.className = 'kind';
192
+ kind.textContent = ev.type;
193
+ var detail = document.createElement('span');
194
+ detail.className = 'detail';
195
+ detail.textContent = detailFor(ev);
196
+ li.appendChild(kind);
197
+ li.appendChild(detail);
198
+ events.appendChild(li);
199
+ }
200
+ }
201
+
202
+ var ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host);
203
+ ws.onopen = function () { dot.classList.add('on'); status.textContent = 'connected'; };
204
+ ws.onclose = function () { dot.classList.remove('on'); status.textContent = 'disconnected'; };
205
+ ws.onmessage = function (event) {
206
+ try {
207
+ var msg = JSON.parse(event.data);
208
+ if (msg.type === 'state') {
209
+ target.textContent = 'Recording: ' + msg.targetUrl;
210
+ renderSteps(msg.steps || []);
211
+ code.textContent = msg.code || '';
212
+ } else if (msg.type === 'exported') {
213
+ saved.textContent = 'Saved ' + msg.path;
214
+ }
215
+ } catch (_) { /* ignore non-JSON frames */ }
216
+ };
217
+
218
+ function requestExport(format) {
219
+ saved.textContent = '';
220
+ ws.send(JSON.stringify({ type: 'export', format: format }));
221
+ }
222
+ document.getElementById('exp-spec').onclick = function () { requestExport('spec'); };
223
+ document.getElementById('exp-script').onclick = function () { requestExport('script'); };
224
+ </script>
225
+ </body>
226
+ </html>
227
+ `;
228
+
229
+ // src/server.ts
230
+ var MODULE_DIR = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
231
+ var DASHBOARD_DIR = path.join(MODULE_DIR, "dashboard");
232
+ var CONTENT_TYPES = {
233
+ ".html": "text/html; charset=utf-8",
234
+ ".js": "text/javascript; charset=utf-8",
235
+ ".css": "text/css; charset=utf-8",
236
+ ".json": "application/json; charset=utf-8",
237
+ ".map": "application/json; charset=utf-8",
238
+ ".svg": "image/svg+xml",
239
+ ".ico": "image/x-icon",
240
+ ".woff2": "font/woff2"
241
+ };
242
+ function contentTypeFor(filePath) {
243
+ const dot = filePath.lastIndexOf(".");
244
+ const ext = dot === -1 ? "" : filePath.slice(dot).toLowerCase();
245
+ return CONTENT_TYPES[ext] ?? "application/octet-stream";
246
+ }
247
+ async function createDashboardServer(options = {}) {
248
+ const dashboardDir = options.dashboardDir ?? DASHBOARD_DIR;
249
+ const http$1 = http.createServer((req, res) => {
250
+ void serve(dashboardDir, req.url ?? "/", res);
251
+ });
252
+ const wss = new ws.WebSocketServer({ server: http$1 });
253
+ await new Promise((resolve2, reject) => {
254
+ http$1.once("error", reject);
255
+ http$1.listen(0, "127.0.0.1", resolve2);
256
+ });
257
+ const address = http$1.address();
258
+ if (address === null || typeof address === "string") {
259
+ throw new Error("dashboard server failed to bind to a TCP port");
260
+ }
261
+ const url = `http://127.0.0.1:${address.port}`;
262
+ return {
263
+ url,
264
+ wss,
265
+ broadcast(message) {
266
+ const data = JSON.stringify(message);
267
+ for (const client of wss.clients) {
268
+ if (client.readyState === ws.WebSocket.OPEN) client.send(data);
269
+ }
270
+ },
271
+ close() {
272
+ return new Promise((resolve2) => {
273
+ for (const client of wss.clients) client.terminate();
274
+ wss.close(() => http$1.close(() => resolve2()));
275
+ });
276
+ }
277
+ };
278
+ }
279
+ async function serve(dashboardDir, rawUrl, res) {
280
+ const pathname = rawUrl.split("?")[0] ?? "/";
281
+ const relative = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
282
+ const filePath = path.normalize(path.join(dashboardDir, relative));
283
+ if (filePath !== dashboardDir && !filePath.startsWith(dashboardDir + path.sep)) {
284
+ res.writeHead(403).end("Forbidden");
285
+ return;
286
+ }
287
+ try {
288
+ const info = await promises.stat(filePath);
289
+ if (info.isFile()) {
290
+ res.writeHead(200, { "content-type": contentTypeFor(filePath) });
291
+ fs.createReadStream(filePath).pipe(res);
292
+ return;
293
+ }
294
+ } catch {
295
+ }
296
+ if (pathname === "/") {
297
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(PLACEHOLDER_HTML);
298
+ return;
299
+ }
300
+ res.writeHead(404).end("Not found");
301
+ }
302
+
303
+ // src/timeline-store.ts
304
+ var TimelineStore = class {
305
+ #steps = [];
306
+ #counter = 0;
307
+ /** The current steps, in order. */
308
+ list() {
309
+ return this.#steps;
310
+ }
311
+ /** Append a freshly captured event, assigning it an id. */
312
+ append(event) {
313
+ this.#steps.push({ ...event, id: this.#nextId() });
314
+ }
315
+ delete(id) {
316
+ this.#steps = this.#steps.filter((step) => step.id !== id);
317
+ }
318
+ /** Move a step to a new index (clamped to the list bounds). */
319
+ move(id, toIndex) {
320
+ const from = this.#steps.findIndex((step2) => step2.id === id);
321
+ if (from === -1) return;
322
+ const [step] = this.#steps.splice(from, 1);
323
+ if (!step) return;
324
+ const clamped = Math.max(0, Math.min(toIndex, this.#steps.length));
325
+ this.#steps.splice(clamped, 0, step);
326
+ }
327
+ /** Reorder the timeline to match the given id order (a full-order set from a drag). */
328
+ reorder(ids) {
329
+ const byId = new Map(this.#steps.map((step) => [step.id, step]));
330
+ const next = [];
331
+ for (const id of ids) {
332
+ const step = byId.get(id);
333
+ if (step) {
334
+ next.push(step);
335
+ byId.delete(id);
336
+ }
337
+ }
338
+ for (const step of this.#steps) if (byId.has(step.id)) next.push(step);
339
+ this.#steps = next;
340
+ }
341
+ update(id, patch) {
342
+ this.#steps = this.#steps.map((step) => step.id === id ? applyPatch(step, patch) : step);
343
+ }
344
+ /** Insert an assertion step after `afterId` (or at the end when null). */
345
+ addAssert(afterId, kind, target, value) {
346
+ const params = { kind };
347
+ if (target !== void 0) params.target = target;
348
+ if (value !== void 0) params.value = value;
349
+ const step = { id: this.#nextId(), type: "assert", params, tMs: 0, durationMs: 0 };
350
+ const index = afterId === null ? this.#steps.length - 1 : this.#findIndex(afterId);
351
+ this.#steps.splice(index + 1, 0, step);
352
+ }
353
+ #findIndex(id) {
354
+ return this.#steps.findIndex((step) => step.id === id);
355
+ }
356
+ #nextId() {
357
+ this.#counter += 1;
358
+ return `s${this.#counter}`;
359
+ }
360
+ };
361
+ function applyPatch(step, patch) {
362
+ const params = { ...step.params };
363
+ if (patch.target !== void 0) params.target = patch.target;
364
+ if (patch.label !== void 0) params.label = patch.label;
365
+ if (patch.secret !== void 0) {
366
+ if (patch.secret === null) delete params.secret;
367
+ else params.secret = patch.secret;
368
+ }
369
+ return {
370
+ ...step,
371
+ params,
372
+ ...patch.inputValue !== void 0 ? { inputValue: patch.inputValue } : {}
373
+ };
374
+ }
375
+
376
+ // src/run.ts
377
+ var PERSONALITIES = ["careful", "fast", "distracted", "precise"];
378
+ var OUTPUT_FILE = {
379
+ spec: "humanjs-recording.spec.ts",
380
+ script: "humanjs-recording.ts"
381
+ };
382
+ async function start(targetUrl) {
383
+ const server = await createDashboardServer();
384
+ const store = new TimelineStore();
385
+ let personality = "careful";
386
+ const previewCode = () => generateCode(store.list(), { format: "spec", personality });
387
+ const stateMessage = () => ({
388
+ type: "state",
389
+ targetUrl,
390
+ steps: store.list(),
391
+ personality,
392
+ personalities: [...PERSONALITIES],
393
+ code: previewCode()
394
+ });
395
+ const broadcastState = () => server.broadcast(stateMessage());
396
+ const exportTimeline = async (format) => {
397
+ const path$1 = path.resolve(process.cwd(), OUTPUT_FILE[format]);
398
+ await promises.writeFile(path$1, generateCode(store.list(), { format, personality }), "utf8");
399
+ server.broadcast({ type: "exported", path: path$1 });
400
+ console.log(` Exported ${format === "spec" ? "test" : "script"} \u2192 ${path$1}`);
401
+ };
402
+ const handleCommand = (message) => {
403
+ switch (message.type) {
404
+ case "delete":
405
+ store.delete(message.id);
406
+ break;
407
+ case "move":
408
+ store.move(message.id, message.toIndex);
409
+ break;
410
+ case "reorder":
411
+ store.reorder(message.ids);
412
+ break;
413
+ case "update":
414
+ store.update(message.id, message.patch);
415
+ break;
416
+ case "addAssert":
417
+ store.addAssert(message.afterId, message.kind, message.target, message.value);
418
+ break;
419
+ case "setPersonality":
420
+ if (PERSONALITIES.includes(message.personality)) {
421
+ personality = message.personality;
422
+ }
423
+ break;
424
+ case "export":
425
+ void exportTimeline(message.format);
426
+ return;
427
+ }
428
+ broadcastState();
429
+ };
430
+ server.wss.on("connection", (socket) => {
431
+ socket.send(JSON.stringify(stateMessage()));
432
+ socket.on("message", (data) => {
433
+ try {
434
+ handleCommand(JSON.parse(data.toString()));
435
+ } catch {
436
+ }
437
+ });
438
+ });
439
+ const browser = await playwright.chromium.launch({ headless: false });
440
+ const context = await browser.newContext({ viewport: null });
441
+ await attachCapture(context, (action) => {
442
+ store.append({
443
+ type: action.type,
444
+ params: action.params,
445
+ tMs: 0,
446
+ durationMs: 0,
447
+ ...action.inputValue === void 0 ? {} : { inputValue: action.inputValue }
448
+ });
449
+ broadcastState();
450
+ });
451
+ const page = await context.newPage();
452
+ let closing = false;
453
+ const shutdown = async () => {
454
+ if (closing) return;
455
+ closing = true;
456
+ await browser.close().catch(() => {
457
+ });
458
+ await server.close();
459
+ process.exit(0);
460
+ };
461
+ process.on("SIGINT", () => void shutdown());
462
+ process.on("SIGTERM", () => void shutdown());
463
+ browser.on("disconnected", () => void shutdown());
464
+ openInBrowser(server.url);
465
+ try {
466
+ await page.goto(targetUrl);
467
+ } catch (error) {
468
+ const reason = error instanceof Error ? error.message : String(error);
469
+ console.warn(` Couldn't load ${targetUrl}: ${reason}`);
470
+ console.warn(" Navigate manually in the Chromium window.");
471
+ }
472
+ console.log("");
473
+ console.log(" HumanJS Generator");
474
+ console.log(` Dashboard: ${server.url}`);
475
+ console.log(` Recording: ${targetUrl}`);
476
+ console.log("");
477
+ console.log(" Interact with the Chromium window. Close it or press Ctrl+C to stop.");
478
+ console.log("");
479
+ }
480
+
481
+ // src/url.ts
482
+ function normalizeUrl(input) {
483
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(input)) return input;
484
+ const isLoopback = /^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/i.test(input);
485
+ return `${isLoopback ? "http" : "https"}://${input}`;
486
+ }
487
+
488
+ // src/index.ts
489
+ var USAGE = "Usage: npx @humanjs/generator <url>";
490
+ async function main(argv) {
491
+ const [rawUrl] = argv;
492
+ if (!rawUrl || rawUrl === "--help" || rawUrl === "-h") {
493
+ console.log(USAGE);
494
+ process.exit(rawUrl ? 0 : 1);
495
+ }
496
+ await start(normalizeUrl(rawUrl));
497
+ }
498
+ main(process.argv.slice(2)).catch((error) => {
499
+ console.error(error instanceof Error ? error.message : String(error));
500
+ process.exit(1);
501
+ });
502
+ //# sourceMappingURL=index.cjs.map
503
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/capture/attach.ts","../src/export.ts","../src/open-browser.ts","../src/dashboard-placeholder.ts","../src/server.ts","../src/timeline-store.ts","../src/run.ts","../src/url.ts","../src/index.ts"],"names":["dirname","fileURLToPath","readFileSync","join","generateHumanJS","generatePlaywrightTest","spawn","http","createServer","WebSocketServer","resolve","WebSocket","normalize","sep","stat","createReadStream","step","path","writeFile","chromium"],"mappings":"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","file":"index.cjs","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { BrowserContext } from 'playwright';\nimport type { CapturedAction } from './types';\n\nlet injectedSource: string | null = null;\n\n/** The bundled in-page recorder (dist/injected.js), read once and cached. */\nfunction loadInjectedScript(): string {\n if (injectedSource === null) {\n const dir = dirname(fileURLToPath(import.meta.url));\n injectedSource = readFileSync(join(dir, 'injected.js'), 'utf8');\n }\n return injectedSource;\n}\n\nfunction isCapturedAction(value: unknown): value is CapturedAction {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { type?: unknown }).type === 'string' &&\n typeof (value as { params?: unknown }).params === 'object'\n );\n}\n\n/**\n * A main-frame navigation within this window of the last in-page gesture\n * (pointerdown / keydown) is treated as a *consequence* of that gesture — a\n * clicked link, a submitted form, a typed search that updates the URL. The\n * gesture is already recorded and reproduces the navigation on replay, so we\n * don't also record a `goto` (which would navigate a second time). Navigations\n * with no recent gesture — the initial load, or the user typing a URL in the\n * address bar — are still captured.\n */\nconst NAV_AFTER_GESTURE_MS = 2500;\n\n/** Sentinel action the recorder pings on each gesture; bumps the clock, never a step. */\nconst NAV_INTENT = '__navIntent';\n\n/**\n * Attach interaction capture to a browser context: expose the `__humanjsEmit`\n * binding the recorder calls, inject the recorder into every page, and report\n * main-frame navigations as `goto` actions. Each captured action is handed to\n * `onAction` in the order it occurred.\n *\n * Must be called before the first page is created so the init script and\n * binding apply to it.\n */\nexport async function attachCapture(\n context: BrowserContext,\n onAction: (action: CapturedAction) => void,\n): Promise<void> {\n // Timestamp of the last in-page gesture (or recorded action). Used to tell a\n // user-driven navigation from one caused by an interaction we already logged.\n let lastGestureAt = 0;\n\n await context.exposeBinding('__humanjsEmit', (_source, action: unknown) => {\n if (!isCapturedAction(action)) return;\n lastGestureAt = Date.now();\n // Gesture pings only advance the clock — they aren't timeline steps.\n if (action.type === NAV_INTENT) return;\n onAction(action);\n });\n await context.addInitScript({ content: loadInjectedScript() });\n\n // Main-frame navigations become `goto` steps. Dedupe consecutive identical\n // URLs (a single navigation can fire more than once) and ignore blanks.\n let lastUrl = '';\n context.on('page', (page) => {\n page.on('framenavigated', (frame) => {\n if (frame !== page.mainFrame()) return;\n const url = frame.url();\n if (!url || url === 'about:blank' || url === lastUrl) return;\n lastUrl = url;\n // Skip navigations that are the consequence of a just-recorded gesture\n // (clicked link, form submit, search-as-you-type) — replaying the gesture\n // navigates on its own; a `goto` here would double-navigate.\n if (Date.now() - lastGestureAt < NAV_AFTER_GESTURE_MS) return;\n onAction({ type: 'goto', params: { url } });\n });\n });\n}\n","import {\n generateHumanJS,\n generatePlaywrightTest,\n type PlaywrightTestOptions,\n type Timeline,\n type TimelineEvent,\n} from '@humanjs/playwright';\n\nexport type ExportFormat = 'spec' | 'script';\n\nexport interface ExportOptions {\n /** `spec` → a `@humanjs/playwright/test` spec; `script` → a standalone HumanJS script. */\n readonly format: ExportFormat;\n readonly personality?: string;\n readonly seed?: string | null;\n readonly speed?: string;\n /** Test title (spec only); defaults to the recording name. */\n readonly title?: string;\n /** Extra `generatePlaywrightTest` options (steps / baseUrl / keepSleeps). */\n readonly playwright?: PlaywrightTestOptions;\n}\n\n// A `type`/`paste` step flagged secret (`params.secret = 'ENV_NAME'`) exports\n// its value as `process.env.ENV_NAME` instead of a literal. We can't make the\n// codegen emit a raw identifier directly, so we route the value through a\n// sentinel string and unquote it in the generated output.\nconst ENV_PREFIX = '__HUMANJS_ENV__';\nconst ENV_SENTINEL_RE = /['\"]__HUMANJS_ENV__([A-Za-z0-9_]+)__['\"]/g;\n\nfunction sanitizeEnvName(name: string): string {\n return name.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/** Replace the value of secret-flagged type/paste steps with an env sentinel. */\nfunction applySecrets(events: readonly TimelineEvent[]): TimelineEvent[] {\n return events.map((event) => {\n const secret = event.params.secret;\n if ((event.type === 'type' || event.type === 'paste') && typeof secret === 'string' && secret) {\n return { ...event, inputValue: `${ENV_PREFIX}${sanitizeEnvName(secret)}__` };\n }\n return event;\n });\n}\n\nfunction restoreSecrets(code: string): string {\n return code.replace(ENV_SENTINEL_RE, 'process.env.$1');\n}\n\n/** Pick the export format from an output filename. `.spec.ts` / `.test.ts` → spec. */\nexport function formatFromFilename(filename: string): ExportFormat {\n return /\\.(spec|test)\\.[cm]?tsx?$/i.test(filename) ? 'spec' : 'script';\n}\n\n/**\n * Generate code from a captured timeline. Wraps the events in a `Timeline` with\n * the chosen personality/seed/speed and runs the shared `@humanjs/playwright`\n * codegen, then substitutes `process.env.*` for any secret-flagged values.\n */\nexport function generateCode(events: readonly TimelineEvent[], options: ExportOptions): string {\n const timeline: Timeline = {\n version: 1,\n name: options.title,\n personality: options.personality ?? 'careful',\n seed: options.seed ?? null,\n speed: options.speed ?? 'human',\n durationMs: 0,\n events: applySecrets(events),\n };\n const code =\n options.format === 'script'\n ? generateHumanJS(timeline)\n : generatePlaywrightTest(timeline, options.playwright);\n return restoreSecrets(code);\n}\n","import { spawn } from 'node:child_process';\n\n/**\n * Best-effort: open `url` in the user's default browser for the dashboard.\n *\n * Uses the platform's native opener (`open` / `start` / `xdg-open`). Failures\n * are swallowed — the CLI always prints the URL too, so the user can open it\n * by hand if no opener is available (headless box, locked-down environment).\n */\nexport function openInBrowser(url: string): void {\n let command: string;\n let args: string[];\n if (process.platform === 'darwin') {\n command = 'open';\n args = [url];\n } else if (process.platform === 'win32') {\n command = 'cmd';\n args = ['/c', 'start', '', url];\n } else {\n command = 'xdg-open';\n args = [url];\n }\n\n try {\n const child = spawn(command, args, {\n stdio: 'ignore',\n detached: true,\n });\n child.on('error', () => {\n // No opener on this platform / not on PATH — the printed URL is the fallback.\n });\n child.unref();\n } catch {\n // spawn threw synchronously (rare) — ignore; the URL is printed regardless.\n }\n}\n","/**\n * Fallback dashboard served when the built Vite + React editor isn't present in\n * `dist/dashboard/`. It's a read-only view of the live timeline + code preview\n * over the same `state` protocol, with Export buttons — enough to exercise the\n * pipeline without the full editor.\n *\n * Single self-contained string — no external assets, no nested backticks.\n */\nexport const PLACEHOLDER_HTML = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>HumanJS Generator</title>\n <style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0; min-height: 100vh; display: grid; place-items: center;\n background: #060604; color: #f0ece5;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n }\n main { width: min(620px, 92vw); padding: 2rem; }\n h1 { font-size: 1.25rem; margin: 0 0 0.25rem; letter-spacing: 0.02em; }\n .accent { color: #f5a55c; }\n .muted { color: #8a857c; }\n .row { display: flex; align-items: center; gap: 0.6rem; margin: 1.25rem 0 0.5rem; }\n .dot { width: 9px; height: 9px; border-radius: 50%; background: #555; transition: background 0.2s; }\n .dot.on { background: #f5a55c; box-shadow: 0 0 10px #f5a55c; }\n .target { margin-top: 0.5rem; word-break: break-all; }\n ol { margin: 1rem 0 0; padding-left: 0; list-style: none; font-size: 0.85rem; }\n ol li { padding: 0.35rem 0; border-top: 1px solid #1a1a1a; display: flex; gap: 0.6rem; }\n ol li .kind { color: #f5a55c; min-width: 5.5rem; }\n ol li .detail { color: #c9c9c9; word-break: break-all; }\n .empty { color: #6a6a6a; }\n .bar { display: flex; align-items: center; gap: 0.6rem; margin-top: 1.5rem; flex-wrap: wrap; }\n button {\n font: inherit; font-size: 0.8rem; color: #060604; background: #f5a55c;\n border: 0; border-radius: 6px; padding: 0.45rem 0.8rem; cursor: pointer;\n }\n button:hover { background: #ffb87a; }\n .saved { color: #f5a55c; font-size: 0.8rem; }\n pre.code {\n margin-top: 1rem; padding: 1rem; background: #0c0b0a; border: 1px solid #1a1a1a;\n border-radius: 8px; overflow: auto; max-height: 320px; font-size: 0.8rem;\n color: #cfcfcf; white-space: pre; line-height: 1.5;\n }\n footer { margin-top: 1.75rem; font-size: 0.8rem; }\n </style>\n </head>\n <body>\n <main>\n <h1><span class=\"accent\">HumanJS</span> Generator</h1>\n <p class=\"muted\">Local dashboard &mdash; read-only fallback view.</p>\n <div class=\"row\"><span id=\"dot\" class=\"dot\"></span><span id=\"status\">connecting&hellip;</span></div>\n <p class=\"muted target\" id=\"target\"></p>\n <ol id=\"events\"><li class=\"empty\">No steps captured yet &mdash; interact with the Chromium window.</li></ol>\n <div class=\"bar\">\n <button id=\"exp-spec\" type=\"button\">Export .spec.ts</button>\n <button id=\"exp-script\" type=\"button\">Export .ts</button>\n <span id=\"saved\" class=\"saved\"></span>\n </div>\n <pre class=\"code\" id=\"code\"></pre>\n <footer class=\"muted\">The full editor (drag, relabel, selector picker, assertions) loads here once built.</footer>\n </main>\n <script>\n var dot = document.getElementById('dot');\n var status = document.getElementById('status');\n var target = document.getElementById('target');\n var events = document.getElementById('events');\n var code = document.getElementById('code');\n var saved = document.getElementById('saved');\n\n function detailFor(ev) {\n var p = ev.params || {};\n if (typeof ev.inputValue === 'string') return (p.target || '') + ' = ' + JSON.stringify(ev.inputValue);\n if (p.from) return p.from + ' -> ' + p.to;\n if (p.key) return String(p.key);\n if (p.url) return String(p.url);\n if (p.kind) return p.kind + (p.target ? ' ' + p.target : '') + (p.value ? ' ' + JSON.stringify(p.value) : '');\n if (typeof p.values !== 'undefined') return (p.target || '') + ' -> ' + JSON.stringify(p.values);\n return String(p.target || '');\n }\n\n function renderSteps(steps) {\n events.innerHTML = '';\n if (!steps.length) {\n events.innerHTML = '<li class=\"empty\">No steps captured yet &mdash; interact with the Chromium window.</li>';\n return;\n }\n for (var i = 0; i < steps.length; i++) {\n var ev = steps[i];\n var li = document.createElement('li');\n var kind = document.createElement('span');\n kind.className = 'kind';\n kind.textContent = ev.type;\n var detail = document.createElement('span');\n detail.className = 'detail';\n detail.textContent = detailFor(ev);\n li.appendChild(kind);\n li.appendChild(detail);\n events.appendChild(li);\n }\n }\n\n var ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host);\n ws.onopen = function () { dot.classList.add('on'); status.textContent = 'connected'; };\n ws.onclose = function () { dot.classList.remove('on'); status.textContent = 'disconnected'; };\n ws.onmessage = function (event) {\n try {\n var msg = JSON.parse(event.data);\n if (msg.type === 'state') {\n target.textContent = 'Recording: ' + msg.targetUrl;\n renderSteps(msg.steps || []);\n code.textContent = msg.code || '';\n } else if (msg.type === 'exported') {\n saved.textContent = 'Saved ' + msg.path;\n }\n } catch (_) { /* ignore non-JSON frames */ }\n };\n\n function requestExport(format) {\n saved.textContent = '';\n ws.send(JSON.stringify({ type: 'export', format: format }));\n }\n document.getElementById('exp-spec').onclick = function () { requestExport('spec'); };\n document.getElementById('exp-script').onclick = function () { requestExport('script'); };\n </script>\n </body>\n</html>\n`;\n","import { createReadStream } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { createServer, type ServerResponse } from 'node:http';\nimport { dirname, join, normalize, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { PLACEHOLDER_HTML } from './dashboard-placeholder';\nimport type { ServerMessage } from './protocol';\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n// The built Vite dashboard lands in dist/dashboard/ (milestone 5). Until it\n// exists, the root path falls back to the embedded placeholder.\nconst DASHBOARD_DIR = join(MODULE_DIR, 'dashboard');\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff2': 'font/woff2',\n};\n\nfunction contentTypeFor(filePath: string): string {\n const dot = filePath.lastIndexOf('.');\n const ext = dot === -1 ? '' : filePath.slice(dot).toLowerCase();\n return CONTENT_TYPES[ext] ?? 'application/octet-stream';\n}\n\nexport interface DashboardServer {\n /** Loopback URL the dashboard is served from, e.g. `http://127.0.0.1:53124`. */\n readonly url: string;\n /** Underlying WebSocket server — attach `connection` handlers for the live channel. */\n readonly wss: WebSocketServer;\n /** Send a message to every connected dashboard client. */\n broadcast(message: ServerMessage): void;\n close(): Promise<void>;\n}\n\nexport interface DashboardServerOptions {\n /** Directory of built dashboard assets to serve. Defaults to `dist/dashboard`. */\n readonly dashboardDir?: string;\n}\n\n/**\n * Start the local dashboard server, bound to loopback on an OS-assigned port.\n * Serves the built dashboard (or the placeholder) over HTTP and upgrades\n * WebSocket connections on the same port.\n */\nexport async function createDashboardServer(\n options: DashboardServerOptions = {},\n): Promise<DashboardServer> {\n const dashboardDir = options.dashboardDir ?? DASHBOARD_DIR;\n const http = createServer((req, res) => {\n void serve(dashboardDir, req.url ?? '/', res);\n });\n const wss = new WebSocketServer({ server: http });\n\n await new Promise<void>((resolve, reject) => {\n http.once('error', reject);\n // Loopback only — this is a local dev tool, never exposed to the network.\n http.listen(0, '127.0.0.1', resolve);\n });\n\n const address = http.address();\n if (address === null || typeof address === 'string') {\n throw new Error('dashboard server failed to bind to a TCP port');\n }\n const url = `http://127.0.0.1:${address.port}`;\n\n return {\n url,\n wss,\n broadcast(message) {\n const data = JSON.stringify(message);\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) client.send(data);\n }\n },\n close() {\n return new Promise<void>((resolve) => {\n for (const client of wss.clients) client.terminate();\n wss.close(() => http.close(() => resolve()));\n });\n },\n };\n}\n\nasync function serve(dashboardDir: string, rawUrl: string, res: ServerResponse): Promise<void> {\n const pathname = rawUrl.split('?')[0] ?? '/';\n const relative = pathname === '/' ? 'index.html' : pathname.replace(/^\\/+/, '');\n const filePath = normalize(join(dashboardDir, relative));\n\n // Path-traversal guard: the resolved file must stay inside the dashboard dir.\n if (filePath !== dashboardDir && !filePath.startsWith(dashboardDir + sep)) {\n res.writeHead(403).end('Forbidden');\n return;\n }\n\n try {\n const info = await stat(filePath);\n if (info.isFile()) {\n res.writeHead(200, { 'content-type': contentTypeFor(filePath) });\n createReadStream(filePath).pipe(res);\n return;\n }\n } catch {\n // No such file — fall through to the placeholder / 404.\n }\n\n // No built dashboard yet (or an unknown path): serve the placeholder at root.\n if (pathname === '/') {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }).end(PLACEHOLDER_HTML);\n return;\n }\n res.writeHead(404).end('Not found');\n}\n","import type { TimelineEvent } from '@humanjs/playwright';\nimport type { AssertKind, Step, StepPatch } from './protocol';\n\n/**\n * The canonical, editable timeline held by the CLI. Capture appends events;\n * the dashboard issues edit commands (delete / move / update / addAssert) that\n * mutate this list. Every step carries a stable id so edits can target it.\n *\n * Steps are valid `TimelineEvent`s (plus the id, which the codegen ignores), so\n * the list can be passed straight to `generateCode`.\n */\nexport class TimelineStore {\n #steps: Step[] = [];\n #counter = 0;\n\n /** The current steps, in order. */\n list(): readonly Step[] {\n return this.#steps;\n }\n\n /** Append a freshly captured event, assigning it an id. */\n append(event: TimelineEvent): void {\n this.#steps.push({ ...event, id: this.#nextId() });\n }\n\n delete(id: string): void {\n this.#steps = this.#steps.filter((step) => step.id !== id);\n }\n\n /** Move a step to a new index (clamped to the list bounds). */\n move(id: string, toIndex: number): void {\n const from = this.#steps.findIndex((step) => step.id === id);\n if (from === -1) return;\n const [step] = this.#steps.splice(from, 1);\n if (!step) return;\n const clamped = Math.max(0, Math.min(toIndex, this.#steps.length));\n this.#steps.splice(clamped, 0, step);\n }\n\n /** Reorder the timeline to match the given id order (a full-order set from a drag). */\n reorder(ids: readonly string[]): void {\n const byId = new Map(this.#steps.map((step) => [step.id, step]));\n const next: Step[] = [];\n for (const id of ids) {\n const step = byId.get(id);\n if (step) {\n next.push(step);\n byId.delete(id);\n }\n }\n // Keep any steps the client didn't mention (e.g. captured mid-drag) at the end.\n for (const step of this.#steps) if (byId.has(step.id)) next.push(step);\n this.#steps = next;\n }\n\n update(id: string, patch: StepPatch): void {\n this.#steps = this.#steps.map((step) => (step.id === id ? applyPatch(step, patch) : step));\n }\n\n /** Insert an assertion step after `afterId` (or at the end when null). */\n addAssert(afterId: string | null, kind: AssertKind, target?: string, value?: string): void {\n const params: Record<string, unknown> = { kind };\n if (target !== undefined) params.target = target;\n if (value !== undefined) params.value = value;\n const step: Step = { id: this.#nextId(), type: 'assert', params, tMs: 0, durationMs: 0 };\n\n const index = afterId === null ? this.#steps.length - 1 : this.#findIndex(afterId);\n this.#steps.splice(index + 1, 0, step);\n }\n\n #findIndex(id: string): number {\n return this.#steps.findIndex((step) => step.id === id);\n }\n\n #nextId(): string {\n this.#counter += 1;\n return `s${this.#counter}`;\n }\n}\n\nfunction applyPatch(step: Step, patch: StepPatch): Step {\n const params: Record<string, unknown> = { ...step.params };\n if (patch.target !== undefined) params.target = patch.target;\n if (patch.label !== undefined) params.label = patch.label;\n if (patch.secret !== undefined) {\n if (patch.secret === null) delete params.secret;\n else params.secret = patch.secret;\n }\n return {\n ...step,\n params,\n ...(patch.inputValue !== undefined ? { inputValue: patch.inputValue } : {}),\n };\n}\n","import { writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { chromium } from 'playwright';\nimport { attachCapture } from './capture/attach';\nimport { type ExportFormat, generateCode } from './export';\nimport { openInBrowser } from './open-browser';\nimport type { ClientMessage, ServerMessage } from './protocol';\nimport { createDashboardServer } from './server';\nimport { TimelineStore } from './timeline-store';\n\n/** Built-in personalities the dashboard switcher offers. */\nconst PERSONALITIES = ['careful', 'fast', 'distracted', 'precise'] as const;\n\n/** Default output filenames per format, written to the CLI's working directory. */\nconst OUTPUT_FILE: Record<ExportFormat, string> = {\n spec: 'humanjs-recording.spec.ts',\n script: 'humanjs-recording.ts',\n};\n\n/**\n * Launch a recording session: start the local dashboard, open a real Chromium\n * window at `targetUrl`, capture interactions into an editable timeline, and let\n * the dashboard curate it (delete / reorder / relabel / edit / pick selectors /\n * add assertions / mark secrets / switch personality) with a live code preview,\n * then export a `.spec.ts` / `.ts`. Runs until the browser closes or the\n * process is interrupted.\n */\nexport async function start(targetUrl: string): Promise<void> {\n const server = await createDashboardServer();\n const store = new TimelineStore();\n let personality = 'careful';\n\n const previewCode = (): string => generateCode(store.list(), { format: 'spec', personality });\n\n const stateMessage = (): ServerMessage => ({\n type: 'state',\n targetUrl,\n steps: store.list(),\n personality,\n personalities: [...PERSONALITIES],\n code: previewCode(),\n });\n\n const broadcastState = (): void => server.broadcast(stateMessage());\n\n const exportTimeline = async (format: ExportFormat): Promise<void> => {\n const path = resolve(process.cwd(), OUTPUT_FILE[format]);\n await writeFile(path, generateCode(store.list(), { format, personality }), 'utf8');\n server.broadcast({ type: 'exported', path });\n console.log(` Exported ${format === 'spec' ? 'test' : 'script'} → ${path}`);\n };\n\n const handleCommand = (message: ClientMessage): void => {\n switch (message.type) {\n case 'delete':\n store.delete(message.id);\n break;\n case 'move':\n store.move(message.id, message.toIndex);\n break;\n case 'reorder':\n store.reorder(message.ids);\n break;\n case 'update':\n store.update(message.id, message.patch);\n break;\n case 'addAssert':\n store.addAssert(message.afterId, message.kind, message.target, message.value);\n break;\n case 'setPersonality':\n if ((PERSONALITIES as readonly string[]).includes(message.personality)) {\n personality = message.personality;\n }\n break;\n case 'export':\n void exportTimeline(message.format);\n return; // export replies with `exported`, not a state refresh\n }\n broadcastState();\n };\n\n server.wss.on('connection', (socket) => {\n socket.send(JSON.stringify(stateMessage()));\n socket.on('message', (data) => {\n try {\n handleCommand(JSON.parse(data.toString()) as ClientMessage);\n } catch {\n // Ignore malformed frames.\n }\n });\n });\n\n const browser = await chromium.launch({ headless: false });\n // `viewport: null` lets the page fill the real window — a person drives this.\n const context = await browser.newContext({ viewport: null });\n // Attach capture before the first page exists so the init script applies.\n await attachCapture(context, (action) => {\n store.append({\n type: action.type,\n params: action.params,\n tMs: 0,\n durationMs: 0,\n ...(action.inputValue === undefined ? {} : { inputValue: action.inputValue }),\n });\n broadcastState();\n });\n const page = await context.newPage();\n\n let closing = false;\n const shutdown = async (): Promise<void> => {\n if (closing) return;\n closing = true;\n await browser.close().catch(() => {});\n await server.close();\n process.exit(0);\n };\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n browser.on('disconnected', () => void shutdown());\n\n openInBrowser(server.url);\n\n try {\n await page.goto(targetUrl);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.warn(` Couldn't load ${targetUrl}: ${reason}`);\n console.warn(' Navigate manually in the Chromium window.');\n }\n\n console.log('');\n console.log(' HumanJS Generator');\n console.log(` Dashboard: ${server.url}`);\n console.log(` Recording: ${targetUrl}`);\n console.log('');\n console.log(' Interact with the Chromium window. Close it or press Ctrl+C to stop.');\n console.log('');\n}\n","/**\n * Normalize a user-supplied target into a navigable URL.\n *\n * A bare host (`example.com`) gets an `https://` scheme so `page.goto` accepts\n * it; loopback hosts default to `http://` since local dev servers rarely speak\n * TLS. An explicit scheme is always left untouched.\n */\nexport function normalizeUrl(input: string): string {\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(input)) return input;\n const isLoopback = /^(localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|\\[::1\\])(:|\\/|$)/i.test(input);\n return `${isLoopback ? 'http' : 'https'}://${input}`;\n}\n","/**\n * @humanjs/generator — visual recorder for HumanJS.\n *\n * `npx @humanjs/generator <url>` launches a real Chromium window, records the\n * clicks / typing / scrolls / navigation you perform (with role- and\n * accessible-name-first selectors), shows a live, editable timeline in a local\n * dashboard, and exports a clean humanized Playwright test.\n *\n * Milestone 2: the CLI launches the browser and the local dashboard server and\n * wires the WebSocket channel. Capture, the editor UI, and export land across\n * the following milestones (see ROADMAP.md).\n */\n\nimport { start } from './run';\nimport { normalizeUrl } from './url';\n\nconst USAGE = 'Usage: npx @humanjs/generator <url>';\n\nasync function main(argv: readonly string[]): Promise<void> {\n const [rawUrl] = argv;\n\n if (!rawUrl || rawUrl === '--help' || rawUrl === '-h') {\n // No URL (or an explicit help flag): print usage. Exit 0 for --help, 1 for\n // a missing-argument error.\n console.log(USAGE);\n process.exit(rawUrl ? 0 : 1);\n }\n\n await start(normalizeUrl(rawUrl));\n}\n\nmain(process.argv.slice(2)).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }