browser-control-and-automation-cli 0.1.1.dev3__py3-none-any.whl
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.
- browser_cli/__init__.py +7 -0
- browser_cli/actions/__init__.py +5 -0
- browser_cli/actions/cli_specs.py +868 -0
- browser_cli/agent_scope/__init__.py +14 -0
- browser_cli/automation/__init__.py +11 -0
- browser_cli/automation/api/__init__.py +5 -0
- browser_cli/automation/api/server.py +321 -0
- browser_cli/automation/hooks.py +42 -0
- browser_cli/automation/loader.py +134 -0
- browser_cli/automation/models.py +127 -0
- browser_cli/automation/persistence/__init__.py +5 -0
- browser_cli/automation/persistence/store.py +682 -0
- browser_cli/automation/projections.py +279 -0
- browser_cli/automation/publisher.py +153 -0
- browser_cli/automation/scheduler/__init__.py +5 -0
- browser_cli/automation/scheduler/schedule.py +113 -0
- browser_cli/automation/service/__init__.py +8 -0
- browser_cli/automation/service/__main__.py +64 -0
- browser_cli/automation/service/client.py +232 -0
- browser_cli/automation/service/runtime.py +388 -0
- browser_cli/automation/toml.py +35 -0
- browser_cli/automation/web/__init__.py +5 -0
- browser_cli/automation/web/assets.py +377 -0
- browser_cli/browser/__init__.py +1 -0
- browser_cli/browser/models.py +44 -0
- browser_cli/browser/network_capture.py +392 -0
- browser_cli/browser/service.py +1519 -0
- browser_cli/browser/session.py +158 -0
- browser_cli/browser/snapshot.py +35 -0
- browser_cli/browser/stealth.py +450 -0
- browser_cli/cli/__init__.py +1 -0
- browser_cli/cli/error_hints.py +26 -0
- browser_cli/cli/main.py +298 -0
- browser_cli/commands/__init__.py +1 -0
- browser_cli/commands/action.py +25 -0
- browser_cli/commands/automation.py +304 -0
- browser_cli/commands/doctor.py +336 -0
- browser_cli/commands/install_skills.py +138 -0
- browser_cli/commands/paths.py +31 -0
- browser_cli/commands/read.py +33 -0
- browser_cli/commands/reload.py +51 -0
- browser_cli/commands/status.py +444 -0
- browser_cli/commands/task.py +71 -0
- browser_cli/constants.py +97 -0
- browser_cli/daemon/__init__.py +1 -0
- browser_cli/daemon/__main__.py +34 -0
- browser_cli/daemon/app.py +941 -0
- browser_cli/daemon/browser_service.py +998 -0
- browser_cli/daemon/client.py +280 -0
- browser_cli/daemon/models.py +63 -0
- browser_cli/daemon/runtime_presentation.py +167 -0
- browser_cli/daemon/server.py +103 -0
- browser_cli/daemon/state.py +15 -0
- browser_cli/daemon/transport.py +77 -0
- browser_cli/drivers/__init__.py +6 -0
- browser_cli/drivers/_extension/__init__.py +1 -0
- browser_cli/drivers/_extension/artifact_actions.py +154 -0
- browser_cli/drivers/_extension/dialog_actions.py +35 -0
- browser_cli/drivers/_extension/input_actions.py +63 -0
- browser_cli/drivers/_extension/locator_actions.py +100 -0
- browser_cli/drivers/_extension/observe_actions.py +285 -0
- browser_cli/drivers/_extension/page_actions.py +210 -0
- browser_cli/drivers/_extension/state_actions.py +134 -0
- browser_cli/drivers/base.py +391 -0
- browser_cli/drivers/extension_driver.py +33 -0
- browser_cli/drivers/models.py +21 -0
- browser_cli/drivers/playwright_driver.py +499 -0
- browser_cli/error_codes.py +27 -0
- browser_cli/errors.py +146 -0
- browser_cli/exit_codes.py +9 -0
- browser_cli/extension/__init__.py +32 -0
- browser_cli/extension/protocol.py +222 -0
- browser_cli/extension/session.py +393 -0
- browser_cli/network.py +147 -0
- browser_cli/outputs/__init__.py +1 -0
- browser_cli/outputs/json.py +10 -0
- browser_cli/outputs/render.py +7 -0
- browser_cli/packaged_skills/__init__.py +1 -0
- browser_cli/packaged_skills/browser-cli-converge/SKILL.md +62 -0
- browser_cli/packaged_skills/browser-cli-delivery/SKILL.md +61 -0
- browser_cli/packaged_skills/browser-cli-explore/SKILL.md +77 -0
- browser_cli/profiles/__init__.py +1 -0
- browser_cli/profiles/discovery.py +185 -0
- browser_cli/refs/__init__.py +17 -0
- browser_cli/refs/generator.py +386 -0
- browser_cli/refs/models.py +127 -0
- browser_cli/refs/registry.py +51 -0
- browser_cli/refs/resolver.py +214 -0
- browser_cli/tabs/__init__.py +5 -0
- browser_cli/tabs/registry.py +283 -0
- browser_cli/task_runtime/__init__.py +31 -0
- browser_cli/task_runtime/client.py +107 -0
- browser_cli/task_runtime/entrypoint.py +110 -0
- browser_cli/task_runtime/errors.py +17 -0
- browser_cli/task_runtime/flow.py +171 -0
- browser_cli/task_runtime/models.py +97 -0
- browser_cli/task_runtime/read.py +71 -0
- browser_cli/task_runtime/templates.py +58 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/METADATA +340 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/RECORD +104 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/WHEEL +5 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/entry_points.txt +2 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/licenses/LICENSE +21 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Static HTML assets for the local automation UI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def render_index_html() -> str:
|
|
7
|
+
return """<!doctype html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<title>Browser CLI automations</title>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #f4f1ea;
|
|
16
|
+
--panel: #fffdf8;
|
|
17
|
+
--line: #d6cdc0;
|
|
18
|
+
--text: #1e1d1a;
|
|
19
|
+
--muted: #6e675d;
|
|
20
|
+
--accent: #0f766e;
|
|
21
|
+
--danger: #b42318;
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
body {
|
|
25
|
+
margin: 0;
|
|
26
|
+
font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
|
|
27
|
+
color: var(--text);
|
|
28
|
+
background:
|
|
29
|
+
radial-gradient(circle at top right, rgba(15,118,110,0.08), transparent 28%),
|
|
30
|
+
linear-gradient(180deg, #f7f2e8, var(--bg));
|
|
31
|
+
}
|
|
32
|
+
header {
|
|
33
|
+
padding: 20px 24px;
|
|
34
|
+
border-bottom: 1px solid var(--line);
|
|
35
|
+
background: rgba(255,255,255,0.75);
|
|
36
|
+
backdrop-filter: blur(10px);
|
|
37
|
+
position: sticky;
|
|
38
|
+
top: 0;
|
|
39
|
+
z-index: 10;
|
|
40
|
+
}
|
|
41
|
+
header h1 { margin: 0 0 8px; font-size: 28px; }
|
|
42
|
+
header p { margin: 0; color: var(--muted); }
|
|
43
|
+
main {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-columns: minmax(260px, 320px) minmax(320px, 1fr) minmax(280px, 360px);
|
|
46
|
+
gap: 16px;
|
|
47
|
+
padding: 16px;
|
|
48
|
+
}
|
|
49
|
+
section {
|
|
50
|
+
background: var(--panel);
|
|
51
|
+
border: 1px solid var(--line);
|
|
52
|
+
border-radius: 16px;
|
|
53
|
+
padding: 16px;
|
|
54
|
+
min-height: 320px;
|
|
55
|
+
box-shadow: 0 12px 32px rgba(30,29,26,0.06);
|
|
56
|
+
}
|
|
57
|
+
h2 { margin-top: 0; font-size: 20px; }
|
|
58
|
+
.automation-item {
|
|
59
|
+
border: 1px solid var(--line);
|
|
60
|
+
border-radius: 12px;
|
|
61
|
+
padding: 12px;
|
|
62
|
+
margin-bottom: 10px;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
background: #fff;
|
|
65
|
+
}
|
|
66
|
+
.automation-item.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
67
|
+
.row { display: grid; gap: 8px; margin-bottom: 12px; }
|
|
68
|
+
label { font-size: 13px; color: var(--muted); display: grid; gap: 6px; }
|
|
69
|
+
input, textarea, select, button {
|
|
70
|
+
font: inherit;
|
|
71
|
+
border-radius: 10px;
|
|
72
|
+
border: 1px solid var(--line);
|
|
73
|
+
padding: 10px 12px;
|
|
74
|
+
background: #fff;
|
|
75
|
+
color: var(--text);
|
|
76
|
+
}
|
|
77
|
+
textarea { min-height: 92px; resize: vertical; }
|
|
78
|
+
button {
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
background: var(--accent);
|
|
81
|
+
border-color: var(--accent);
|
|
82
|
+
color: white;
|
|
83
|
+
}
|
|
84
|
+
button.secondary {
|
|
85
|
+
background: #fff;
|
|
86
|
+
color: var(--text);
|
|
87
|
+
border-color: var(--line);
|
|
88
|
+
}
|
|
89
|
+
button.danger {
|
|
90
|
+
background: var(--danger);
|
|
91
|
+
border-color: var(--danger);
|
|
92
|
+
}
|
|
93
|
+
.button-row {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-wrap: wrap;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
margin-bottom: 12px;
|
|
98
|
+
}
|
|
99
|
+
.status-line { color: var(--muted); font-size: 13px; margin-bottom: 12px; }
|
|
100
|
+
.run-item {
|
|
101
|
+
border-bottom: 1px solid var(--line);
|
|
102
|
+
padding: 10px 0;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
.run-item:last-child { border-bottom: 0; }
|
|
106
|
+
.mono { font-family: "SFMono-Regular", "Menlo", monospace; font-size: 12px; white-space: pre-wrap; }
|
|
107
|
+
.pill {
|
|
108
|
+
display: inline-block;
|
|
109
|
+
border-radius: 999px;
|
|
110
|
+
padding: 2px 8px;
|
|
111
|
+
background: rgba(15,118,110,0.12);
|
|
112
|
+
color: var(--accent);
|
|
113
|
+
font-size: 12px;
|
|
114
|
+
}
|
|
115
|
+
.error { color: var(--danger); }
|
|
116
|
+
@media (max-width: 1100px) {
|
|
117
|
+
main { grid-template-columns: 1fr; }
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
120
|
+
</head>
|
|
121
|
+
<body>
|
|
122
|
+
<header>
|
|
123
|
+
<h1>Browser CLI automations</h1>
|
|
124
|
+
<p>Persistent local automation service for published tasks.</p>
|
|
125
|
+
</header>
|
|
126
|
+
<main>
|
|
127
|
+
<section>
|
|
128
|
+
<h2>automations</h2>
|
|
129
|
+
<div class="button-row">
|
|
130
|
+
<button id="refresh-list" class="secondary">Refresh</button>
|
|
131
|
+
<button id="new-automation" class="secondary">New</button>
|
|
132
|
+
</div>
|
|
133
|
+
<div id="service-status" class="status-line"></div>
|
|
134
|
+
<div id="automation-list"></div>
|
|
135
|
+
</section>
|
|
136
|
+
<section>
|
|
137
|
+
<h2>automation Detail</h2>
|
|
138
|
+
<div class="button-row">
|
|
139
|
+
<button id="save-automation">Save</button>
|
|
140
|
+
<button id="toggle-automation" class="secondary">Enable/Disable</button>
|
|
141
|
+
<button id="run-now" class="secondary">Run Now</button>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="row">
|
|
144
|
+
<label>ID<input id="wf-id"></label>
|
|
145
|
+
<label>Name<input id="wf-name"></label>
|
|
146
|
+
<label>Description<textarea id="wf-description"></textarea></label>
|
|
147
|
+
<label>Task Path<input id="wf-task-path"></label>
|
|
148
|
+
<label>Task Meta Path<input id="wf-task-meta-path"></label>
|
|
149
|
+
<label>Entrypoint<input id="wf-entrypoint" value="run"></label>
|
|
150
|
+
<label>Timezone<input id="wf-timezone" value="UTC"></label>
|
|
151
|
+
<label>Schedule Mode
|
|
152
|
+
<select id="wf-schedule-kind">
|
|
153
|
+
<option value="manual">manual</option>
|
|
154
|
+
<option value="interval">interval</option>
|
|
155
|
+
<option value="daily">daily</option>
|
|
156
|
+
<option value="weekly">weekly</option>
|
|
157
|
+
</select>
|
|
158
|
+
</label>
|
|
159
|
+
<label>Schedule Payload JSON<textarea id="wf-schedule-payload">{}</textarea></label>
|
|
160
|
+
<label>Input Overrides JSON<textarea id="wf-input-overrides">{}</textarea></label>
|
|
161
|
+
<label>Output Directory<input id="wf-output-dir"></label>
|
|
162
|
+
<label>Result JSON Path<input id="wf-result-json-path"></label>
|
|
163
|
+
<label>Retry Attempts<input id="wf-retry-attempts" type="number" value="0"></label>
|
|
164
|
+
<label>Retry Backoff Seconds<input id="wf-retry-backoff" type="number" value="0"></label>
|
|
165
|
+
<label>Timeout Seconds<input id="wf-timeout-seconds" type="number" step="0.1"></label>
|
|
166
|
+
</div>
|
|
167
|
+
<div id="detail-status" class="status-line"></div>
|
|
168
|
+
</section>
|
|
169
|
+
<section>
|
|
170
|
+
<h2>Runs</h2>
|
|
171
|
+
<div id="run-list"></div>
|
|
172
|
+
<h2>Run Detail</h2>
|
|
173
|
+
<div class="button-row">
|
|
174
|
+
<button id="retry-run" class="secondary">Retry Selected Run</button>
|
|
175
|
+
</div>
|
|
176
|
+
<div id="run-detail" class="mono"></div>
|
|
177
|
+
</section>
|
|
178
|
+
</main>
|
|
179
|
+
<script>
|
|
180
|
+
let automations = [];
|
|
181
|
+
let selectedautomationId = null;
|
|
182
|
+
let selectedRunId = null;
|
|
183
|
+
|
|
184
|
+
async function api(path, options = {}) {
|
|
185
|
+
const response = await fetch(path, {
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
...options,
|
|
188
|
+
});
|
|
189
|
+
const payload = await response.json();
|
|
190
|
+
if (!response.ok || payload.ok === false) {
|
|
191
|
+
throw new Error(payload.error_message || "Request failed");
|
|
192
|
+
}
|
|
193
|
+
return payload.data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function parseJson(text, fallback) {
|
|
197
|
+
if (!text.trim()) return fallback;
|
|
198
|
+
return JSON.parse(text);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function automationPayload() {
|
|
202
|
+
return {
|
|
203
|
+
id: document.getElementById("wf-id").value.trim(),
|
|
204
|
+
name: document.getElementById("wf-name").value.trim(),
|
|
205
|
+
description: document.getElementById("wf-description").value.trim(),
|
|
206
|
+
task_path: document.getElementById("wf-task-path").value.trim(),
|
|
207
|
+
task_meta_path: document.getElementById("wf-task-meta-path").value.trim(),
|
|
208
|
+
entrypoint: document.getElementById("wf-entrypoint").value.trim() || "run",
|
|
209
|
+
timezone: document.getElementById("wf-timezone").value.trim() || "UTC",
|
|
210
|
+
schedule_kind: document.getElementById("wf-schedule-kind").value,
|
|
211
|
+
schedule_payload: parseJson(document.getElementById("wf-schedule-payload").value, {}),
|
|
212
|
+
input_overrides: parseJson(document.getElementById("wf-input-overrides").value, {}),
|
|
213
|
+
output_dir: document.getElementById("wf-output-dir").value.trim(),
|
|
214
|
+
result_json_path: document.getElementById("wf-result-json-path").value.trim(),
|
|
215
|
+
retry_attempts: Number(document.getElementById("wf-retry-attempts").value || 0),
|
|
216
|
+
retry_backoff_seconds: Number(document.getElementById("wf-retry-backoff").value || 0),
|
|
217
|
+
timeout_seconds: document.getElementById("wf-timeout-seconds").value
|
|
218
|
+
? Number(document.getElementById("wf-timeout-seconds").value)
|
|
219
|
+
: null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function fillautomation(automation) {
|
|
224
|
+
selectedautomationId = automation ? automation.id : null;
|
|
225
|
+
document.getElementById("wf-id").value = automation?.id || "";
|
|
226
|
+
document.getElementById("wf-name").value = automation?.name || "";
|
|
227
|
+
document.getElementById("wf-description").value = automation?.description || "";
|
|
228
|
+
document.getElementById("wf-task-path").value = automation?.task_path || "";
|
|
229
|
+
document.getElementById("wf-task-meta-path").value = automation?.task_meta_path || "";
|
|
230
|
+
document.getElementById("wf-entrypoint").value = automation?.entrypoint || "run";
|
|
231
|
+
document.getElementById("wf-timezone").value = automation?.timezone || "UTC";
|
|
232
|
+
document.getElementById("wf-schedule-kind").value = automation?.schedule_kind || "manual";
|
|
233
|
+
document.getElementById("wf-schedule-payload").value = JSON.stringify(automation?.schedule_payload || {}, null, 2);
|
|
234
|
+
document.getElementById("wf-input-overrides").value = JSON.stringify(automation?.input_overrides || {}, null, 2);
|
|
235
|
+
document.getElementById("wf-output-dir").value = automation?.output_dir || "";
|
|
236
|
+
document.getElementById("wf-result-json-path").value = automation?.result_json_path || "";
|
|
237
|
+
document.getElementById("wf-retry-attempts").value = automation?.retry_attempts ?? 0;
|
|
238
|
+
document.getElementById("wf-retry-backoff").value = automation?.retry_backoff_seconds ?? 0;
|
|
239
|
+
document.getElementById("wf-timeout-seconds").value = automation?.timeout_seconds ?? "";
|
|
240
|
+
document.getElementById("detail-status").textContent = automation
|
|
241
|
+
? `status=${automation.definition_status} enabled=${automation.enabled} next=${automation.next_run_at || "manual"}`
|
|
242
|
+
: "Create a new automation or select an existing one.";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function pill(text) {
|
|
246
|
+
const span = document.createElement("span");
|
|
247
|
+
span.className = "pill";
|
|
248
|
+
span.textContent = text;
|
|
249
|
+
return span;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function statusLine(text, extraClass = "") {
|
|
253
|
+
const div = document.createElement("div");
|
|
254
|
+
div.className = extraClass ? `status-line ${extraClass}` : "status-line";
|
|
255
|
+
div.textContent = text;
|
|
256
|
+
return div;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function renderautomationList() {
|
|
260
|
+
const root = document.getElementById("automation-list");
|
|
261
|
+
root.innerHTML = "";
|
|
262
|
+
automations.forEach((automation) => {
|
|
263
|
+
const item = document.createElement("div");
|
|
264
|
+
item.className = "automation-item" + (automation.id === selectedautomationId ? " active" : "");
|
|
265
|
+
const title = document.createElement("div");
|
|
266
|
+
const strong = document.createElement("strong");
|
|
267
|
+
strong.textContent = automation.name || automation.id;
|
|
268
|
+
title.appendChild(strong);
|
|
269
|
+
item.appendChild(title);
|
|
270
|
+
item.appendChild(statusLine(automation.id));
|
|
271
|
+
const pills = document.createElement("div");
|
|
272
|
+
pills.appendChild(pill(automation.enabled ? "enabled" : "disabled"));
|
|
273
|
+
pills.appendChild(document.createTextNode(" "));
|
|
274
|
+
pills.appendChild(pill(automation.definition_status));
|
|
275
|
+
item.appendChild(pills);
|
|
276
|
+
item.appendChild(statusLine(`next: ${automation.next_run_at || "manual"}`));
|
|
277
|
+
item.appendChild(statusLine(`latest: ${automation.latest_run?.status || "none"}`));
|
|
278
|
+
item.onclick = async () => {
|
|
279
|
+
await selectautomation(automation.id);
|
|
280
|
+
};
|
|
281
|
+
root.appendChild(item);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function renderRunList(runs) {
|
|
286
|
+
const root = document.getElementById("run-list");
|
|
287
|
+
root.innerHTML = "";
|
|
288
|
+
runs.forEach((run) => {
|
|
289
|
+
const item = document.createElement("div");
|
|
290
|
+
item.className = "run-item";
|
|
291
|
+
const header = document.createElement("div");
|
|
292
|
+
const strong = document.createElement("strong");
|
|
293
|
+
strong.textContent = run.status;
|
|
294
|
+
header.appendChild(strong);
|
|
295
|
+
header.appendChild(document.createTextNode(" "));
|
|
296
|
+
header.appendChild(pill(run.trigger_type));
|
|
297
|
+
item.appendChild(header);
|
|
298
|
+
item.appendChild(statusLine(run.run_id));
|
|
299
|
+
item.appendChild(statusLine(run.queued_at || ""));
|
|
300
|
+
item.appendChild(statusLine(run.error_message || "", run.error_message ? "error" : ""));
|
|
301
|
+
item.onclick = async () => {
|
|
302
|
+
selectedRunId = run.run_id;
|
|
303
|
+
await refreshRunDetail();
|
|
304
|
+
};
|
|
305
|
+
root.appendChild(item);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function refreshStatus() {
|
|
310
|
+
const status = await api("/api/service/status");
|
|
311
|
+
document.getElementById("service-status").textContent =
|
|
312
|
+
`service healthy=${status.service.healthy} automations=${status.metrics.automation_count} queued=${status.metrics.queued_runs} running=${status.metrics.running_runs}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function refreshautomations() {
|
|
316
|
+
automations = await api("/api/automations");
|
|
317
|
+
renderautomationList();
|
|
318
|
+
await refreshStatus();
|
|
319
|
+
if (selectedautomationId) {
|
|
320
|
+
await selectautomation(selectedautomationId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function selectautomation(automationId) {
|
|
325
|
+
const automation = await api(`/api/automations/${automationId}`);
|
|
326
|
+
fillautomation(automation);
|
|
327
|
+
renderautomationList();
|
|
328
|
+
renderRunList(await api(`/api/automations/${automationId}/runs`));
|
|
329
|
+
selectedRunId = null;
|
|
330
|
+
document.getElementById("run-detail").textContent = "";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function refreshRunDetail() {
|
|
334
|
+
if (!selectedRunId) return;
|
|
335
|
+
const run = await api(`/api/runs/${selectedRunId}`);
|
|
336
|
+
document.getElementById("run-detail").textContent = JSON.stringify(run, null, 2);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
document.getElementById("refresh-list").onclick = refreshautomations;
|
|
340
|
+
document.getElementById("new-automation").onclick = () => {
|
|
341
|
+
selectedRunId = null;
|
|
342
|
+
document.getElementById("run-list").innerHTML = "";
|
|
343
|
+
document.getElementById("run-detail").textContent = "";
|
|
344
|
+
fillautomation(null);
|
|
345
|
+
renderautomationList();
|
|
346
|
+
};
|
|
347
|
+
document.getElementById("save-automation").onclick = async () => {
|
|
348
|
+
const payload = automationPayload();
|
|
349
|
+
const method = selectedautomationId ? "PUT" : "POST";
|
|
350
|
+
const path = selectedautomationId ? `/api/automations/${selectedautomationId}` : "/api/automations";
|
|
351
|
+
const automation = await api(path, { method, body: JSON.stringify(payload) });
|
|
352
|
+
fillautomation(automation);
|
|
353
|
+
await refreshautomations();
|
|
354
|
+
};
|
|
355
|
+
document.getElementById("toggle-automation").onclick = async () => {
|
|
356
|
+
if (!selectedautomationId) return;
|
|
357
|
+
const automation = await api(`/api/automations/${selectedautomationId}`);
|
|
358
|
+
const endpoint = automation.enabled ? "disable" : "enable";
|
|
359
|
+
await api(`/api/automations/${selectedautomationId}/${endpoint}`, { method: "POST" });
|
|
360
|
+
await refreshautomations();
|
|
361
|
+
};
|
|
362
|
+
document.getElementById("run-now").onclick = async () => {
|
|
363
|
+
if (!selectedautomationId) return;
|
|
364
|
+
await api(`/api/automations/${selectedautomationId}/run`, { method: "POST" });
|
|
365
|
+
await selectautomation(selectedautomationId);
|
|
366
|
+
};
|
|
367
|
+
document.getElementById("retry-run").onclick = async () => {
|
|
368
|
+
if (!selectedRunId) return;
|
|
369
|
+
await api(`/api/runs/${selectedRunId}/retry`, { method: "POST" });
|
|
370
|
+
if (selectedautomationId) await selectautomation(selectedautomationId);
|
|
371
|
+
};
|
|
372
|
+
refreshautomations().catch((error) => {
|
|
373
|
+
document.getElementById("service-status").textContent = error.message;
|
|
374
|
+
});
|
|
375
|
+
</script>
|
|
376
|
+
</body>
|
|
377
|
+
</html>"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Browser core."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Browser-facing dataclasses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import locale as py_locale
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
HEADLESS_ENV = "BROWSER_CLI_HEADLESS"
|
|
11
|
+
LOCALE_ENV = "BROWSER_CLI_LOCALE"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def default_headless() -> bool:
|
|
15
|
+
raw = os.environ.get(HEADLESS_ENV, "").strip().lower()
|
|
16
|
+
if not raw:
|
|
17
|
+
return False
|
|
18
|
+
return raw in {"1", "true", "yes", "on"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def default_locale() -> str:
|
|
22
|
+
raw = os.environ.get(LOCALE_ENV, "").strip()
|
|
23
|
+
if not raw:
|
|
24
|
+
raw = os.environ.get("LC_ALL", "").strip() or os.environ.get("LANG", "").strip()
|
|
25
|
+
if not raw:
|
|
26
|
+
system_locale, _encoding = py_locale.getlocale()
|
|
27
|
+
raw = system_locale or ""
|
|
28
|
+
raw = raw.split(".", 1)[0].strip()
|
|
29
|
+
if not raw or raw.upper() in {"C", "POSIX"}:
|
|
30
|
+
return "en-US"
|
|
31
|
+
return raw.replace("_", "-")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(slots=True)
|
|
35
|
+
class BrowserLaunchConfig:
|
|
36
|
+
executable_path: Path | None
|
|
37
|
+
user_data_dir: Path
|
|
38
|
+
profile_directory: str = "Default"
|
|
39
|
+
headless: bool = field(default_factory=default_headless)
|
|
40
|
+
locale: str = field(default_factory=default_locale)
|
|
41
|
+
viewport_width: int = 1440
|
|
42
|
+
viewport_height: int = 1024
|
|
43
|
+
navigation_timeout_ms: int = 30_000
|
|
44
|
+
settle_timeout_ms: int = 1_200
|