@monoes/monobrowse 1.0.1 → 1.0.3

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 (277) hide show
  1. package/dist/src/__tests__/output.test.d.ts +2 -0
  2. package/dist/src/__tests__/output.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/output.test.js +54 -0
  4. package/dist/src/__tests__/output.test.js.map +1 -0
  5. package/dist/src/__tests__/ref-cache.test.d.ts +2 -0
  6. package/dist/src/__tests__/ref-cache.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/ref-cache.test.js +109 -0
  8. package/dist/src/__tests__/ref-cache.test.js.map +1 -0
  9. package/dist/src/browser/action-builder/analyzer.d.ts +0 -1
  10. package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
  11. package/dist/src/browser/action-builder/analyzer.js +43 -21
  12. package/dist/src/browser/action-builder/analyzer.js.map +1 -1
  13. package/dist/src/browser/action-builder/types.d.ts +0 -0
  14. package/dist/src/browser/action-builder/types.d.ts.map +0 -0
  15. package/dist/src/browser/action-builder/types.js +0 -0
  16. package/dist/src/browser/action-builder/types.js.map +0 -0
  17. package/dist/src/browser/actions.d.ts +0 -0
  18. package/dist/src/browser/actions.d.ts.map +0 -0
  19. package/dist/src/browser/actions.js +0 -0
  20. package/dist/src/browser/actions.js.map +0 -0
  21. package/dist/src/browser/adapters/gemini.d.ts +0 -0
  22. package/dist/src/browser/adapters/gemini.d.ts.map +0 -0
  23. package/dist/src/browser/adapters/gemini.js +0 -0
  24. package/dist/src/browser/adapters/gemini.js.map +0 -0
  25. package/dist/src/browser/adapters/google.d.ts +0 -0
  26. package/dist/src/browser/adapters/google.d.ts.map +0 -0
  27. package/dist/src/browser/adapters/google.js +0 -0
  28. package/dist/src/browser/adapters/google.js.map +0 -0
  29. package/dist/src/browser/adapters/index.d.ts +0 -0
  30. package/dist/src/browser/adapters/index.d.ts.map +0 -0
  31. package/dist/src/browser/adapters/index.js +0 -0
  32. package/dist/src/browser/adapters/index.js.map +0 -0
  33. package/dist/src/browser/adapters/instagram.d.ts +0 -0
  34. package/dist/src/browser/adapters/instagram.d.ts.map +0 -0
  35. package/dist/src/browser/adapters/instagram.js +0 -0
  36. package/dist/src/browser/adapters/instagram.js.map +0 -0
  37. package/dist/src/browser/adapters/linkedin.d.ts +0 -0
  38. package/dist/src/browser/adapters/linkedin.d.ts.map +0 -0
  39. package/dist/src/browser/adapters/linkedin.js +0 -0
  40. package/dist/src/browser/adapters/linkedin.js.map +0 -0
  41. package/dist/src/browser/adapters/microsoft.d.ts +0 -0
  42. package/dist/src/browser/adapters/microsoft.d.ts.map +0 -0
  43. package/dist/src/browser/adapters/microsoft.js +0 -0
  44. package/dist/src/browser/adapters/microsoft.js.map +0 -0
  45. package/dist/src/browser/adapters/x.d.ts +0 -0
  46. package/dist/src/browser/adapters/x.d.ts.map +0 -0
  47. package/dist/src/browser/adapters/x.js +0 -0
  48. package/dist/src/browser/adapters/x.js.map +0 -0
  49. package/dist/src/browser/batch.d.ts +0 -0
  50. package/dist/src/browser/batch.d.ts.map +0 -0
  51. package/dist/src/browser/batch.js +0 -0
  52. package/dist/src/browser/batch.js.map +0 -0
  53. package/dist/src/browser/browser.d.ts +0 -0
  54. package/dist/src/browser/browser.d.ts.map +0 -0
  55. package/dist/src/browser/browser.js +0 -0
  56. package/dist/src/browser/browser.js.map +0 -0
  57. package/dist/src/browser/cdp.d.ts +0 -0
  58. package/dist/src/browser/cdp.d.ts.map +0 -0
  59. package/dist/src/browser/cdp.js +0 -0
  60. package/dist/src/browser/cdp.js.map +0 -0
  61. package/dist/src/browser/console-log.d.ts +0 -0
  62. package/dist/src/browser/console-log.d.ts.map +0 -0
  63. package/dist/src/browser/console-log.js +0 -0
  64. package/dist/src/browser/console-log.js.map +0 -0
  65. package/dist/src/browser/dashboard/api-types.d.ts +0 -0
  66. package/dist/src/browser/dashboard/api-types.d.ts.map +0 -0
  67. package/dist/src/browser/dashboard/api-types.js +0 -0
  68. package/dist/src/browser/dashboard/api-types.js.map +0 -0
  69. package/dist/src/browser/dashboard/server.d.ts +12 -1
  70. package/dist/src/browser/dashboard/server.d.ts.map +1 -1
  71. package/dist/src/browser/dashboard/server.js +4 -198
  72. package/dist/src/browser/dashboard/server.js.map +1 -1
  73. package/dist/src/browser/dashboard/ui.html +0 -0
  74. package/dist/src/browser/dialog.d.ts +0 -0
  75. package/dist/src/browser/dialog.d.ts.map +0 -0
  76. package/dist/src/browser/dialog.js +0 -0
  77. package/dist/src/browser/dialog.js.map +0 -0
  78. package/dist/src/browser/emulation.d.ts +0 -0
  79. package/dist/src/browser/emulation.d.ts.map +0 -0
  80. package/dist/src/browser/emulation.js +0 -0
  81. package/dist/src/browser/emulation.js.map +0 -0
  82. package/dist/src/browser/find.d.ts +0 -0
  83. package/dist/src/browser/find.d.ts.map +0 -0
  84. package/dist/src/browser/find.js +0 -0
  85. package/dist/src/browser/find.js.map +0 -0
  86. package/dist/src/browser/har.d.ts +0 -0
  87. package/dist/src/browser/har.d.ts.map +0 -0
  88. package/dist/src/browser/har.js +0 -0
  89. package/dist/src/browser/har.js.map +0 -0
  90. package/dist/src/browser/index.d.ts +1 -0
  91. package/dist/src/browser/index.d.ts.map +1 -1
  92. package/dist/src/browser/index.js +1 -0
  93. package/dist/src/browser/index.js.map +1 -1
  94. package/dist/src/browser/network.d.ts +0 -0
  95. package/dist/src/browser/network.d.ts.map +0 -0
  96. package/dist/src/browser/network.js +0 -0
  97. package/dist/src/browser/network.js.map +0 -0
  98. package/dist/src/browser/pdf.d.ts +0 -0
  99. package/dist/src/browser/pdf.d.ts.map +0 -0
  100. package/dist/src/browser/pdf.js +0 -0
  101. package/dist/src/browser/pdf.js.map +0 -0
  102. package/dist/src/browser/profiler.d.ts +0 -0
  103. package/dist/src/browser/profiler.d.ts.map +0 -0
  104. package/dist/src/browser/profiler.js +0 -0
  105. package/dist/src/browser/profiler.js.map +0 -0
  106. package/dist/src/browser/record.d.ts +0 -0
  107. package/dist/src/browser/record.d.ts.map +0 -0
  108. package/dist/src/browser/record.js +0 -0
  109. package/dist/src/browser/record.js.map +0 -0
  110. package/dist/src/browser/ref-cache.d.ts +21 -0
  111. package/dist/src/browser/ref-cache.d.ts.map +1 -0
  112. package/dist/src/browser/ref-cache.js +70 -0
  113. package/dist/src/browser/ref-cache.js.map +1 -0
  114. package/dist/src/browser/screenshot.d.ts +0 -0
  115. package/dist/src/browser/screenshot.d.ts.map +0 -0
  116. package/dist/src/browser/screenshot.js +0 -0
  117. package/dist/src/browser/screenshot.js.map +0 -0
  118. package/dist/src/browser/session.d.ts +0 -0
  119. package/dist/src/browser/session.d.ts.map +0 -0
  120. package/dist/src/browser/session.js +0 -0
  121. package/dist/src/browser/session.js.map +0 -0
  122. package/dist/src/browser/snapshot.d.ts +0 -0
  123. package/dist/src/browser/snapshot.d.ts.map +0 -0
  124. package/dist/src/browser/snapshot.js +0 -0
  125. package/dist/src/browser/snapshot.js.map +0 -0
  126. package/dist/src/browser/storage.d.ts +0 -0
  127. package/dist/src/browser/storage.d.ts.map +0 -0
  128. package/dist/src/browser/storage.js +0 -0
  129. package/dist/src/browser/storage.js.map +0 -0
  130. package/dist/src/browser/tabs.d.ts +1 -1
  131. package/dist/src/browser/tabs.d.ts.map +1 -1
  132. package/dist/src/browser/tabs.js +20 -4
  133. package/dist/src/browser/tabs.js.map +1 -1
  134. package/dist/src/browser/trace.d.ts +0 -0
  135. package/dist/src/browser/trace.d.ts.map +0 -0
  136. package/dist/src/browser/trace.js +0 -0
  137. package/dist/src/browser/trace.js.map +0 -0
  138. package/dist/src/browser/types.d.ts +0 -0
  139. package/dist/src/browser/types.d.ts.map +0 -0
  140. package/dist/src/browser/types.js +0 -0
  141. package/dist/src/browser/types.js.map +0 -0
  142. package/dist/src/browser/vitals.d.ts +0 -0
  143. package/dist/src/browser/vitals.d.ts.map +0 -0
  144. package/dist/src/browser/vitals.js +0 -0
  145. package/dist/src/browser/vitals.js.map +0 -0
  146. package/dist/src/browser/wait.d.ts +0 -0
  147. package/dist/src/browser/wait.d.ts.map +0 -0
  148. package/dist/src/browser/wait.js +0 -0
  149. package/dist/src/browser/wait.js.map +0 -0
  150. package/dist/src/cli/action.d.ts +0 -0
  151. package/dist/src/cli/action.d.ts.map +1 -1
  152. package/dist/src/cli/action.js +92 -4
  153. package/dist/src/cli/action.js.map +1 -1
  154. package/dist/src/cli/commands.d.ts +0 -0
  155. package/dist/src/cli/commands.d.ts.map +1 -1
  156. package/dist/src/cli/commands.js +32 -6
  157. package/dist/src/cli/commands.js.map +1 -1
  158. package/dist/src/cli/output.d.ts +0 -0
  159. package/dist/src/cli/output.d.ts.map +0 -0
  160. package/dist/src/cli/output.js +0 -0
  161. package/dist/src/cli/output.js.map +0 -0
  162. package/dist/src/cli/platform.d.ts +0 -0
  163. package/dist/src/cli/platform.d.ts.map +0 -0
  164. package/dist/src/cli/platform.js +0 -0
  165. package/dist/src/cli/platform.js.map +0 -0
  166. package/dist/src/cli/types.d.ts +0 -0
  167. package/dist/src/cli/types.d.ts.map +0 -0
  168. package/dist/src/cli/types.js +0 -0
  169. package/dist/src/cli/types.js.map +0 -0
  170. package/dist/src/cli.d.ts +0 -0
  171. package/dist/src/cli.d.ts.map +0 -0
  172. package/dist/src/cli.js.map +0 -0
  173. package/dist/src/index.d.ts +0 -5
  174. package/dist/src/index.d.ts.map +1 -1
  175. package/dist/src/index.js +0 -16
  176. package/dist/src/index.js.map +1 -1
  177. package/dist/tsconfig.tsbuildinfo +1 -1
  178. package/package.json +6 -15
  179. package/src/__tests__/output.test.ts +62 -0
  180. package/src/__tests__/ref-cache.test.ts +134 -0
  181. package/src/browser/action-builder/analyzer.ts +41 -23
  182. package/src/browser/action-builder/types.ts +0 -0
  183. package/src/browser/actions.ts +0 -0
  184. package/src/browser/adapters/gemini.ts +0 -0
  185. package/src/browser/adapters/google.ts +0 -0
  186. package/src/browser/adapters/index.ts +0 -0
  187. package/src/browser/adapters/instagram.ts +0 -0
  188. package/src/browser/adapters/linkedin.ts +0 -0
  189. package/src/browser/adapters/microsoft.ts +0 -0
  190. package/src/browser/adapters/x.ts +0 -0
  191. package/src/browser/batch.ts +0 -0
  192. package/src/browser/browser.ts +0 -0
  193. package/src/browser/cdp.ts +0 -0
  194. package/src/browser/console-log.ts +0 -0
  195. package/src/browser/dashboard/api-types.ts +0 -0
  196. package/src/browser/dashboard/server.ts +16 -198
  197. package/src/browser/dashboard/ui.html +0 -0
  198. package/src/browser/dialog.ts +0 -0
  199. package/src/browser/emulation.ts +0 -0
  200. package/src/browser/find.ts +0 -0
  201. package/src/browser/har.ts +0 -0
  202. package/src/browser/index.ts +1 -0
  203. package/src/browser/network.ts +0 -0
  204. package/src/browser/pdf.ts +0 -0
  205. package/src/browser/profiler.ts +0 -0
  206. package/src/browser/record.ts +0 -0
  207. package/src/browser/ref-cache.ts +90 -0
  208. package/src/browser/screenshot.ts +0 -0
  209. package/src/browser/session.ts +0 -0
  210. package/src/browser/snapshot.ts +0 -0
  211. package/src/browser/storage.ts +0 -0
  212. package/src/browser/tabs.ts +36 -7
  213. package/src/browser/trace.ts +0 -0
  214. package/src/browser/types.ts +0 -0
  215. package/src/browser/vitals.ts +0 -0
  216. package/src/browser/wait.ts +0 -0
  217. package/src/cli/action.ts +91 -5
  218. package/src/cli/commands.ts +35 -6
  219. package/src/cli/output.ts +0 -0
  220. package/src/cli/platform.ts +0 -0
  221. package/src/cli/types.ts +0 -0
  222. package/src/cli.ts +0 -0
  223. package/src/index.ts +0 -20
  224. package/dist/package.json +0 -75
  225. package/dist/src/browser/playbook/browser-handlers.d.ts +0 -3
  226. package/dist/src/browser/playbook/browser-handlers.d.ts.map +0 -1
  227. package/dist/src/browser/playbook/browser-handlers.js +0 -377
  228. package/dist/src/browser/playbook/browser-handlers.js.map +0 -1
  229. package/dist/src/browser/playbook/builtin-handlers.d.ts +0 -3
  230. package/dist/src/browser/playbook/builtin-handlers.d.ts.map +0 -1
  231. package/dist/src/browser/playbook/builtin-handlers.js +0 -364
  232. package/dist/src/browser/playbook/builtin-handlers.js.map +0 -1
  233. package/dist/src/browser/playbook/index.d.ts +0 -3
  234. package/dist/src/browser/playbook/index.d.ts.map +0 -1
  235. package/dist/src/browser/playbook/index.js +0 -3
  236. package/dist/src/browser/playbook/index.js.map +0 -1
  237. package/dist/src/browser/playbook/store.d.ts +0 -8
  238. package/dist/src/browser/playbook/store.d.ts.map +0 -1
  239. package/dist/src/browser/playbook/store.js +0 -87
  240. package/dist/src/browser/playbook/store.js.map +0 -1
  241. package/dist/src/browser/workflow/builtin-handlers.d.ts +0 -3
  242. package/dist/src/browser/workflow/builtin-handlers.d.ts.map +0 -1
  243. package/dist/src/browser/workflow/builtin-handlers.js +0 -343
  244. package/dist/src/browser/workflow/builtin-handlers.js.map +0 -1
  245. package/dist/src/browser/workflow/engine.d.ts +0 -10
  246. package/dist/src/browser/workflow/engine.d.ts.map +0 -1
  247. package/dist/src/browser/workflow/engine.js +0 -174
  248. package/dist/src/browser/workflow/engine.js.map +0 -1
  249. package/dist/src/browser/workflow/expression.d.ts +0 -4
  250. package/dist/src/browser/workflow/expression.d.ts.map +0 -1
  251. package/dist/src/browser/workflow/expression.js +0 -89
  252. package/dist/src/browser/workflow/expression.js.map +0 -1
  253. package/dist/src/browser/workflow/index.d.ts +0 -6
  254. package/dist/src/browser/workflow/index.d.ts.map +0 -1
  255. package/dist/src/browser/workflow/index.js +0 -6
  256. package/dist/src/browser/workflow/index.js.map +0 -1
  257. package/dist/src/browser/workflow/store.d.ts +0 -8
  258. package/dist/src/browser/workflow/store.d.ts.map +0 -1
  259. package/dist/src/browser/workflow/store.js +0 -87
  260. package/dist/src/browser/workflow/store.js.map +0 -1
  261. package/dist/src/browser/workflow/types.d.ts +0 -62
  262. package/dist/src/browser/workflow/types.d.ts.map +0 -1
  263. package/dist/src/browser/workflow/types.js +0 -2
  264. package/dist/src/browser/workflow/types.js.map +0 -1
  265. package/dist/src/cli/playbook.d.ts +0 -3
  266. package/dist/src/cli/playbook.d.ts.map +0 -1
  267. package/dist/src/cli/playbook.js +0 -265
  268. package/dist/src/cli/playbook.js.map +0 -1
  269. package/dist/src/cli/workflow.d.ts +0 -2
  270. package/dist/src/cli/workflow.d.ts.map +0 -1
  271. package/dist/src/cli/workflow.js +0 -3
  272. package/dist/src/cli/workflow.js.map +0 -1
  273. package/src/browser/playbook/browser-handlers.ts +0 -449
  274. package/src/browser/playbook/builtin-handlers.ts +0 -400
  275. package/src/browser/playbook/index.ts +0 -2
  276. package/src/browser/playbook/store.ts +0 -87
  277. package/src/cli/playbook.ts +0 -280
@@ -0,0 +1,134 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtemp, readFile, rm, stat } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ // We need to mock process.cwd() before importing ref-cache so CACHE_DIR
7
+ // resolves to our temp directory.
8
+ let tempDir: string;
9
+
10
+ beforeEach(async () => {
11
+ tempDir = await mkdtemp(join(tmpdir(), 'monobrowse-test-'));
12
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
13
+ });
14
+
15
+ afterEach(async () => {
16
+ vi.restoreAllMocks();
17
+ await rm(tempDir, { recursive: true, force: true });
18
+ });
19
+
20
+ // Dynamic import so each test picks up the mocked cwd.
21
+ // We must bust the module cache between tests.
22
+ async function importRefCache() {
23
+ // Vitest module cache means re-import returns the same module.
24
+ // Since CACHE_DIR is set at module-load time, we need resetModules.
25
+ vi.resetModules();
26
+ return import('../browser/ref-cache.js');
27
+ }
28
+
29
+ function makeRefs(count = 2): Map<string, { ref: string; role: string; name: string; nodeId: number }> {
30
+ const map = new Map();
31
+ for (let i = 1; i <= count; i++) {
32
+ map.set(`ref${i}`, { ref: `ref${i}`, role: 'button', name: `Button ${i}`, nodeId: i });
33
+ }
34
+ return map;
35
+ }
36
+
37
+ describe('ref-cache', () => {
38
+ it('round-trip: save then load returns equivalent data', async () => {
39
+ const mod = await importRefCache();
40
+ const refs = makeRefs(2);
41
+
42
+ await mod.saveRefCache('target-1', 'https://example.com', refs as any);
43
+ const loaded = await mod.loadRefCache('target-1');
44
+
45
+ expect(loaded).not.toBeNull();
46
+ expect(loaded!.url).toBe('https://example.com');
47
+ expect(loaded!.refs.size).toBe(2);
48
+ expect(loaded!.refs.get('ref1')!.role).toBe('button');
49
+ expect(loaded!.refs.get('ref2')!.name).toBe('Button 2');
50
+ expect(typeof loaded!.savedAt).toBe('number');
51
+ expect(typeof loaded!.ageMs).toBe('number');
52
+ });
53
+
54
+ it('targetId mismatch returns null', async () => {
55
+ const mod = await importRefCache();
56
+ const refs = makeRefs(1);
57
+
58
+ await mod.saveRefCache('target-A', 'https://a.com', refs as any);
59
+ const loaded = await mod.loadRefCache('target-B');
60
+
61
+ expect(loaded).toBeNull();
62
+ });
63
+
64
+ it('corrupt JSON file returns null', async () => {
65
+ const mod = await importRefCache();
66
+ // First save a valid cache to create the directory structure
67
+ await mod.saveRefCache('t', 'https://x.com', makeRefs(1) as any);
68
+
69
+ // Now overwrite the file with invalid JSON
70
+ const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
71
+ const { writeFile: wf } = await import('fs/promises');
72
+ await wf(cacheFile, '{{{not valid json!!!');
73
+
74
+ const loaded = await mod.loadRefCache('t');
75
+ expect(loaded).toBeNull();
76
+ });
77
+
78
+ it('clearRefCache removes the cache file', async () => {
79
+ const mod = await importRefCache();
80
+ await mod.saveRefCache('t1', 'https://x.com', makeRefs(1) as any);
81
+
82
+ const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
83
+ // Verify file exists
84
+ const before = await stat(cacheFile).catch(() => null);
85
+ expect(before).not.toBeNull();
86
+
87
+ await mod.clearRefCache();
88
+
89
+ const after = await stat(cacheFile).catch(() => null);
90
+ expect(after).toBeNull();
91
+ });
92
+
93
+ it('load after clear returns null', async () => {
94
+ const mod = await importRefCache();
95
+ await mod.saveRefCache('t1', 'https://x.com', makeRefs(1) as any);
96
+ await mod.clearRefCache();
97
+
98
+ const loaded = await mod.loadRefCache('t1');
99
+ expect(loaded).toBeNull();
100
+ });
101
+
102
+ it('freshly saved cache is not stale', async () => {
103
+ const mod = await importRefCache();
104
+ await mod.saveRefCache('t1', 'https://x.com', makeRefs(1) as any);
105
+
106
+ const loaded = await mod.loadRefCache('t1');
107
+ expect(loaded).not.toBeNull();
108
+ expect(loaded!.stale).toBe(false);
109
+ expect(loaded!.ageMs).toBeLessThan(mod.REF_CACHE_STALE_MS);
110
+ });
111
+
112
+ it('cache older than REF_CACHE_STALE_MS is flagged stale', async () => {
113
+ const mod = await importRefCache();
114
+ await mod.saveRefCache('t1', 'https://x.com', makeRefs(1) as any);
115
+
116
+ // Manually backdate the savedAt timestamp
117
+ const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
118
+ const raw = JSON.parse(await readFile(cacheFile, 'utf8'));
119
+ raw.savedAt = Date.now() - mod.REF_CACHE_STALE_MS - 5000;
120
+ const { writeFile: wf } = await import('fs/promises');
121
+ await wf(cacheFile, JSON.stringify(raw));
122
+
123
+ const loaded = await mod.loadRefCache('t1');
124
+ expect(loaded).not.toBeNull();
125
+ expect(loaded!.stale).toBe(true);
126
+ expect(loaded!.ageMs).toBeGreaterThan(mod.REF_CACHE_STALE_MS);
127
+ });
128
+
129
+ it('loading with no cache file returns null (no crash)', async () => {
130
+ const mod = await importRefCache();
131
+ const loaded = await mod.loadRefCache('nonexistent');
132
+ expect(loaded).toBeNull();
133
+ });
134
+ });
@@ -1,15 +1,14 @@
1
1
  // src/browser/action-builder/analyzer.ts
2
- import Anthropic from '@anthropic-ai/sdk';
2
+ import { spawn } from 'child_process';
3
3
  import type { ActionDef } from './types.js';
4
- const MODEL_DEFAULTS = { sonnet: 'claude-sonnet-4-6' } as const;
5
4
 
6
5
  export interface AnalyzerPage {
7
6
  evaluate<T>(expression: string): Promise<T>;
8
7
  url(): Promise<string>;
9
8
  }
10
9
 
10
+ // monolean: no options needed — routes through claude --print, no API key required
11
11
  export interface AnalyzerOptions {
12
- apiKey?: string;
13
12
  model?: string;
14
13
  }
15
14
 
@@ -42,6 +41,40 @@ JSON.stringify(
42
41
  }))
43
42
  )`;
44
43
 
44
+ function claudeCliCall(prompt: string): Promise<string> {
45
+ return new Promise((resolve, reject) => {
46
+ const child = spawn(
47
+ 'claude',
48
+ ['--print', '--model', 'haiku', '--strict-mcp-config', '--no-session-persistence', '--', prompt],
49
+ { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true },
50
+ );
51
+ let stdout = '';
52
+ let stderr = '';
53
+ let settled = false;
54
+ const timer = setTimeout(() => {
55
+ if (settled) return;
56
+ settled = true;
57
+ try { child.kill('SIGTERM'); } catch { /* ignore */ }
58
+ reject(new Error('claude --print timed out after 60s'));
59
+ }, 60_000);
60
+ child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
61
+ child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
62
+ child.on('close', (code) => {
63
+ if (settled) return;
64
+ settled = true;
65
+ clearTimeout(timer);
66
+ if (code === 0) resolve(stdout.trim());
67
+ else reject(new Error(stderr.trim() || `claude exited with code ${code}`));
68
+ });
69
+ child.on('error', (err) => {
70
+ if (settled) return;
71
+ settled = true;
72
+ clearTimeout(timer);
73
+ reject(new Error(`claude CLI not found — is Claude Code installed? (${(err as NodeJS.ErrnoException).code})`));
74
+ });
75
+ });
76
+ }
77
+
45
78
  const SYSTEM_PROMPT = `You are an expert browser automation engineer. Given a webpage's interactive elements and a task description, generate a browser automation ActionDef JSON.
46
79
 
47
80
  Rules:
@@ -94,33 +127,18 @@ ${elements.map((el, i) => {
94
127
  return `${i + 1}. <${el.tag}${attrs ? ' ' + attrs : ''}>${el.text}</${el.tag}>`;
95
128
  }).join('\n')}`;
96
129
 
97
- const client = new Anthropic({ apiKey: options.apiKey ?? process.env['ANTHROPIC_API_KEY'] });
98
- const model = options.model ?? MODEL_DEFAULTS.sonnet;
99
-
100
- const message = await client.messages.create({
101
- model,
102
- max_tokens: 2048,
103
- system: SYSTEM_PROMPT,
104
- messages: [
105
- {
106
- role: 'user',
107
- content: `Task: ${task}\n\nPage context:\n${domContext}`,
108
- },
109
- ],
110
- });
111
-
112
- const content = message.content[0];
113
- if (content.type !== 'text') throw new Error('Claude returned non-text response');
130
+ const fullPrompt = `${SYSTEM_PROMPT}\n\nTask: ${task}\n\nPage context:\n${domContext}`;
131
+ const responseText = await claudeCliCall(fullPrompt);
114
132
 
115
133
  let actionDef: ActionDef;
116
134
  try {
117
- actionDef = JSON.parse(content.text) as ActionDef;
135
+ actionDef = JSON.parse(responseText) as ActionDef;
118
136
  } catch {
119
- throw new Error(`Claude returned invalid JSON:\n${content.text.slice(0, 500)}`);
137
+ throw new Error(`Claude returned invalid JSON:\n${responseText.slice(0, 500)}`);
120
138
  }
121
139
 
122
140
  if (!actionDef.id || !actionDef.steps || !Array.isArray(actionDef.steps)) {
123
- throw new Error(`Claude returned invalid ActionDef: missing id or steps\n${content.text.slice(0, 500)}`);
141
+ throw new Error(`Claude returned invalid ActionDef: missing id or steps\n${responseText.slice(0, 500)}`);
124
142
  }
125
143
 
126
144
  return actionDef;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -5,23 +5,23 @@ import { join, dirname } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { homedir } from 'node:os';
7
7
  import { createRequire } from 'node:module';
8
- import type { StepEvent, RunRecord, PlaybookDef } from '@monoes/monoplaybook';
9
8
 
10
- const RUNS_FILE = join(homedir(), '.monomind', 'browse-runs.json');
11
- const PLAYBOOKS_FILE = join(homedir(), '.monomind', 'playbooks.json');
12
-
13
- async function loadPlaybooks(): Promise<PlaybookDef[]> {
14
- if (!existsSync(PLAYBOOKS_FILE)) return [];
15
- try {
16
- return JSON.parse(await readFile(PLAYBOOKS_FILE, 'utf-8')) as PlaybookDef[];
17
- } catch { return []; }
9
+ export interface StepEvent {
10
+ type: string;
11
+ projectDir?: string;
12
+ [key: string]: unknown;
18
13
  }
19
14
 
20
- async function savePlaybooks(playbooks: PlaybookDef[]): Promise<void> {
21
- await mkdir(join(homedir(), '.monomind'), { recursive: true });
22
- await writeFile(PLAYBOOKS_FILE, JSON.stringify(playbooks, null, 2));
15
+ export interface RunRecord {
16
+ id: string;
17
+ startedAt: number;
18
+ status: string;
19
+ itemsProcessed?: number;
20
+ [key: string]: unknown;
23
21
  }
24
22
 
23
+ const RUNS_FILE = join(homedir(), '.monomind', 'browse-runs.json');
24
+
25
25
  function readBody(req: IncomingMessage): Promise<string> {
26
26
  return new Promise((resolve, reject) => {
27
27
  let body = '';
@@ -90,7 +90,6 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
90
90
  }
91
91
 
92
92
  if (url === '/runs' && req.method === 'GET') {
93
- // Merge in-memory runs with persisted runs so the UI shows history even across restarts
94
93
  const persisted = await loadPersistedRuns().catch(() => [] as RunRecord[]);
95
94
  const seen = new Set(runHistory.map(r => r.id));
96
95
  const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
@@ -109,12 +108,9 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
109
108
  return;
110
109
  }
111
110
 
112
- // Parse the URL to correctly handle query parameters such as ?dir=
113
111
  const parsed = new URL(url, 'http://localhost');
114
112
  if (parsed.pathname === '/events' && (req.method === 'GET' || req.method === 'HEAD') && !WebSocketServer) {
115
- // SSE endpoint (fallback when ws not available).
116
- // No CORS header — the dashboard is served from 127.0.0.1:4242 and no cross-origin
117
- // access is needed. A wildcard ACAO would let any web page subscribe to playbook events.
113
+ // SSE fallback when ws not available
118
114
  const subscribedDir = parsed.searchParams.get('dir') ?? null;
119
115
  res.writeHead(200, {
120
116
  'Content-Type': 'text/event-stream',
@@ -123,7 +119,6 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
123
119
  });
124
120
  res.write(`data: ${JSON.stringify({ type: 'connected' })}\n\n`);
125
121
  clientDirs.set(res, subscribedDir);
126
- // 30-second keep-alive heartbeat prevents proxy idle-timeout drops.
127
122
  const heartbeat = setInterval(() => {
128
123
  try { res.write(': keep-alive\n\n'); } catch { clearInterval(heartbeat); }
129
124
  }, 30_000);
@@ -134,176 +129,6 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
134
129
  return;
135
130
  }
136
131
 
137
- // ── Playbook CRUD ────────────────────────────────────────────────────
138
- // GET /api/playbooks → list all saved playbooks
139
- // POST /api/playbooks → create/update a playbook (body: PlaybookDef)
140
- // GET /api/playbooks/:id → get single playbook
141
- // DELETE /api/playbooks/:id → delete a playbook
142
- // POST /api/playbooks/:id/run → run a playbook (delegates to engine)
143
- if (parsed.pathname === '/api/playbooks' && req.method === 'GET') {
144
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
145
- res.writeHead(200, { 'Content-Type': 'application/json' });
146
- res.end(JSON.stringify(list));
147
- return;
148
- }
149
-
150
- if (parsed.pathname === '/api/playbooks' && req.method === 'POST') {
151
- try {
152
- const raw = await readBody(req);
153
- const pb = JSON.parse(raw) as PlaybookDef;
154
- if (!pb.id || !pb.name) {
155
- res.writeHead(400, { 'Content-Type': 'application/json' });
156
- res.end(JSON.stringify({ error: 'id and name are required' }));
157
- return;
158
- }
159
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
160
- const idx = list.findIndex(w => w.id === pb.id);
161
- if (idx >= 0) list[idx] = pb; else list.push(pb);
162
- await savePlaybooks(list);
163
- res.writeHead(200, { 'Content-Type': 'application/json' });
164
- res.end(JSON.stringify(pb));
165
- } catch (e) {
166
- res.writeHead(500, { 'Content-Type': 'application/json' });
167
- res.end(JSON.stringify({ error: String(e) }));
168
- }
169
- return;
170
- }
171
-
172
- const pbIdMatch = parsed.pathname.match(/^\/api\/playbooks\/([^/]+)$/);
173
- const pbRunMatch = parsed.pathname.match(/^\/api\/playbooks\/([^/]+)\/run$/);
174
-
175
- if (pbRunMatch && req.method === 'POST') {
176
- const pbId = pbRunMatch[1];
177
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
178
- const pb = list.find(w => w.id === pbId);
179
- if (!pb) {
180
- res.writeHead(404, { 'Content-Type': 'application/json' });
181
- res.end(JSON.stringify({ error: `Playbook ${pbId} not found` }));
182
- return;
183
- }
184
- // Import engine and builtin handlers dynamically to avoid circular deps at startup
185
- res.writeHead(202, { 'Content-Type': 'application/json' });
186
- res.end(JSON.stringify({ ok: true, playbookId: pbId, message: 'Playbook run started' }));
187
- // Run async, broadcast events via dashboard
188
- Promise.resolve().then(async () => {
189
- try {
190
- const { runPlaybook, createDefaultHandlers } = await import('../../index.js');
191
- const handlers = createDefaultHandlers();
192
- const record = await runPlaybook(pb, {
193
- handlers,
194
- onEvent: (evt) => { instance?.broadcast(evt); },
195
- isStopRequested: (id) => instance?.isStopRequested(id) ?? false,
196
- });
197
- instance?.addRunRecord(record);
198
- } catch (err) {
199
- console.error('[dashboard] playbook run error:', err);
200
- }
201
- });
202
- return;
203
- }
204
-
205
- if (pbIdMatch && req.method === 'GET') {
206
- const pbId = pbIdMatch[1];
207
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
208
- const pb = list.find(w => w.id === pbId);
209
- if (!pb) { res.writeHead(404); res.end('Not found'); return; }
210
- res.writeHead(200, { 'Content-Type': 'application/json' });
211
- res.end(JSON.stringify(pb));
212
- return;
213
- }
214
-
215
- if (pbIdMatch && req.method === 'DELETE') {
216
- const pbId = pbIdMatch[1];
217
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
218
- const newList = list.filter(w => w.id !== pbId);
219
- await savePlaybooks(newList);
220
- res.writeHead(200, { 'Content-Type': 'application/json' });
221
- res.end(JSON.stringify({ ok: true, deleted: pbId }));
222
- return;
223
- }
224
-
225
- // ── Webhook trigger ──────────────────────────────────────────────────────
226
- // POST /webhooks/:playbookId
227
- // Body (optional): JSON object or array — merged as `params` into the playbook run.
228
- // Header (optional): X-Webhook-Signature: sha256=<hex> for HMAC verification.
229
- // Response: 202 { ok, runId, playbookId } — run executes asynchronously.
230
- const webhookMatch = parsed.pathname.match(/^\/webhooks\/([^/]+)$/);
231
- if (webhookMatch && req.method === 'POST') {
232
- const pbId = webhookMatch[1];
233
- const list = await loadPlaybooks().catch(() => [] as PlaybookDef[]);
234
- const pb = list.find(w => w.id === pbId);
235
- if (!pb) {
236
- res.writeHead(404, { 'Content-Type': 'application/json' });
237
- res.end(JSON.stringify({ error: `Playbook ${pbId} not found` }));
238
- return;
239
- }
240
-
241
- const rawBody = await readBody(req).catch(() => '');
242
-
243
- // Optional HMAC-SHA256 signature verification
244
- const sigHeader = (req.headers['x-webhook-signature'] as string | undefined) ?? '';
245
- if (sigHeader) {
246
- try {
247
- const { createHmac } = await import('node:crypto');
248
- // Secret is stored in the playbook's trigger config (first schedule/webhook trigger)
249
- const triggers = (pb as unknown as Record<string, unknown>)['triggers'];
250
- const secret: string | undefined =
251
- Array.isArray(triggers)
252
- ? (triggers as Array<Record<string, unknown>>).find(t => t['secret'])?.[
253
- 'secret'
254
- ] as string | undefined
255
- : undefined;
256
- if (secret) {
257
- const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
258
- if (sigHeader !== expected) {
259
- res.writeHead(403, { 'Content-Type': 'application/json' });
260
- res.end(JSON.stringify({ error: 'Invalid webhook signature' }));
261
- return;
262
- }
263
- }
264
- } catch {
265
- // crypto not available or other error — skip verification
266
- }
267
- }
268
-
269
- // Parse body into params (string values only, per engine contract)
270
- const params: Record<string, string> = {};
271
- try {
272
- if (rawBody.trim()) {
273
- const parsed2 = JSON.parse(rawBody);
274
- if (parsed2 && typeof parsed2 === 'object' && !Array.isArray(parsed2)) {
275
- for (const [k, v] of Object.entries(parsed2 as Record<string, unknown>)) {
276
- params[k] = typeof v === 'string' ? v : JSON.stringify(v);
277
- }
278
- }
279
- params['__body'] = rawBody;
280
- }
281
- } catch {
282
- params['__body'] = rawBody;
283
- }
284
-
285
- const runId = `webhook-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
286
- res.writeHead(202, { 'Content-Type': 'application/json' });
287
- res.end(JSON.stringify({ ok: true, runId, playbookId: pbId }));
288
-
289
- Promise.resolve().then(async () => {
290
- try {
291
- const { runPlaybook, createDefaultHandlers } = await import('../../index.js');
292
- const handlers = createDefaultHandlers();
293
- const record = await runPlaybook(pb, {
294
- handlers,
295
- params,
296
- onEvent: (evt) => { instance?.broadcast(evt); },
297
- isStopRequested: (id) => instance?.isStopRequested(id) ?? false,
298
- });
299
- instance?.addRunRecord(record);
300
- } catch (err) {
301
- console.error('[dashboard] webhook run error:', err);
302
- }
303
- });
304
- return;
305
- }
306
-
307
132
  res.writeHead(404);
308
133
  res.end('Not found');
309
134
  });
@@ -311,12 +136,10 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
311
136
  if (WebSocketServer) {
312
137
  const wss = new WebSocketServer({ server });
313
138
  wss.on('connection', async (ws: any, req: any) => {
314
- // Extract optional project-dir filter from the WebSocket upgrade URL
315
139
  const upgradeUrl = req?.url ?? '/ws';
316
140
  const upgradeParsed = new URL(upgradeUrl, 'http://localhost');
317
141
  const subscribedDir = upgradeParsed.searchParams.get('dir') ?? null;
318
142
  clientDirs.set(ws, subscribedDir);
319
- // Merge in-memory and persisted runs so history survives server restarts
320
143
  const persisted = await loadPersistedRuns().catch(() => [] as RunRecord[]);
321
144
  const seen = new Set(runHistory.map(r => r.id));
322
145
  const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
@@ -329,11 +152,10 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
329
152
 
330
153
  server.on('error', (err: NodeJS.ErrnoException) => {
331
154
  if (err.code === 'EADDRINUSE') {
332
- console.log(`Dashboard port ${port} in use — playbook will run without live dashboard.`);
155
+ console.log(`Dashboard port ${port} in use — continuing without live dashboard.`);
333
156
  } else {
334
157
  console.error(`Dashboard server error: ${err.message}`);
335
158
  }
336
- // Replace with a no-op instance so the engine doesn't crash
337
159
  instance = {
338
160
  broadcast: () => {},
339
161
  addRunRecord: () => {},
@@ -348,17 +170,14 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
348
170
  function broadcast(event: StepEvent): void {
349
171
  const msg = JSON.stringify(event);
350
172
  for (const [client, subscribedDir] of clientDirs) {
351
- // Server-side project filtering: if the client subscribed with ?dir=<path>, only
352
- // deliver events whose projectDir matches. Clients without a dir filter (subscribedDir
353
- // === null) receive all events, preserving backward-compatibility with ui.html.
354
173
  if (subscribedDir !== null && event.projectDir !== undefined && subscribedDir !== event.projectDir) {
355
174
  continue;
356
175
  }
357
176
  try {
358
177
  if (typeof client.send === 'function') {
359
- client.send(msg); // WebSocket
178
+ client.send(msg);
360
179
  } else {
361
- client.write(`data: ${msg}\n\n`); // SSE
180
+ client.write(`data: ${msg}\n\n`);
362
181
  }
363
182
  } catch {
364
183
  clientDirs.delete(client);
@@ -374,7 +193,6 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
374
193
  runHistory.unshift(record);
375
194
  if (runHistory.length > MAX_RUN_HISTORY) runHistory.pop();
376
195
  }
377
- // Persist to disk so the CLI dashboard (/api/workflow-runs) can read run history
378
196
  const dir = join(homedir(), '.monomind');
379
197
  mkdir(dir, { recursive: true }).then(() =>
380
198
  writeFile(RUNS_FILE, JSON.stringify(runHistory, null, 2))
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -20,3 +20,4 @@ export * from './trace.js';
20
20
  export * from './profiler.js';
21
21
  export * from './vitals.js';
22
22
  export * from './har.js';
23
+ export * from './ref-cache.js';
File without changes
File without changes
File without changes
File without changes