@monoes/monobrowse 1.0.0 → 1.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 (101) hide show
  1. package/dist/package.json +75 -0
  2. package/dist/src/browser/adapters/index.d.ts.map +1 -1
  3. package/dist/src/browser/adapters/index.js +4 -0
  4. package/dist/src/browser/adapters/index.js.map +1 -1
  5. package/dist/src/browser/browser.js +2 -2
  6. package/dist/src/browser/browser.js.map +1 -1
  7. package/dist/src/browser/cdp.d.ts +6 -0
  8. package/dist/src/browser/cdp.d.ts.map +1 -1
  9. package/dist/src/browser/cdp.js +56 -1
  10. package/dist/src/browser/cdp.js.map +1 -1
  11. package/dist/src/browser/dashboard/api-types.d.ts +1 -1
  12. package/dist/src/browser/dashboard/server.d.ts +1 -1
  13. package/dist/src/browser/dashboard/server.d.ts.map +1 -1
  14. package/dist/src/browser/dashboard/server.js +204 -15
  15. package/dist/src/browser/dashboard/server.js.map +1 -1
  16. package/dist/src/browser/dashboard/ui.html +1811 -0
  17. package/dist/src/browser/playbook/browser-handlers.d.ts +3 -0
  18. package/dist/src/browser/playbook/browser-handlers.d.ts.map +1 -0
  19. package/dist/src/browser/playbook/browser-handlers.js +377 -0
  20. package/dist/src/browser/playbook/browser-handlers.js.map +1 -0
  21. package/dist/src/browser/playbook/builtin-handlers.d.ts +3 -0
  22. package/dist/src/browser/playbook/builtin-handlers.d.ts.map +1 -0
  23. package/dist/src/browser/playbook/builtin-handlers.js +364 -0
  24. package/dist/src/browser/playbook/builtin-handlers.js.map +1 -0
  25. package/dist/src/browser/playbook/index.d.ts +3 -0
  26. package/dist/src/browser/playbook/index.d.ts.map +1 -0
  27. package/dist/src/browser/playbook/index.js +3 -0
  28. package/dist/src/browser/playbook/index.js.map +1 -0
  29. package/dist/src/browser/playbook/store.d.ts +8 -0
  30. package/dist/src/browser/playbook/store.d.ts.map +1 -0
  31. package/dist/src/browser/playbook/store.js +87 -0
  32. package/dist/src/browser/playbook/store.js.map +1 -0
  33. package/dist/src/browser/screenshot.d.ts +2 -0
  34. package/dist/src/browser/screenshot.d.ts.map +1 -1
  35. package/dist/src/browser/screenshot.js +69 -2
  36. package/dist/src/browser/screenshot.js.map +1 -1
  37. package/dist/src/browser/types.d.ts.map +1 -1
  38. package/dist/src/browser/types.js +11 -0
  39. package/dist/src/browser/types.js.map +1 -1
  40. package/dist/src/cli/action.d.ts +3 -0
  41. package/dist/src/cli/action.d.ts.map +1 -0
  42. package/dist/src/cli/action.js +139 -0
  43. package/dist/src/cli/action.js.map +1 -0
  44. package/dist/src/cli/commands.d.ts +8 -0
  45. package/dist/src/cli/commands.d.ts.map +1 -0
  46. package/dist/src/cli/commands.js +3021 -0
  47. package/dist/src/cli/commands.js.map +1 -0
  48. package/dist/src/cli/output.d.ts +11 -0
  49. package/dist/src/cli/output.d.ts.map +1 -0
  50. package/dist/src/cli/output.js +33 -0
  51. package/dist/src/cli/output.js.map +1 -0
  52. package/dist/src/cli/platform.d.ts +7 -0
  53. package/dist/src/cli/platform.d.ts.map +1 -0
  54. package/dist/src/cli/platform.js +193 -0
  55. package/dist/src/cli/platform.js.map +1 -0
  56. package/dist/src/cli/playbook.d.ts +3 -0
  57. package/dist/src/cli/playbook.d.ts.map +1 -0
  58. package/dist/src/cli/playbook.js +265 -0
  59. package/dist/src/cli/playbook.js.map +1 -0
  60. package/dist/src/cli/types.d.ts +42 -0
  61. package/dist/src/cli/types.d.ts.map +1 -0
  62. package/dist/src/cli/types.js +2 -0
  63. package/dist/src/cli/types.js.map +1 -0
  64. package/dist/src/cli/workflow.d.ts +2 -0
  65. package/dist/src/cli/workflow.d.ts.map +1 -0
  66. package/dist/src/cli/workflow.js +3 -0
  67. package/dist/src/cli/workflow.js.map +1 -0
  68. package/dist/src/cli.d.ts +7 -0
  69. package/dist/src/cli.d.ts.map +1 -0
  70. package/dist/src/cli.js +187 -0
  71. package/dist/src/cli.js.map +1 -0
  72. package/dist/src/index.d.ts +5 -1
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/index.js +16 -1
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +24 -5
  78. package/src/browser/adapters/index.ts +4 -0
  79. package/src/browser/browser.ts +2 -2
  80. package/src/browser/cdp.ts +68 -1
  81. package/src/browser/dashboard/api-types.ts +1 -1
  82. package/src/browser/dashboard/server.ts +210 -17
  83. package/src/browser/dashboard/ui.html +1412 -45
  84. package/src/browser/playbook/browser-handlers.ts +449 -0
  85. package/src/browser/{workflow → playbook}/builtin-handlers.ts +35 -14
  86. package/src/browser/playbook/index.ts +2 -0
  87. package/src/browser/{workflow → playbook}/store.ts +14 -14
  88. package/src/browser/screenshot.ts +78 -3
  89. package/src/browser/types.ts +11 -0
  90. package/src/cli/action.ts +145 -0
  91. package/src/cli/commands.ts +2895 -0
  92. package/src/cli/output.ts +37 -0
  93. package/src/cli/platform.ts +211 -0
  94. package/src/cli/playbook.ts +280 -0
  95. package/src/cli/types.ts +47 -0
  96. package/src/cli.ts +208 -0
  97. package/src/index.ts +20 -1
  98. package/src/browser/workflow/engine.ts +0 -202
  99. package/src/browser/workflow/expression.ts +0 -107
  100. package/src/browser/workflow/index.ts +0 -5
  101. package/src/browser/workflow/types.ts +0 -62
@@ -1,13 +1,35 @@
1
- import { createServer } from 'node:http';
1
+ import { createServer, IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { readFileSync, existsSync } from 'node:fs';
3
- import { readFile } from 'node:fs/promises';
3
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
4
4
  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 } from '../workflow/types.js';
8
+ import type { StepEvent, RunRecord, PlaybookDef } from '@monoes/monoplaybook';
9
9
 
10
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 []; }
18
+ }
19
+
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));
23
+ }
24
+
25
+ function readBody(req: IncomingMessage): Promise<string> {
26
+ return new Promise((resolve, reject) => {
27
+ let body = '';
28
+ req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
29
+ req.on('end', () => resolve(body));
30
+ req.on('error', reject);
31
+ });
32
+ }
11
33
 
12
34
  async function loadPersistedRuns(): Promise<RunRecord[]> {
13
35
  if (!existsSync(RUNS_FILE)) return [];
@@ -58,7 +80,7 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
58
80
  uiHtml = `<!DOCTYPE html><html><head><title>monobrowse dashboard</title></head><body style="background:#0f0f1a;color:#ccc;font-family:system-ui;padding:20px"><h1>monobrowse dashboard</h1><p>Dashboard UI not found. Run the build to include ui.html.</p><script>let _retryDelay=1000;function connectSSE(){const es=new EventSource('/events');es.onmessage=e=>{console.log(JSON.parse(e.data));_retryDelay=1000;};es.onerror=()=>{es.close();setTimeout(connectSSE,Math.min(_retryDelay*=2,30000));};};connectSSE();</script></body></html>`;
59
81
  }
60
82
 
61
- const server = createServer((req, res) => {
83
+ const server = createServer(async (req, res) => {
62
84
  const url = req.url ?? '/';
63
85
 
64
86
  if (url === '/' && req.method === 'GET') {
@@ -69,17 +91,13 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
69
91
 
70
92
  if (url === '/runs' && req.method === 'GET') {
71
93
  // Merge in-memory runs with persisted runs so the UI shows history even across restarts
72
- loadPersistedRuns().then(persisted => {
73
- const seen = new Set(runHistory.map(r => r.id));
74
- const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
75
- .sort((a, b) => b.startedAt - a.startedAt)
76
- .slice(0, MAX_RUN_HISTORY);
77
- res.writeHead(200, { 'Content-Type': 'application/json' });
78
- res.end(JSON.stringify(merged));
79
- }).catch(() => {
80
- res.writeHead(200, { 'Content-Type': 'application/json' });
81
- res.end(JSON.stringify(runHistory));
82
- });
94
+ const persisted = await loadPersistedRuns().catch(() => [] as RunRecord[]);
95
+ const seen = new Set(runHistory.map(r => r.id));
96
+ const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
97
+ .sort((a, b) => b.startedAt - a.startedAt)
98
+ .slice(0, MAX_RUN_HISTORY);
99
+ res.writeHead(200, { 'Content-Type': 'application/json' });
100
+ res.end(JSON.stringify(merged));
83
101
  return;
84
102
  }
85
103
 
@@ -96,7 +114,7 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
96
114
  if (parsed.pathname === '/events' && (req.method === 'GET' || req.method === 'HEAD') && !WebSocketServer) {
97
115
  // SSE endpoint (fallback when ws not available).
98
116
  // No CORS header — the dashboard is served from 127.0.0.1:4242 and no cross-origin
99
- // access is needed. A wildcard ACAO would let any web page subscribe to workflow events.
117
+ // access is needed. A wildcard ACAO would let any web page subscribe to playbook events.
100
118
  const subscribedDir = parsed.searchParams.get('dir') ?? null;
101
119
  res.writeHead(200, {
102
120
  'Content-Type': 'text/event-stream',
@@ -116,6 +134,176 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
116
134
  return;
117
135
  }
118
136
 
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
+
119
307
  res.writeHead(404);
120
308
  res.end('Not found');
121
309
  });
@@ -141,7 +329,7 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
141
329
 
142
330
  server.on('error', (err: NodeJS.ErrnoException) => {
143
331
  if (err.code === 'EADDRINUSE') {
144
- console.log(`Dashboard port ${port} in use — workflow will run without live dashboard.`);
332
+ console.log(`Dashboard port ${port} in use — playbook will run without live dashboard.`);
145
333
  } else {
146
334
  console.error(`Dashboard server error: ${err.message}`);
147
335
  }
@@ -186,6 +374,11 @@ export function startDashboard(port = DEFAULT_PORT): DashboardServer {
186
374
  runHistory.unshift(record);
187
375
  if (runHistory.length > MAX_RUN_HISTORY) runHistory.pop();
188
376
  }
377
+ // Persist to disk so the CLI dashboard (/api/workflow-runs) can read run history
378
+ const dir = join(homedir(), '.monomind');
379
+ mkdir(dir, { recursive: true }).then(() =>
380
+ writeFile(RUNS_FILE, JSON.stringify(runHistory, null, 2))
381
+ ).catch(() => {});
189
382
  }
190
383
 
191
384
  function isStopRequested(runId: string): boolean {