aetherius 0.2.0__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.
- aetherius/__init__.py +42 -0
- aetherius/__main__.py +12 -0
- aetherius/_contracts/__init__.py +0 -0
- aetherius/_contracts/blueprint.schema.json +144 -0
- aetherius/acts/__init__.py +1 -0
- aetherius/acts/_shared.py +61 -0
- aetherius/acts/continuum/__init__.py +7 -0
- aetherius/acts/continuum/actions.py +149 -0
- aetherius/acts/continuum/bridge.py +159 -0
- aetherius/acts/continuum/browser.py +164 -0
- aetherius/acts/continuum/debug_overlay.py +65 -0
- aetherius/acts/continuum/driver.py +127 -0
- aetherius/acts/continuum/human_actions.py +74 -0
- aetherius/acts/oracle/__init__.py +1 -0
- aetherius/acts/oracle/driver.py +1 -0
- aetherius/acts/oracle/locator.py +1 -0
- aetherius/acts/oracle/model.py +1 -0
- aetherius/acts/oracle/perception.py +1 -0
- aetherius/acts/phantom/__init__.py +1 -0
- aetherius/acts/phantom/driver.py +1 -0
- aetherius/acts/phantom/loop.py +1 -0
- aetherius/acts/phantom/memory.py +1 -0
- aetherius/acts/phantom/perception.py +1 -0
- aetherius/acts/phantom/planner.py +1 -0
- aetherius/acts/vector/__init__.py +1 -0
- aetherius/acts/vector/auth.py +108 -0
- aetherius/acts/vector/client.py +129 -0
- aetherius/acts/vector/driver.py +128 -0
- aetherius/builder/__init__.py +40 -0
- aetherius/builder/catalog.py +93 -0
- aetherius/builder/factory.py +243 -0
- aetherius/builder/templates.py +150 -0
- aetherius/builder/validation.py +120 -0
- aetherius/cli.py +236 -0
- aetherius/config/__init__.py +1 -0
- aetherius/config/secrets.py +71 -0
- aetherius/config/settings.py +44 -0
- aetherius/console/__init__.py +1 -0
- aetherius/console/app.py +66 -0
- aetherius/console/console.tcss +97 -0
- aetherius/console/daemon_control.py +83 -0
- aetherius/console/run_bridge.py +43 -0
- aetherius/console/screens/__init__.py +1 -0
- aetherius/console/screens/_pending.py +38 -0
- aetherius/console/screens/builder/__init__.py +1 -0
- aetherius/console/screens/builder/act_picker.py +75 -0
- aetherius/console/screens/builder/io_editor.py +167 -0
- aetherius/console/screens/builder/options_editor.py +132 -0
- aetherius/console/screens/builder/preview.py +57 -0
- aetherius/console/screens/builder/screen.py +235 -0
- aetherius/console/screens/builder/step_editor.py +249 -0
- aetherius/console/screens/catalog.py +52 -0
- aetherius/console/screens/home.py +90 -0
- aetherius/console/screens/library.py +124 -0
- aetherius/console/screens/library_scan.py +99 -0
- aetherius/console/screens/recorder.py +193 -0
- aetherius/console/screens/runs.py +138 -0
- aetherius/console/screens/sessions.py +14 -0
- aetherius/console/screens/settings.py +132 -0
- aetherius/console/screenshots.py +173 -0
- aetherius/console/theme.py +114 -0
- aetherius/console/widgets/__init__.py +1 -0
- aetherius/console/widgets/event_log.py +44 -0
- aetherius/console/widgets/form.py +101 -0
- aetherius/console/widgets/json_preview.py +18 -0
- aetherius/console/widgets/run_summary.py +82 -0
- aetherius/core/__init__.py +1 -0
- aetherius/core/actions/__init__.py +1 -0
- aetherius/core/actions/base.py +99 -0
- aetherius/core/actions/data.py +126 -0
- aetherius/core/actions/flow.py +76 -0
- aetherius/core/actions/interaction.py +109 -0
- aetherius/core/actions/navigation.py +32 -0
- aetherius/core/actions/registry.py +70 -0
- aetherius/core/actions/spec.py +42 -0
- aetherius/core/blueprint/__init__.py +1 -0
- aetherius/core/blueprint/loader.py +82 -0
- aetherius/core/blueprint/models.py +81 -0
- aetherius/core/blueprint/template.py +101 -0
- aetherius/core/blueprint/validator.py +45 -0
- aetherius/core/driver.py +36 -0
- aetherius/core/errors.py +134 -0
- aetherius/core/events/__init__.py +1 -0
- aetherius/core/events/bus.py +28 -0
- aetherius/core/events/models.py +29 -0
- aetherius/core/events/sinks.py +55 -0
- aetherius/core/extraction/__init__.py +1 -0
- aetherius/core/extraction/html_extractor.py +52 -0
- aetherius/core/extraction/json_extractor.py +125 -0
- aetherius/core/runtime/__init__.py +1 -0
- aetherius/core/runtime/context.py +61 -0
- aetherius/core/runtime/engine.py +188 -0
- aetherius/core/runtime/result.py +39 -0
- aetherius/core/runtime/selector.py +1 -0
- aetherius/models/__init__.py +1 -0
- aetherius/models/registry.py +1 -0
- aetherius/models/store/.gitkeep +0 -0
- aetherius/recorder/__init__.py +12 -0
- aetherius/recorder/_capture_js.py +80 -0
- aetherius/recorder/_gesture_js.py +40 -0
- aetherius/recorder/_names.py +20 -0
- aetherius/recorder/_overlay_js.py +262 -0
- aetherius/recorder/_playwright.py +49 -0
- aetherius/recorder/_selector_js.py +148 -0
- aetherius/recorder/_transform.py +214 -0
- aetherius/recorder/_vector_js.py +174 -0
- aetherius/recorder/base.py +92 -0
- aetherius/recorder/blueprint_recorder.py +72 -0
- aetherius/recorder/capture.py +59 -0
- aetherius/recorder/continuum_backend.py +119 -0
- aetherius/recorder/gesture_recorder.py +193 -0
- aetherius/recorder/selector_synth.py +83 -0
- aetherius/recorder/session.py +101 -0
- aetherius/recorder/vector_backend.py +158 -0
- aetherius/server/__init__.py +12 -0
- aetherius/server/app.py +37 -0
- aetherius/server/config.py +25 -0
- aetherius/server/deps.py +50 -0
- aetherius/server/jobs.py +155 -0
- aetherius/server/routes/__init__.py +1 -0
- aetherius/server/routes/blueprints.py +45 -0
- aetherius/server/routes/recorder.py +25 -0
- aetherius/server/routes/runs.py +49 -0
- aetherius/server/routes/stream.py +53 -0
- aetherius/server/schemas.py +78 -0
- aetherius/stealth/__init__.py +1 -0
- aetherius/stealth/fingerprint/__init__.py +1 -0
- aetherius/stealth/fingerprint/patch.py +38 -0
- aetherius/stealth/fingerprint/profile.py +95 -0
- aetherius/stealth/gestures/__init__.py +1 -0
- aetherius/stealth/gestures/data/human_library.json +7107 -0
- aetherius/stealth/gestures/library.py +126 -0
- aetherius/stealth/gestures/seed.py +97 -0
- aetherius/stealth/humanizer/__init__.py +1 -0
- aetherius/stealth/humanizer/input.py +89 -0
- aetherius/stealth/humanizer/keyboard.py +75 -0
- aetherius/stealth/humanizer/mouse.py +153 -0
- aetherius/stealth/humanizer/scroll.py +55 -0
- aetherius/stealth/humanizer/timing.py +54 -0
- aetherius/stealth/ml/__init__.py +1 -0
- aetherius/stealth/ml/fingerprint_model.py +1 -0
- aetherius/stealth/ml/motion_model.py +1 -0
- aetherius/stealth/policy.py +133 -0
- aetherius/stealth/session/__init__.py +1 -0
- aetherius/stealth/session/store.py +35 -0
- aetherius/stealth/session/warmup.py +52 -0
- aetherius/version.py +3 -0
- aetherius-0.2.0.data/data/aetherius/_contracts/__init__.py +0 -0
- aetherius-0.2.0.data/data/aetherius/_contracts/blueprint.schema.json +144 -0
- aetherius-0.2.0.dist-info/METADATA +606 -0
- aetherius-0.2.0.dist-info/RECORD +154 -0
- aetherius-0.2.0.dist-info/WHEEL +4 -0
- aetherius-0.2.0.dist-info/entry_points.txt +2 -0
- aetherius-0.2.0.dist-info/licenses/LICENSE +11 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""StealthPolicy: the decoded, typed intent behind ``options.stealth``.
|
|
2
|
+
|
|
3
|
+
The Blueprint carries ``options.stealth`` as loosely-typed data (off, a preset name, or an inline
|
|
4
|
+
object) so the JSON Schema stays the single source of shape. This module turns that data into one
|
|
5
|
+
frozen policy object the browser Acts can query without re-parsing: which humanizer features are on
|
|
6
|
+
and which fingerprint profile to wear. ``build_policy`` is the only entry point; a no-op policy
|
|
7
|
+
(everything off) preserves the historical default behaviour exactly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any, Literal, get_args
|
|
14
|
+
|
|
15
|
+
from ..core.errors import BlueprintValidationError
|
|
16
|
+
|
|
17
|
+
MouseMode = Literal["off", "gestures"]
|
|
18
|
+
KeyboardMode = Literal["off", "human"]
|
|
19
|
+
ScrollMode = Literal["off", "eased"]
|
|
20
|
+
|
|
21
|
+
_MOUSE_MODES = frozenset(get_args(MouseMode))
|
|
22
|
+
_KEYBOARD_MODES = frozenset(get_args(KeyboardMode))
|
|
23
|
+
_SCROLL_MODES = frozenset(get_args(ScrollMode))
|
|
24
|
+
_KNOWN_KEYS = frozenset({"mouse", "keyboard", "scroll", "timing", "fingerprint"})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class StealthPolicy:
|
|
29
|
+
"""Which discretion features are active for a run. Immutable once assembled.
|
|
30
|
+
|
|
31
|
+
``mouse``/``keyboard``/``scroll`` gate the humanizers; ``distraction`` is the probability of an
|
|
32
|
+
occasional idle pause; ``fingerprint`` names a profile (or ``None`` to keep Playwright's stock
|
|
33
|
+
fingerprint). ``OFF`` is the identity policy: no feature touches the page.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
mouse: MouseMode = "off"
|
|
37
|
+
keyboard: KeyboardMode = "off"
|
|
38
|
+
scroll: ScrollMode = "off"
|
|
39
|
+
distraction: float = 0.0
|
|
40
|
+
fingerprint: str | None = None
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_active(self) -> bool:
|
|
44
|
+
"""True when at least one feature would alter the browser or its inputs."""
|
|
45
|
+
return (
|
|
46
|
+
self.mouse != "off"
|
|
47
|
+
or self.keyboard != "off"
|
|
48
|
+
or self.scroll != "off"
|
|
49
|
+
or self.fingerprint is not None
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Identity policy shared by every "off" path, so callers can compare against a single instance.
|
|
54
|
+
OFF = StealthPolicy()
|
|
55
|
+
|
|
56
|
+
# Named presets usable as ``"stealth": "<name>"``. Kept deliberately small; a preset is just a
|
|
57
|
+
# curated StealthPolicy so power users can still spell every field out inline.
|
|
58
|
+
_PRESETS: dict[str, StealthPolicy] = {
|
|
59
|
+
"off": OFF,
|
|
60
|
+
"human": StealthPolicy(
|
|
61
|
+
mouse="gestures",
|
|
62
|
+
keyboard="human",
|
|
63
|
+
scroll="eased",
|
|
64
|
+
distraction=0.1,
|
|
65
|
+
fingerprint="chrome-desktop",
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def preset_names() -> list[str]:
|
|
71
|
+
"""The stealth preset names usable as ``"stealth": "<name>"``, sorted. A public view of the
|
|
72
|
+
preset table so the builder offers exactly what ``build_policy`` accepts, without duplicating it.
|
|
73
|
+
"""
|
|
74
|
+
return sorted(_PRESETS)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _fail(detail: str) -> BlueprintValidationError:
|
|
78
|
+
return BlueprintValidationError(f"Invalid options.stealth: {detail}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _distraction(timing: Any) -> float:
|
|
82
|
+
if timing is None:
|
|
83
|
+
return 0.0
|
|
84
|
+
if not isinstance(timing, dict):
|
|
85
|
+
raise _fail("'timing' must be an object.")
|
|
86
|
+
value = timing.get("distraction", 0.0)
|
|
87
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
88
|
+
raise _fail("'timing.distraction' must be a number between 0 and 1.")
|
|
89
|
+
if not 0.0 <= value <= 1.0:
|
|
90
|
+
raise _fail("'timing.distraction' must be between 0 and 1.")
|
|
91
|
+
return float(value)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _choice(value: Any, allowed: frozenset[str], field: str) -> str:
|
|
95
|
+
if value is None:
|
|
96
|
+
return "off"
|
|
97
|
+
if value not in allowed:
|
|
98
|
+
raise _fail(f"'{field}' must be one of {sorted(allowed)}, got {value!r}.")
|
|
99
|
+
return str(value)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def build_policy(raw: Any) -> StealthPolicy:
|
|
103
|
+
"""Decode ``options.stealth`` into a :class:`StealthPolicy`.
|
|
104
|
+
|
|
105
|
+
Accepts the three shapes the schema allows: ``None``/``"off"`` (no-op), a preset name, or an
|
|
106
|
+
inline object. Anything else raises a typed :class:`BlueprintValidationError` rather than
|
|
107
|
+
silently degrading, so a malformed Blueprint fails loud and early.
|
|
108
|
+
"""
|
|
109
|
+
if raw is None:
|
|
110
|
+
return OFF
|
|
111
|
+
if isinstance(raw, str):
|
|
112
|
+
preset = _PRESETS.get(raw)
|
|
113
|
+
if preset is None:
|
|
114
|
+
raise _fail(f"unknown preset {raw!r} (known: {sorted(_PRESETS)}).")
|
|
115
|
+
return preset
|
|
116
|
+
if not isinstance(raw, dict):
|
|
117
|
+
raise _fail("expected 'off', a preset name, or an object.")
|
|
118
|
+
|
|
119
|
+
unknown = set(raw) - _KNOWN_KEYS
|
|
120
|
+
if unknown:
|
|
121
|
+
raise _fail(f"unknown keys {sorted(unknown)} (allowed: {sorted(_KNOWN_KEYS)}).")
|
|
122
|
+
|
|
123
|
+
fingerprint = raw.get("fingerprint")
|
|
124
|
+
if fingerprint is not None and not isinstance(fingerprint, str):
|
|
125
|
+
raise _fail("'fingerprint' must be a profile name (string).")
|
|
126
|
+
|
|
127
|
+
return StealthPolicy(
|
|
128
|
+
mouse=_choice(raw.get("mouse"), _MOUSE_MODES, "mouse"), # type: ignore[arg-type]
|
|
129
|
+
keyboard=_choice(raw.get("keyboard"), _KEYBOARD_MODES, "keyboard"), # type: ignore[arg-type]
|
|
130
|
+
scroll=_choice(raw.get("scroll"), _SCROLL_MODES, "scroll"), # type: ignore[arg-type]
|
|
131
|
+
distraction=_distraction(raw.get("timing")),
|
|
132
|
+
fingerprint=fingerprint,
|
|
133
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Persistent browser sessions and profile warmup."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Persistent browser profiles and per-run artifact directories.
|
|
2
|
+
|
|
3
|
+
A *profile* is a directory Playwright reuses across runs to carry authentic cookies, cache and
|
|
4
|
+
history (``launch_persistent_context``). A *run directory* collects the artifacts a single run
|
|
5
|
+
produces (screenshots, HAR, DOM snapshots). Both live under the configured ``data_dir``.
|
|
6
|
+
|
|
7
|
+
Only directory resolution lives here for now. Warmup (building an authentic history before an
|
|
8
|
+
automation) and storage-state export are later milestones and are intentionally left out.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from ...config.settings import get_settings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_profile_dir(profile: str | None) -> Path | None:
|
|
19
|
+
"""Return the directory for a persistent browser *profile*, creating it on demand.
|
|
20
|
+
|
|
21
|
+
Returns ``None`` when *profile* is falsy, signalling the caller to use an ephemeral
|
|
22
|
+
(non-persistent) browser context instead.
|
|
23
|
+
"""
|
|
24
|
+
if not profile:
|
|
25
|
+
return None
|
|
26
|
+
path = get_settings().profiles_dir / profile
|
|
27
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
return path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_dir(run_id: str) -> Path:
|
|
32
|
+
"""Return the artifact directory for *run_id*, creating it on demand."""
|
|
33
|
+
path = get_settings().runs_dir / run_id
|
|
34
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
return path
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Profile warmup: build an authentic browsing history before automation.
|
|
2
|
+
|
|
3
|
+
A persistent profile that jumps straight to a sensitive target on its very first request looks
|
|
4
|
+
synthetic. Warmup visits a few innocuous pages first, dwelling on each, so the profile accrues real
|
|
5
|
+
cookies, cache and history — the human touch a cold profile lacks. Generalizes the manual
|
|
6
|
+
login-setup flow of the legacy projects.
|
|
7
|
+
|
|
8
|
+
:func:`plan_warmup` is pure (the itinerary and dwell times, deterministic given an ``rng``);
|
|
9
|
+
:func:`warmup_profile` plays it against a page. Kept intentionally small.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Iterable
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from random import Random
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from ..humanizer.timing import Sleeper, precise_sleep
|
|
20
|
+
|
|
21
|
+
_DEFAULT_DWELL_MS = 2000.0
|
|
22
|
+
_DEFAULT_RNG = Random()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class WarmupStep:
|
|
27
|
+
"""One warmup visit: a URL and how long (seconds) to dwell before moving on."""
|
|
28
|
+
|
|
29
|
+
url: str
|
|
30
|
+
dwell_s: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class WarmupPlan:
|
|
35
|
+
"""An ordered warmup itinerary."""
|
|
36
|
+
|
|
37
|
+
steps: list[WarmupStep]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def plan_warmup(
|
|
41
|
+
urls: Iterable[str], *, dwell_ms: float = _DEFAULT_DWELL_MS, rng: Random = _DEFAULT_RNG
|
|
42
|
+
) -> WarmupPlan:
|
|
43
|
+
"""Build a warmup itinerary from *urls*, each with a randomized dwell around *dwell_ms*."""
|
|
44
|
+
steps = [WarmupStep(url, rng.uniform(dwell_ms * 0.6, dwell_ms * 1.4) / 1000.0) for url in urls]
|
|
45
|
+
return WarmupPlan(steps)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def warmup_profile(page: Any, plan: WarmupPlan, *, sleep: Sleeper = precise_sleep) -> None:
|
|
49
|
+
"""Play *plan* against a page: navigate to each URL and dwell, accreting authentic history."""
|
|
50
|
+
for step in plan.steps:
|
|
51
|
+
page.goto(step.url)
|
|
52
|
+
sleep(step.dwell_s)
|
aetherius/version.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aetherius.dev/contracts/blueprint.schema.json",
|
|
4
|
+
"title": "Aetherius Blueprint",
|
|
5
|
+
"description": "The declarative instruction file executed by Aetherius. Language-agnostic source of truth shared by the Python core and every SDK.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["aetherius", "name", "act"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"aetherius": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Blueprint format version.",
|
|
13
|
+
"pattern": "^[0-9]+\\.[0-9]+$"
|
|
14
|
+
},
|
|
15
|
+
"name": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Dotted identifier, e.g. domain.task.",
|
|
18
|
+
"minLength": 1
|
|
19
|
+
},
|
|
20
|
+
"description": { "type": "string" },
|
|
21
|
+
"act": {
|
|
22
|
+
"description": "Execution engine.",
|
|
23
|
+
"enum": ["vector", "continuum", "oracle", "phantom"]
|
|
24
|
+
},
|
|
25
|
+
"inputs": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"description": "Declared, typed parameters that make the Blueprint reusable.",
|
|
28
|
+
"additionalProperties": { "$ref": "#/$defs/inputSpec" }
|
|
29
|
+
},
|
|
30
|
+
"secrets": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"description": "Names of runtime-only secrets. Values are injected at execution and never stored in the file.",
|
|
33
|
+
"items": { "type": "string" },
|
|
34
|
+
"uniqueItems": true
|
|
35
|
+
},
|
|
36
|
+
"vars": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Local constants (domains, paths) available for interpolation."
|
|
39
|
+
},
|
|
40
|
+
"options": { "$ref": "#/$defs/options" },
|
|
41
|
+
"vision": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Vision configuration for Oracle/Phantom.",
|
|
44
|
+
"properties": { "model": { "type": "string" } },
|
|
45
|
+
"additionalProperties": true
|
|
46
|
+
},
|
|
47
|
+
"goal": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "High-level objective for Phantom (Act IV) when the run is not fully scripted."
|
|
50
|
+
},
|
|
51
|
+
"constraints": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"description": "Guardrails for Phantom.",
|
|
54
|
+
"items": { "type": "string" }
|
|
55
|
+
},
|
|
56
|
+
"steps": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"description": "The ordered action sequence (the action dictionary).",
|
|
59
|
+
"items": { "$ref": "#/$defs/step" }
|
|
60
|
+
},
|
|
61
|
+
"outputs": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "Shape of the returned data, via interpolation of step results."
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"anyOf": [
|
|
67
|
+
{ "required": ["steps"] },
|
|
68
|
+
{ "required": ["goal"] }
|
|
69
|
+
],
|
|
70
|
+
"$defs": {
|
|
71
|
+
"inputSpec": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"required": ["type"],
|
|
75
|
+
"properties": {
|
|
76
|
+
"type": { "enum": ["string", "number", "integer", "boolean", "date", "path", "object", "array"] },
|
|
77
|
+
"required": { "type": "boolean", "default": false },
|
|
78
|
+
"format": { "type": "string" },
|
|
79
|
+
"default": {},
|
|
80
|
+
"description": { "type": "string" }
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"options": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"properties": {
|
|
87
|
+
"debug": {
|
|
88
|
+
"type": "boolean",
|
|
89
|
+
"default": false,
|
|
90
|
+
"description": "Visible browser + cursor overlay + slow-mo for browser Acts; structured terminal logs otherwise."
|
|
91
|
+
},
|
|
92
|
+
"stealth": { "$ref": "#/$defs/stealth" },
|
|
93
|
+
"session": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"properties": {
|
|
97
|
+
"profile": { "type": "string" },
|
|
98
|
+
"persist": { "type": "boolean", "default": false }
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"timeout_ms": { "type": "integer", "minimum": 0 },
|
|
102
|
+
"retries": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"max": { "type": "integer", "minimum": 0 },
|
|
107
|
+
"backoff": { "enum": ["none", "linear", "exponential"] }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"stealth": {
|
|
113
|
+
"description": "Modular discretion. Off by default; a preset name; or an inline configuration.",
|
|
114
|
+
"oneOf": [
|
|
115
|
+
{ "type": "string", "description": "\"off\" or a named preset." },
|
|
116
|
+
{
|
|
117
|
+
"type": "object",
|
|
118
|
+
"additionalProperties": false,
|
|
119
|
+
"properties": {
|
|
120
|
+
"mouse": { "enum": ["off", "gestures"] },
|
|
121
|
+
"keyboard": { "enum": ["off", "human"] },
|
|
122
|
+
"scroll": { "enum": ["off", "eased"] },
|
|
123
|
+
"timing": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"properties": { "distraction": { "type": "number", "minimum": 0, "maximum": 1 } }
|
|
127
|
+
},
|
|
128
|
+
"fingerprint": { "type": "string" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"step": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"description": "A single action. Parameters depend on the action; extra properties are allowed and validated per-action by the engine.",
|
|
136
|
+
"properties": {
|
|
137
|
+
"id": { "type": "string" },
|
|
138
|
+
"action": { "type": "string" }
|
|
139
|
+
},
|
|
140
|
+
"required": ["action"],
|
|
141
|
+
"additionalProperties": true
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|