@lnar/cli 0.0.1-dev.f1de48c → 0.0.1

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 (72) hide show
  1. package/dist/api-client.d.ts +19 -0
  2. package/dist/api-client.js +38 -0
  3. package/dist/api-client.js.map +1 -1
  4. package/dist/auth.d.ts +20 -0
  5. package/dist/auth.js +74 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/browser.d.ts +1 -0
  8. package/dist/browser.js +68 -0
  9. package/dist/browser.js.map +1 -0
  10. package/dist/capture-client.d.ts +13 -0
  11. package/dist/capture-client.js +37 -0
  12. package/dist/capture-client.js.map +1 -0
  13. package/dist/capture-worker.d.ts +14 -0
  14. package/dist/capture-worker.js +105 -0
  15. package/dist/capture-worker.js.map +1 -0
  16. package/dist/cli.js +21 -0
  17. package/dist/cli.js.map +1 -1
  18. package/dist/commands/daemon.js +27 -0
  19. package/dist/commands/daemon.js.map +1 -1
  20. package/dist/commands/login.d.ts +2 -0
  21. package/dist/commands/login.js +19 -9
  22. package/dist/commands/login.js.map +1 -1
  23. package/dist/commands/record.d.ts +7 -0
  24. package/dist/commands/record.js +117 -0
  25. package/dist/commands/record.js.map +1 -0
  26. package/dist/commands/up.js +19 -0
  27. package/dist/commands/up.js.map +1 -1
  28. package/dist/recording/bundle.d.ts +25 -0
  29. package/dist/recording/bundle.js +55 -0
  30. package/dist/recording/bundle.js.map +1 -0
  31. package/dist/recording/capture.d.ts +53 -0
  32. package/dist/recording/capture.js +163 -0
  33. package/dist/recording/capture.js.map +1 -0
  34. package/dist/recording/session.d.ts +30 -0
  35. package/dist/recording/session.js +47 -0
  36. package/dist/recording/session.js.map +1 -0
  37. package/dist/recording/types.d.ts +59 -0
  38. package/dist/recording/types.js +8 -0
  39. package/dist/recording/types.js.map +1 -0
  40. package/dist/run-client.d.ts +19 -0
  41. package/dist/run-client.js +44 -0
  42. package/dist/run-client.js.map +1 -0
  43. package/dist/run-worker.d.ts +25 -0
  44. package/dist/run-worker.js +85 -0
  45. package/dist/run-worker.js.map +1 -0
  46. package/dist/runtime/actions.d.ts +13 -0
  47. package/dist/runtime/actions.js +107 -0
  48. package/dist/runtime/actions.js.map +1 -0
  49. package/dist/runtime/client.d.ts +3 -0
  50. package/dist/runtime/client.js +85 -0
  51. package/dist/runtime/client.js.map +1 -0
  52. package/dist/runtime/login.d.ts +20 -0
  53. package/dist/runtime/login.js +34 -0
  54. package/dist/runtime/login.js.map +1 -0
  55. package/dist/runtime/loop.d.ts +28 -0
  56. package/dist/runtime/loop.js +68 -0
  57. package/dist/runtime/loop.js.map +1 -0
  58. package/dist/runtime/playbook.d.ts +49 -0
  59. package/dist/runtime/playbook.js +73 -0
  60. package/dist/runtime/playbook.js.map +1 -0
  61. package/dist/runtime/runner.d.ts +20 -0
  62. package/dist/runtime/runner.js +54 -0
  63. package/dist/runtime/runner.js.map +1 -0
  64. package/dist/runtime/types.d.ts +69 -0
  65. package/dist/runtime/types.js +10 -0
  66. package/dist/runtime/types.js.map +1 -0
  67. package/dist/service/files.d.ts +14 -2
  68. package/dist/service/files.js +16 -4
  69. package/dist/service/files.js.map +1 -1
  70. package/dist/service/install.js +11 -3
  71. package/dist/service/install.js.map +1 -1
  72. package/package.json +12 -2
@@ -1,5 +1,6 @@
1
+ import { openBrowser } from '../browser.js';
1
2
  import { DEFAULT_API_BASE_URL, DEFAULT_CLIENT_ID, saveConfig } from '../config.js';
2
- import { OAuthError, pollForToken, requestDeviceAuthorization, sleep, } from '../oauth-client.js';
3
+ import { OAuthError, pollForToken, requestDeviceAuthorization, sleep } from '../oauth-client.js';
3
4
  const DEFAULT_SCOPES = ['monitoring:read', 'monitoring:write'];
4
5
  const resolveBaseUrl = (override) => {
5
6
  const value = override ?? process.env.LNAR_API_BASE_URL ?? DEFAULT_API_BASE_URL;
@@ -11,15 +12,22 @@ const resolveBaseUrl = (override) => {
11
12
  }
12
13
  return value;
13
14
  };
14
- const printAuthorizationInstructions = (verificationUri, verificationUriComplete, userCode, expiresInSeconds) => {
15
+ const printAuthorizationInstructions = (verificationUri, verificationUriComplete, userCode, expiresInSeconds, browserOpened) => {
15
16
  const minutes = Math.max(1, Math.round(expiresInSeconds / 60));
16
17
  process.stdout.write('\n');
17
- process.stdout.write('To authorize this device, visit:\n');
18
- process.stdout.write(` ${verificationUri}\n`);
19
- process.stdout.write('and enter the code:\n\n');
20
- process.stdout.write(` ${userCode}\n\n`);
21
- process.stdout.write('Or open the URL with the code pre-filled:\n');
22
- process.stdout.write(` ${verificationUriComplete}\n\n`);
18
+ if (browserOpened) {
19
+ process.stdout.write('Opening your browser to authorize this device…\n');
20
+ process.stdout.write('If it does not open automatically, visit:\n');
21
+ process.stdout.write(` ${verificationUriComplete}\n\n`);
22
+ }
23
+ else {
24
+ process.stdout.write('To authorize this device, visit:\n');
25
+ process.stdout.write(` ${verificationUri}\n`);
26
+ process.stdout.write('and enter the code:\n\n');
27
+ process.stdout.write(` ${userCode}\n\n`);
28
+ process.stdout.write('Or open the URL with the code pre-filled:\n');
29
+ process.stdout.write(` ${verificationUriComplete}\n\n`);
30
+ }
23
31
  process.stdout.write(`Waiting for authorization (this code expires in ${minutes} minute${minutes === 1 ? '' : 's'})…\n`);
24
32
  };
25
33
  export const runLogin = async (options = {}) => {
@@ -37,7 +45,9 @@ export const runLogin = async (options = {}) => {
37
45
  }
38
46
  throw err;
39
47
  }
40
- printAuthorizationInstructions(auth.verification_uri, auth.verification_uri_complete, auth.user_code, auth.expires_in);
48
+ const shouldOpenBrowser = !(options.noBrowser ?? process.env.LNAR_NO_BROWSER === '1');
49
+ const browserOpened = shouldOpenBrowser && openBrowser(auth.verification_uri_complete);
50
+ printAuthorizationInstructions(auth.verification_uri, auth.verification_uri_complete, auth.user_code, auth.expires_in, browserOpened);
41
51
  // RFC 8628 §3.5: ポーリング間隔はサーバ指定の `interval` 以上、
42
52
  // `slow_down` を受け取ったら +5 秒 (サーバ側で自動加算済みだが、念のためここでも対応)。
43
53
  let intervalSeconds = Math.max(1, auth.interval || 5);
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type RecordCommandOptions = {
2
+ url?: string;
3
+ name?: string;
4
+ purpose?: string;
5
+ headless?: boolean;
6
+ };
7
+ export declare const runRecord: (options?: RecordCommandOptions) => Promise<void>;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * `lnar record` — ローカルブラウザでデモ操作を記録し、バンドルを lnar にアップロードする。
3
+ *
4
+ * Demo-to-MCP (Phase 1)。記録したバンドルは後段で gemini-3.5-flash が解析し、
5
+ * タスク手順書 (playbook) → ブラウザ操作 MCP の生成に使う。
6
+ *
7
+ * 録画内容 (キーフレーム画像) はメモリ上の Buffer としてのみ保持し、tar.gz も
8
+ * メモリ上で組み立ててアップロードする。ローカルディスクには一切書き出さない。
9
+ */
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { stdin, stdout } from 'node:process';
13
+ import { createInterface } from 'node:readline/promises';
14
+ import { ApiError, createRecording, uploadRecordingBundle } from '../api-client.js';
15
+ import { NotLoggedInError, resolveAuth } from '../auth.js';
16
+ import { packBundleToBuffer } from '../recording/bundle.js';
17
+ import { recordSession } from '../recording/session.js';
18
+ const MAX_PURPOSE = 2000;
19
+ /**
20
+ * 録画後に「この作業の目的」を尋ねる。--purpose 指定時はそれを使い、対話端末
21
+ * (TTY) でなければスキップする。目的は解析プロンプトに渡され playbook 精度を高める。
22
+ */
23
+ const resolvePurpose = async (option) => {
24
+ if (option != null && option.trim() !== '')
25
+ return option.trim().slice(0, MAX_PURPOSE);
26
+ if (!stdin.isTTY)
27
+ return null;
28
+ const rl = createInterface({ input: stdin, output: stdout });
29
+ try {
30
+ stdout.write('\nこの作業の目的を入力してください (任意、Enter でスキップ):\n');
31
+ const answer = await rl.question('> ');
32
+ const trimmed = answer.trim();
33
+ return trimmed === '' ? null : trimmed.slice(0, MAX_PURPOSE);
34
+ }
35
+ finally {
36
+ rl.close();
37
+ }
38
+ };
39
+ const fsSafeTimestamp = () => new Date().toISOString().replace(/[:.]/g, '-');
40
+ /** Enter キー or ブラウザ終了で停止するシグナルを作る。 */
41
+ const makeWaitForStop = () => (page, context) => new Promise((resolve) => {
42
+ const rl = createInterface({ input: stdin, output: stdout });
43
+ let done = false;
44
+ const finish = () => {
45
+ if (done)
46
+ return;
47
+ done = true;
48
+ rl.close();
49
+ resolve();
50
+ };
51
+ stdout.write('\n● 録画中。ブラウザで操作してください。\n');
52
+ stdout.write(' 終了するには、このターミナルで Enter を押すかブラウザを閉じてください。\n\n');
53
+ // ブラウザ close 起因で先に rl.close() されると question が reject するため、
54
+ // 成功/reject どちらでも finish() を呼び unhandled rejection を防ぐ。
55
+ void rl.question('').then(finish, finish);
56
+ context.on('close', finish);
57
+ page.on('close', finish);
58
+ });
59
+ export const runRecord = async (options = {}) => {
60
+ // アップロードは必須。録画後に失敗して取り直しを避けるため先に認証を解決する。
61
+ let auth;
62
+ try {
63
+ auth = await resolveAuth();
64
+ }
65
+ catch (err) {
66
+ if (err instanceof NotLoggedInError) {
67
+ process.stderr.write(`lnar: ${err.message}\n`);
68
+ process.exit(1);
69
+ }
70
+ throw err;
71
+ }
72
+ const createdAt = new Date().toISOString();
73
+ const name = options.name ?? `recording-${fsSafeTimestamp()}`;
74
+ const profileDir = join(homedir(), '.config', 'lnar', 'record-profile');
75
+ const result = await recordSession({
76
+ name,
77
+ startUrl: options.url ?? null,
78
+ profileDir,
79
+ headless: options.headless,
80
+ createdAt,
81
+ waitForStop: makeWaitForStop(),
82
+ });
83
+ process.stdout.write(`\n録画完了: ${result.manifest.actionCount} アクション, ` +
84
+ `${result.manifest.screenshots.length} キーフレーム\n`);
85
+ // 録画後に作業の目的を尋ねる (解析プロンプトに渡し playbook 精度を高める)。
86
+ const purpose = await resolvePurpose(options.purpose);
87
+ try {
88
+ const created = await createRecording(auth.baseUrl, auth.token, {
89
+ name,
90
+ startUrl: result.manifest.startUrl,
91
+ purpose,
92
+ manifest: result.manifest,
93
+ actions: result.actions,
94
+ });
95
+ // tar.gz をメモリ上で組み立ててそのままアップロードする (ディスク不使用)。
96
+ const bundle = await packBundleToBuffer({
97
+ manifest: result.manifest,
98
+ actions: result.actions,
99
+ frames: result.frames,
100
+ });
101
+ // アップロード時にサーバー側で即解析される (画像は保存されない)。
102
+ const analyzed = await uploadRecordingBundle(auth.baseUrl, auth.token, created.id, bundle);
103
+ process.stdout.write(`アップロード・解析完了: recording ${analyzed.id} (status=${analyzed.status})\n`);
104
+ }
105
+ catch (err) {
106
+ if (err instanceof ApiError) {
107
+ if (err.status === 401) {
108
+ process.stderr.write('lnar: not authorized (token may be expired). Run `lnar login`.\n');
109
+ process.exit(1);
110
+ }
111
+ process.stderr.write(`lnar: ${err.message}\n`);
112
+ process.exit(1);
113
+ }
114
+ throw err;
115
+ }
116
+ };
117
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/commands/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AASxD,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;GAGG;AACH,MAAM,cAAc,GAAG,KAAK,EAAE,MAAe,EAA0B,EAAE;IACvE,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvF,IAAI,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC/D,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,GAAW,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAErF,sCAAsC;AACtC,MAAM,eAAe,GACnB,GAAG,EAAE,CAAC,CAAC,IAA+B,EAAE,OAA4C,EAAE,EAAE,CACtF,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;IAC5B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,MAAM,MAAM,GAAG,GAAS,EAAE;QACxB,IAAI,IAAI;YAAE,OAAO;QACjB,IAAI,GAAG,IAAI,CAAC;QACZ,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC9D,2DAA2D;IAC3D,wDAAwD;IACxD,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEP,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,UAAgC,EAAE,EAAiB,EAAE;IACnF,yCAAyC;IACzC,IAAI,IAA6C,CAAC;IAClD,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,WAAW,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,gBAAgB,EAAE,CAAC;YACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,eAAe,EAAE,EAAE,CAAC;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAExE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;QACjC,IAAI;QACJ,QAAQ,EAAE,OAAO,CAAC,GAAG,IAAI,IAAI;QAC7B,UAAU;QACV,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS;QACT,WAAW,EAAE,eAAe,EAAE;KAC/B,CAAC,CAAC;IAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,WAAW,MAAM,CAAC,QAAQ,CAAC,WAAW,UAAU;QAC9C,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,WAAW,CACnD,CAAC;IAEF,8CAA8C;IAC9C,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE;YAC9D,IAAI;YACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;YAClC,OAAO;YACP,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC,CAAC;QACH,4CAA4C;QAC5C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC;YACtC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;QACH,oCAAoC;QACpC,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC3F,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0BAA0B,QAAQ,CAAC,EAAE,YAAY,QAAQ,CAAC,MAAM,KAAK,CACtE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAC;gBACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC"}
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * 1. `~/.config/lnar/auth.json` を読んで、認証済みでなければ device flow を起動する
5
5
  * 2. OS の常駐サービスとして登録 (macOS=launchd / Linux=systemd / Windows=Task Scheduler)
6
+ * 3. インストール直後に初回 sync を 1 回だけ同期実行し、daemon の最初の cycle を
7
+ * 待たずダッシュボードへの反映を即時化する。Tailscale が `tailscale up` で
8
+ * 同期的に "phone home" するのと同じ思想。失敗しても daemon 側 polling が
9
+ * 後続 cycle で再試行するので、ここはベストエフォート扱いにする。
6
10
  *
7
11
  * 認証済みかどうかは「資格情報ファイルが存在し、accessToken or apiKey が入っているか」で
8
12
  * 判断する。トークンの有効期限は daemon 側の API クライアントが refresh するので、
@@ -12,6 +16,20 @@
12
16
  import { loadConfig } from '../config.js';
13
17
  import { runDaemonInstall } from '../service/install.js';
14
18
  import { runLogin } from './login.js';
19
+ import { runSync } from './sync.js';
20
+ const runInitialSync = async () => {
21
+ process.stdout.write('\nRunning initial sync…\n');
22
+ try {
23
+ await runSync();
24
+ }
25
+ catch (err) {
26
+ // runSync は ApiError / 401 では process.exit(1) を直接呼ぶので、ここに到達する
27
+ // のは予期しない例外のみ。daemon サービスのインストール自体は成功しているので、
28
+ // 続く polling cycle が同じ scan を再試行できる。lnar up 自体は warn で済ませる。
29
+ const message = err instanceof Error ? err.message : String(err);
30
+ process.stderr.write(`lnar up: initial sync failed (${message}). The daemon will retry on its next cycle.\n`);
31
+ }
32
+ };
15
33
  export const runUp = async (options = {}) => {
16
34
  const config = await loadConfig();
17
35
  if (config == null) {
@@ -23,5 +41,6 @@ export const runUp = async (options = {}) => {
23
41
  process.stdout.write(`Using existing credentials (${config.apiBaseUrl}).\n\n`);
24
42
  }
25
43
  await runDaemonInstall();
44
+ await runInitialSync();
26
45
  };
27
46
  //# sourceMappingURL=up.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"up.js","sourceRoot":"","sources":["../../src/commands/up.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAqB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EAAE,UAAqB,EAAE,EAAiB,EAAE;IACpE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACxF,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,QAAQ,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,gBAAgB,EAAE,CAAC;AAC3B,CAAC,CAAC"}
1
+ {"version":3,"file":"up.js","sourceRoot":"","sources":["../../src/commands/up.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAqB,QAAQ,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,cAAc,GAAG,KAAK,IAAmB,EAAE;IAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,OAAO,EAAE,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,8DAA8D;QAC9D,6CAA6C;QAC7C,4DAA4D;QAC5D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iCAAiC,OAAO,+CAA+C,CACxF,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EAAE,UAAqB,EAAE,EAAiB,EAAE;IACpE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACxF,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,QAAQ,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,gBAAgB,EAAE,CAAC;IACzB,MAAM,cAAc,EAAE,CAAC;AACzB,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { Keyframe } from './capture.js';
2
+ import type { RecordedAction, RecordingManifest } from './types.js';
3
+ export interface BuildManifestParams {
4
+ readonly name: string;
5
+ readonly startUrl: string | null;
6
+ readonly createdAt: string;
7
+ readonly viewport: {
8
+ readonly width: number;
9
+ readonly height: number;
10
+ };
11
+ readonly actions: ReadonlyArray<RecordedAction>;
12
+ readonly screenshots: ReadonlyArray<string>;
13
+ }
14
+ /** manifest を組み立てる純関数。 */
15
+ export declare function buildManifest(params: BuildManifestParams): RecordingManifest;
16
+ export interface BundleContent {
17
+ readonly manifest: RecordingManifest;
18
+ readonly actions: ReadonlyArray<RecordedAction>;
19
+ readonly frames: ReadonlyArray<Keyframe>;
20
+ }
21
+ /**
22
+ * バンドルを tar.gz の Buffer として組み立てる (ディスク不使用)。
23
+ * 返り値をそのままアップロード body に使う。
24
+ */
25
+ export declare function packBundleToBuffer(content: BundleContent): Promise<Buffer>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 録画バンドルの組み立て。
3
+ *
4
+ * バンドルは tar.gz をメモリ上で組み立て、そのまま raw body でアップロードする。
5
+ * 録画内容 (キーフレーム画像) を一瞬でもディスクへ書かないため、ファイル経由
6
+ * (writeFile → packBundle) は使わず、すべて Buffer で完結させる。
7
+ *
8
+ * tar 構成 (サーバーが展開して読む):
9
+ * manifest.json メタデータ
10
+ * actions.json RecordedAction[]
11
+ * screenshots/NNNN.png キーフレーム
12
+ */
13
+ import { gzipSync } from 'node:zlib';
14
+ import { pack as tarPack } from 'tar-stream';
15
+ /** manifest を組み立てる純関数。 */
16
+ export function buildManifest(params) {
17
+ return {
18
+ version: 1,
19
+ name: params.name,
20
+ startUrl: params.startUrl,
21
+ createdAt: params.createdAt,
22
+ viewport: params.viewport,
23
+ actionCount: params.actions.length,
24
+ // 動画 / trace はディスクへ書かない方針のため常に未取得。
25
+ video: null,
26
+ trace: null,
27
+ screenshots: [...params.screenshots],
28
+ };
29
+ }
30
+ const jsonBuffer = (value) => Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf-8');
31
+ /**
32
+ * バンドルを tar.gz の Buffer として組み立てる (ディスク不使用)。
33
+ * 返り値をそのままアップロード body に使う。
34
+ */
35
+ export async function packBundleToBuffer(content) {
36
+ const pack = tarPack();
37
+ const addEntry = (name, bytes) => new Promise((resolve, reject) => {
38
+ pack.entry({ name }, bytes, (err) => (err ? reject(err) : resolve()));
39
+ });
40
+ const collected = new Promise((resolve, reject) => {
41
+ const chunks = [];
42
+ pack.on('data', (chunk) => chunks.push(chunk));
43
+ pack.on('error', reject);
44
+ pack.on('end', () => resolve(Buffer.concat(chunks)));
45
+ });
46
+ await addEntry('manifest.json', jsonBuffer(content.manifest));
47
+ await addEntry('actions.json', jsonBuffer(content.actions));
48
+ for (const frame of content.frames) {
49
+ await addEntry(`screenshots/${frame.name}`, frame.bytes);
50
+ }
51
+ pack.finalize();
52
+ const tarBytes = await collected;
53
+ return gzipSync(tarBytes);
54
+ }
55
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/recording/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAa7C,0BAA0B;AAC1B,MAAM,UAAU,aAAa,CAAC,MAA2B;IACvD,OAAO;QACL,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;QAClC,mCAAmC;QACnC,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;KACrC,CAAC;AACJ,CAAC;AAQD,MAAM,UAAU,GAAG,CAAC,KAAc,EAAU,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAsB;IAC7D,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,KAAa,EAAiB,EAAE,CAC9D,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC9B,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEL,MAAM,SAAS,GAAoB,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACjE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9D,MAAM,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,QAAQ,CAAC,eAAe,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;IAEhB,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;IACjC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * ローカルブラウザのユーザー操作をキャプチャする。
3
+ *
4
+ * 設計:
5
+ * - ページ内に capture-phase のリスナを注入し、click / change / submit / keydown を
6
+ * `window.__lnarRecord` (exposeBinding) 経由で Node 側へ送る。
7
+ * - Node 側 (Recorder) で録画開始からの相対時刻を付与し、キーフレームを保存する。
8
+ * - main frame のナビゲーションは Playwright の framenavigated で記録する。
9
+ *
10
+ * 機微フィールド (password 等) の値は **ページ内でマスク** してから送るため、生の
11
+ * 認証情報は Node 側にもバンドルにも残らない。
12
+ */
13
+ import type { BrowserContext, Page } from 'playwright';
14
+ import type { RecordedAction } from './types.js';
15
+ /** メモリ上に保持するキーフレーム (ディスクには一切書かない)。 */
16
+ export interface Keyframe {
17
+ readonly name: string;
18
+ readonly bytes: Buffer;
19
+ }
20
+ /** 全ページに注入する記録スクリプト (self-contained・依存なし)。 */
21
+ export declare const INIT_SCRIPT = "() => {\n if (window.__lnarRecorderInstalled) return;\n window.__lnarRecorderInstalled = true;\n var MAXV = 300;\n function esc(s){ return (window.CSS && CSS.escape) ? CSS.escape(s) : String(s).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&'); }\n function selectorFor(el){\n if (!el || el.nodeType !== 1) return undefined;\n var tid = el.getAttribute && el.getAttribute('data-testid');\n if (tid) return '[data-testid=\"' + esc(tid) + '\"]';\n if (el.id) return '#' + esc(el.id);\n var parts = [], cur = el, depth = 0;\n while (cur && cur.nodeType === 1 && depth < 4) {\n var part = cur.tagName.toLowerCase();\n var parent = cur.parentElement;\n if (parent) {\n var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === cur.tagName; });\n if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(cur) + 1) + ')';\n }\n parts.unshift(part);\n cur = cur.parentElement;\n depth++;\n }\n return parts.join(' > ');\n }\n function labelFor(el){\n if (!el || !el.getAttribute) return undefined;\n var a = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('name');\n if (a) return String(a).slice(0, 80);\n var t = (el.innerText || el.value || '').trim();\n return t ? t.slice(0, 80) : undefined;\n }\n function isSensitive(el){\n var type = ((el.getAttribute && el.getAttribute('type')) || '').toLowerCase();\n if (type === 'password') return true;\n var ac = ((el.getAttribute && el.getAttribute('autocomplete')) || '').toLowerCase();\n if (/password|cc-|one-time-code/.test(ac)) return true;\n var idn = (((el.name || '') + ' ' + (el.id || '')).toLowerCase());\n return /pass|otp|secret|token|card|cvv|ssn/.test(idn);\n }\n function send(p){ try { window.__lnarRecord(JSON.stringify(p)); } catch (e) {} }\n document.addEventListener('click', function(e){\n var el = e.target;\n send({ kind: 'click', x: Math.round(e.clientX), y: Math.round(e.clientY), selector: selectorFor(el), label: labelFor(el) });\n }, true);\n document.addEventListener('change', function(e){\n var el = e.target;\n if (!el || !('value' in el)) return;\n var sens = isSensitive(el);\n send({ kind: 'input', selector: selectorFor(el), label: labelFor(el), sensitive: sens, value: sens ? '***' : String(el.value).slice(0, MAXV) });\n }, true);\n document.addEventListener('submit', function(e){\n send({ kind: 'submit', selector: selectorFor(e.target), label: labelFor(e.target) });\n }, true);\n document.addEventListener('keydown', function(e){\n if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {\n send({ kind: 'keydown', key: e.key, selector: selectorFor(e.target) });\n }\n }, true);\n}";
22
+ export interface RecorderOptions {
23
+ readonly maxScreenshots?: number;
24
+ /** テスト用の時刻ソース。既定は Date.now。 */
25
+ readonly now?: () => number;
26
+ }
27
+ /**
28
+ * 録画状態を保持し、ページ内イベント / ナビゲーションを RecordedAction に変換する。
29
+ *
30
+ * キーフレームは PNG の Buffer としてメモリ上にのみ保持し、ディスクには書かない
31
+ * (録画内容が一瞬でもローカルに残るのを防ぐ)。
32
+ */
33
+ export declare class Recorder {
34
+ readonly actions: RecordedAction[];
35
+ readonly frames: Keyframe[];
36
+ private readonly startMs;
37
+ private readonly now;
38
+ private readonly maxScreenshots;
39
+ private shotCount;
40
+ constructor(options?: RecorderOptions);
41
+ /** 記録済みキーフレームのファイル名一覧 (manifest 用)。 */
42
+ get screenshots(): string[];
43
+ private capture;
44
+ /** ページ内イベントを受けてアクションを記録する。 */
45
+ handleInPage(page: Page, payloadJson: string): Promise<void>;
46
+ /** main frame のナビゲーションを記録する。 */
47
+ handleNavigate(page: Page, url: string): Promise<void>;
48
+ }
49
+ /**
50
+ * context にレコーダを取り付ける。exposeBinding → addInitScript の順で行うこと。
51
+ * 取り付け後に作成/遷移したページが記録対象になる。
52
+ */
53
+ export declare function attachRecorder(context: BrowserContext, recorder: Recorder): Promise<void>;
@@ -0,0 +1,163 @@
1
+ /** 全ページに注入する記録スクリプト (self-contained・依存なし)。 */
2
+ export const INIT_SCRIPT = `() => {
3
+ if (window.__lnarRecorderInstalled) return;
4
+ window.__lnarRecorderInstalled = true;
5
+ var MAXV = 300;
6
+ function esc(s){ return (window.CSS && CSS.escape) ? CSS.escape(s) : String(s).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&'); }
7
+ function selectorFor(el){
8
+ if (!el || el.nodeType !== 1) return undefined;
9
+ var tid = el.getAttribute && el.getAttribute('data-testid');
10
+ if (tid) return '[data-testid="' + esc(tid) + '"]';
11
+ if (el.id) return '#' + esc(el.id);
12
+ var parts = [], cur = el, depth = 0;
13
+ while (cur && cur.nodeType === 1 && depth < 4) {
14
+ var part = cur.tagName.toLowerCase();
15
+ var parent = cur.parentElement;
16
+ if (parent) {
17
+ var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === cur.tagName; });
18
+ if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(cur) + 1) + ')';
19
+ }
20
+ parts.unshift(part);
21
+ cur = cur.parentElement;
22
+ depth++;
23
+ }
24
+ return parts.join(' > ');
25
+ }
26
+ function labelFor(el){
27
+ if (!el || !el.getAttribute) return undefined;
28
+ var a = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('name');
29
+ if (a) return String(a).slice(0, 80);
30
+ var t = (el.innerText || el.value || '').trim();
31
+ return t ? t.slice(0, 80) : undefined;
32
+ }
33
+ function isSensitive(el){
34
+ var type = ((el.getAttribute && el.getAttribute('type')) || '').toLowerCase();
35
+ if (type === 'password') return true;
36
+ var ac = ((el.getAttribute && el.getAttribute('autocomplete')) || '').toLowerCase();
37
+ if (/password|cc-|one-time-code/.test(ac)) return true;
38
+ var idn = (((el.name || '') + ' ' + (el.id || '')).toLowerCase());
39
+ return /pass|otp|secret|token|card|cvv|ssn/.test(idn);
40
+ }
41
+ function send(p){ try { window.__lnarRecord(JSON.stringify(p)); } catch (e) {} }
42
+ document.addEventListener('click', function(e){
43
+ var el = e.target;
44
+ send({ kind: 'click', x: Math.round(e.clientX), y: Math.round(e.clientY), selector: selectorFor(el), label: labelFor(el) });
45
+ }, true);
46
+ document.addEventListener('change', function(e){
47
+ var el = e.target;
48
+ if (!el || !('value' in el)) return;
49
+ var sens = isSensitive(el);
50
+ send({ kind: 'input', selector: selectorFor(el), label: labelFor(el), sensitive: sens, value: sens ? '***' : String(el.value).slice(0, MAXV) });
51
+ }, true);
52
+ document.addEventListener('submit', function(e){
53
+ send({ kind: 'submit', selector: selectorFor(e.target), label: labelFor(e.target) });
54
+ }, true);
55
+ document.addEventListener('keydown', function(e){
56
+ if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
57
+ send({ kind: 'keydown', key: e.key, selector: selectorFor(e.target) });
58
+ }
59
+ }, true);
60
+ }`;
61
+ /**
62
+ * 録画状態を保持し、ページ内イベント / ナビゲーションを RecordedAction に変換する。
63
+ *
64
+ * キーフレームは PNG の Buffer としてメモリ上にのみ保持し、ディスクには書かない
65
+ * (録画内容が一瞬でもローカルに残るのを防ぐ)。
66
+ */
67
+ export class Recorder {
68
+ actions = [];
69
+ frames = [];
70
+ startMs;
71
+ now;
72
+ maxScreenshots;
73
+ shotCount = 0;
74
+ constructor(options = {}) {
75
+ this.now = options.now ?? (() => Date.now());
76
+ this.maxScreenshots = options.maxScreenshots ?? 200;
77
+ this.startMs = this.now();
78
+ }
79
+ /** 記録済みキーフレームのファイル名一覧 (manifest 用)。 */
80
+ get screenshots() {
81
+ return this.frames.map((f) => f.name);
82
+ }
83
+ async capture(page) {
84
+ if (this.shotCount >= this.maxScreenshots)
85
+ return undefined;
86
+ // 採番は await の前に同期的に予約する。クリック/遷移/入力が近接して発火すると
87
+ // capture が並行実行されるため、ここで予約しないと同じ番号を取り合い、
88
+ // 同名フレームで上書きして manifest に重複が出る (実データで確認済みの不具合)。
89
+ const index = ++this.shotCount;
90
+ const name = `${String(index).padStart(4, '0')}.png`;
91
+ try {
92
+ const bytes = await page.screenshot({ type: 'png' });
93
+ this.frames.push({ name, bytes });
94
+ return name;
95
+ }
96
+ catch {
97
+ return undefined; // ナビゲーション中などは握り潰す (予約番号は欠番になる)
98
+ }
99
+ }
100
+ /** ページ内イベントを受けてアクションを記録する。 */
101
+ async handleInPage(page, payloadJson) {
102
+ let payload;
103
+ try {
104
+ payload = JSON.parse(payloadJson);
105
+ }
106
+ catch {
107
+ return;
108
+ }
109
+ const screenshot = await this.capture(page);
110
+ this.actions.push({
111
+ type: payload.kind,
112
+ atMs: this.now() - this.startMs,
113
+ url: page.url(),
114
+ selector: payload.selector,
115
+ label: payload.label,
116
+ value: payload.value,
117
+ sensitive: payload.sensitive,
118
+ x: payload.x,
119
+ y: payload.y,
120
+ key: payload.key,
121
+ screenshot,
122
+ });
123
+ }
124
+ /** main frame のナビゲーションを記録する。 */
125
+ async handleNavigate(page, url) {
126
+ const screenshot = await this.capture(page);
127
+ this.actions.push({
128
+ type: 'navigate',
129
+ atMs: this.now() - this.startMs,
130
+ url,
131
+ screenshot,
132
+ });
133
+ }
134
+ }
135
+ /**
136
+ * context にレコーダを取り付ける。exposeBinding → addInitScript の順で行うこと。
137
+ * 取り付け後に作成/遷移したページが記録対象になる。
138
+ */
139
+ export async function attachRecorder(context, recorder) {
140
+ await context.exposeBinding('__lnarRecord', (source, payloadJson) => {
141
+ // 戻り値を待たない (ページ側 send は fire-and-forget)。
142
+ void recorder.handleInPage(source.page, payloadJson);
143
+ });
144
+ // INIT_SCRIPT は関数式。addInitScript は文字列をソースとして評価するだけで呼び出さ
145
+ // ないため、自己実行する形で渡す。
146
+ await context.addInitScript({ content: `(${INIT_SCRIPT})()` });
147
+ const wirePage = (page) => {
148
+ let lastUrl = '';
149
+ page.on('framenavigated', (frame) => {
150
+ if (frame !== page.mainFrame())
151
+ return;
152
+ const url = frame.url();
153
+ if (!url || url === 'about:blank' || url === lastUrl)
154
+ return;
155
+ lastUrl = url;
156
+ void recorder.handleNavigate(page, url);
157
+ });
158
+ };
159
+ for (const page of context.pages())
160
+ wirePage(page);
161
+ context.on('page', wirePage);
162
+ }
163
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/recording/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 録画セッションのオーケストレーション。
3
+ *
4
+ * ローカルブラウザを起動 → ユーザー操作を記録 → 停止シグナルで終了 → バンドル内容を返す。
5
+ * キーフレームはメモリ上の Buffer として保持し、ディスクには一切書かない。
6
+ * 停止条件 (`waitForStop`) は呼び出し側から注入し、本体をテスト可能に保つ。
7
+ */
8
+ import { type BrowserContext, type Page } from 'playwright';
9
+ import { type Keyframe } from './capture.js';
10
+ import type { RecordedAction, RecordingManifest } from './types.js';
11
+ export interface RecordSessionOptions {
12
+ readonly name: string;
13
+ readonly startUrl: string | null;
14
+ readonly profileDir: string;
15
+ readonly viewport?: {
16
+ readonly width: number;
17
+ readonly height: number;
18
+ };
19
+ readonly headless?: boolean;
20
+ readonly createdAt?: string;
21
+ /** 停止シグナル。resolve でバンドル化に進む。 */
22
+ readonly waitForStop: (page: Page, context: BrowserContext) => Promise<void>;
23
+ }
24
+ export interface RecordSessionResult {
25
+ readonly manifest: RecordingManifest;
26
+ readonly actions: ReadonlyArray<RecordedAction>;
27
+ readonly frames: ReadonlyArray<Keyframe>;
28
+ }
29
+ /** 録画を実行し、バンドル内容 (メモリ上) を返す。 */
30
+ export declare function recordSession(options: RecordSessionOptions): Promise<RecordSessionResult>;