@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.
- package/dist/package.json +75 -0
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +4 -0
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/browser.js +2 -2
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts +6 -0
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +56 -1
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/dashboard/api-types.d.ts +1 -1
- package/dist/src/browser/dashboard/server.d.ts +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +204 -15
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +1811 -0
- package/dist/src/browser/playbook/browser-handlers.d.ts +3 -0
- package/dist/src/browser/playbook/browser-handlers.d.ts.map +1 -0
- package/dist/src/browser/playbook/browser-handlers.js +377 -0
- package/dist/src/browser/playbook/browser-handlers.js.map +1 -0
- package/dist/src/browser/playbook/builtin-handlers.d.ts +3 -0
- package/dist/src/browser/playbook/builtin-handlers.d.ts.map +1 -0
- package/dist/src/browser/playbook/builtin-handlers.js +364 -0
- package/dist/src/browser/playbook/builtin-handlers.js.map +1 -0
- package/dist/src/browser/playbook/index.d.ts +3 -0
- package/dist/src/browser/playbook/index.d.ts.map +1 -0
- package/dist/src/browser/playbook/index.js +3 -0
- package/dist/src/browser/playbook/index.js.map +1 -0
- package/dist/src/browser/playbook/store.d.ts +8 -0
- package/dist/src/browser/playbook/store.d.ts.map +1 -0
- package/dist/src/browser/playbook/store.js +87 -0
- package/dist/src/browser/playbook/store.js.map +1 -0
- package/dist/src/browser/screenshot.d.ts +2 -0
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +69 -2
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +11 -0
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/cli/action.d.ts +3 -0
- package/dist/src/cli/action.d.ts.map +1 -0
- package/dist/src/cli/action.js +139 -0
- package/dist/src/cli/action.js.map +1 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.d.ts.map +1 -0
- package/dist/src/cli/commands.js +3021 -0
- package/dist/src/cli/commands.js.map +1 -0
- package/dist/src/cli/output.d.ts +11 -0
- package/dist/src/cli/output.d.ts.map +1 -0
- package/dist/src/cli/output.js +33 -0
- package/dist/src/cli/output.js.map +1 -0
- package/dist/src/cli/platform.d.ts +7 -0
- package/dist/src/cli/platform.d.ts.map +1 -0
- package/dist/src/cli/platform.js +193 -0
- package/dist/src/cli/platform.js.map +1 -0
- package/dist/src/cli/playbook.d.ts +3 -0
- package/dist/src/cli/playbook.d.ts.map +1 -0
- package/dist/src/cli/playbook.js +265 -0
- package/dist/src/cli/playbook.js.map +1 -0
- package/dist/src/cli/types.d.ts +42 -0
- package/dist/src/cli/types.d.ts.map +1 -0
- package/dist/src/cli/types.js +2 -0
- package/dist/src/cli/types.js.map +1 -0
- package/dist/src/cli/workflow.d.ts +2 -0
- package/dist/src/cli/workflow.d.ts.map +1 -0
- package/dist/src/cli/workflow.js +3 -0
- package/dist/src/cli/workflow.js.map +1 -0
- package/dist/src/cli.d.ts +7 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +187 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +16 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +24 -5
- package/src/browser/adapters/index.ts +4 -0
- package/src/browser/browser.ts +2 -2
- package/src/browser/cdp.ts +68 -1
- package/src/browser/dashboard/api-types.ts +1 -1
- package/src/browser/dashboard/server.ts +210 -17
- package/src/browser/dashboard/ui.html +1412 -45
- package/src/browser/playbook/browser-handlers.ts +449 -0
- package/src/browser/{workflow → playbook}/builtin-handlers.ts +35 -14
- package/src/browser/playbook/index.ts +2 -0
- package/src/browser/{workflow → playbook}/store.ts +14 -14
- package/src/browser/screenshot.ts +78 -3
- package/src/browser/types.ts +11 -0
- package/src/cli/action.ts +145 -0
- package/src/cli/commands.ts +2895 -0
- package/src/cli/output.ts +37 -0
- package/src/cli/platform.ts +211 -0
- package/src/cli/playbook.ts +280 -0
- package/src/cli/types.ts +47 -0
- package/src/cli.ts +208 -0
- package/src/index.ts +20 -1
- package/src/browser/workflow/engine.ts +0 -202
- package/src/browser/workflow/expression.ts +0 -107
- package/src/browser/workflow/index.ts +0 -5
- 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 '
|
|
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().
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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 —
|
|
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 {
|