prism-mcp-server 20.21.18 → 20.21.20
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/README.md +20 -0
- package/dist/server.js +2 -8
- package/dist/storage/supabase.js +5 -5
- package/dist/tools/ledgerHandlers.js +27 -0
- package/docs/prism-browser.md +35 -0
- package/package.json +2 -2
- package/scripts/dev/browse.py +114 -4
package/README.md
CHANGED
|
@@ -158,6 +158,26 @@ or by re-enabling after each run.
|
|
|
158
158
|
<details>
|
|
159
159
|
<summary>Release history (optional)</summary>
|
|
160
160
|
|
|
161
|
+
## What's New in v20.21.20
|
|
162
|
+
|
|
163
|
+
- Fixed: a `session_save_ledger` call refused with `context_not_loaded`, or the
|
|
164
|
+
greeting-only reply that writes nothing, restarted the hourly drift reminder.
|
|
165
|
+
A ledger save restarts it now only when it writes an entry.
|
|
166
|
+
- A written ledger entry's reply also asks for the handoff when the checkpoint
|
|
167
|
+
ends a unit of work.
|
|
168
|
+
- A ledger save skipped as a duplicate of one saved in the last 5 minutes now
|
|
169
|
+
says nothing new was written, instead of "saved".
|
|
170
|
+
- Fixed: with Supabase storage, a handoff whose save failed was reported as
|
|
171
|
+
updated. It is now an error the agent can see and retry.
|
|
172
|
+
|
|
173
|
+
## What's New in v20.21.19
|
|
174
|
+
|
|
175
|
+
- Prism Browser emulates phones and tablets. `--device iphone-17`, or one of
|
|
176
|
+
five other measured iPhones and iPads, turns on touch CSS, the pixel ratio,
|
|
177
|
+
and the iPhone safe area, so a layout that only breaks on the phone can be
|
|
178
|
+
caught in a test. `--touch`, `--device-scale-factor`, and `--safe-area` set
|
|
179
|
+
each part on its own.
|
|
180
|
+
|
|
161
181
|
## What's New in v20.21.18
|
|
162
182
|
|
|
163
183
|
- Fixed: when a save was refused with `context_not_loaded`, the instructions
|
package/dist/server.js
CHANGED
|
@@ -764,14 +764,8 @@ export function createServer() {
|
|
|
764
764
|
if (!SESSION_MEMORY_ENABLED)
|
|
765
765
|
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
766
766
|
result = await sessionSaveLedgerHandler(args);
|
|
767
|
-
// GATE 5:
|
|
768
|
-
|
|
769
|
-
const cid = args?.conversation_id;
|
|
770
|
-
if (cid) {
|
|
771
|
-
const { noteDriftCheck } = await import("./session/sessionContext.js");
|
|
772
|
-
noteDriftCheck(cid);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
767
|
+
// GATE 5: the handler resets the drift timer itself, and only when
|
|
768
|
+
// it wrote an entry (not on a refusal or the greeting-only skip).
|
|
775
769
|
// Savings sync (paid, opt-in, fail-soft): session end is the natural
|
|
776
770
|
// push point. pushSavings gates itself — disabled/free/no-jwt all
|
|
777
771
|
// return without a request, and nothing here awaits it.
|
package/dist/storage/supabase.js
CHANGED
|
@@ -154,11 +154,11 @@ export class SupabaseStorage {
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
catch (e) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
157
|
+
// The write failed. Reporting "updated" here told the agent its handoff
|
|
158
|
+
// was saved when nothing was; the caller turns this into an error reply.
|
|
159
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
160
|
+
debugLog("[SupabaseStorage] saveHandoff RPC failed: " + message);
|
|
161
|
+
throw new Error(`Handoff for project "${handoff.project}" was not saved: ${message}`);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
async patchHandoff(project, userId, data) {
|
|
@@ -810,6 +810,22 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
810
810
|
keywords,
|
|
811
811
|
role: effectiveRole, // v3.0: Hivemind role scoping (dashboard fallback)
|
|
812
812
|
});
|
|
813
|
+
// SQLite skips an identical (project, conversation_id, summary) saved in the
|
|
814
|
+
// last 5 minutes and returns { id, deduplicated: true } without writing.
|
|
815
|
+
// Nothing new was saved, so this is not a drift checkpoint and must not read
|
|
816
|
+
// as one: no timer reset, no "saved" line, no handoff nudge, and no second
|
|
817
|
+
// embedding or auto-link pass over the existing row.
|
|
818
|
+
if (result?.deduplicated === true) {
|
|
819
|
+
return {
|
|
820
|
+
content: [{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: (_saveLedgerGateWarning ? `⚠️ ${_saveLedgerGateWarning}\n\n` : "") +
|
|
823
|
+
`ℹ️ An identical ledger entry for project "${project}" was already saved in the last 5 minutes; nothing new was written.` +
|
|
824
|
+
resolverNote,
|
|
825
|
+
}],
|
|
826
|
+
isError: false,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
813
829
|
// ─── Fire-and-forget embedding generation ───
|
|
814
830
|
let embeddingQueued = false;
|
|
815
831
|
if (result) {
|
|
@@ -908,6 +924,14 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
908
924
|
debugLog(`[session_save_ledger] Background decay failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
909
925
|
});
|
|
910
926
|
const metricsBlock = formatInferenceMetrics();
|
|
927
|
+
// GATE 5: a ledger entry that was WRITTEN is a drift checkpoint. Reset the
|
|
928
|
+
// timer here, on the write path only. It used to be reset by the server
|
|
929
|
+
// after every returned result, so a context_not_loaded refusal or the
|
|
930
|
+
// greeting-only skip silenced the hourly reminder without saving anything.
|
|
931
|
+
{
|
|
932
|
+
const { noteDriftCheck } = await import("../session/sessionContext.js");
|
|
933
|
+
noteDriftCheck(conversation_id);
|
|
934
|
+
}
|
|
911
935
|
return {
|
|
912
936
|
content: [{
|
|
913
937
|
type: "text",
|
|
@@ -920,6 +944,9 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
920
944
|
(embeddingQueued
|
|
921
945
|
? `📊 Embedding generation queued for semantic search.`
|
|
922
946
|
: `📊 Primary history saved; optional semantic indexing was not queued.`) +
|
|
947
|
+
// The ledger logs what happened; the next session starts from the
|
|
948
|
+
// handoff. Only a written entry asks, never the skip or a refusal.
|
|
949
|
+
`\n💾 If this checkpoint ends a unit of work, also save the handoff with session_save_handoff.` +
|
|
923
950
|
metricsBlock +
|
|
924
951
|
resolverNote,
|
|
925
952
|
}],
|
package/docs/prism-browser.md
CHANGED
|
@@ -80,6 +80,41 @@ state carries between runs and makes them order dependent. For test runs use
|
|
|
80
80
|
`--trace PATH`, `--video DIR`, and `--har PATH` record Playwright artifacts for
|
|
81
81
|
a failing run.
|
|
82
82
|
|
|
83
|
+
## Phones and tablets
|
|
84
|
+
|
|
85
|
+
A desktop run never matches `(pointer: coarse)` and reads every
|
|
86
|
+
`env(safe-area-inset-*)` as 0, so CSS written for touchscreens, and layouts
|
|
87
|
+
that pad the iPhone status area and home indicator, cannot show up at a phone
|
|
88
|
+
size alone. Emulate the device instead:
|
|
89
|
+
|
|
90
|
+
- `--device NAME` — a measured iPhone or iPad in portrait: viewport, pixel
|
|
91
|
+
ratio, touch, and safe area. `iphone-se`, `iphone-17`, `iphone-air`,
|
|
92
|
+
`iphone-17-pro-max`, `ipad-a16`, `ipad-pro-13`. The values were read from a
|
|
93
|
+
full-screen web view on iOS 26.5 simulators.
|
|
94
|
+
- `--touch` — a touchscreen: `(pointer: coarse)` and `(hover: none)` match,
|
|
95
|
+
touch events exist, and the page's meta viewport is honoured.
|
|
96
|
+
- `--device-scale-factor N` — the device pixel ratio, for example `3`.
|
|
97
|
+
- `--safe-area T,R,B,L` — `env(safe-area-inset-*)` in CSS pixels, for example
|
|
98
|
+
`62,0,34,0`. On iOS a page reads the insets only if its meta viewport sets
|
|
99
|
+
`viewport-fit=cover`; without it an iPhone 17 reports 0. Chromium applies
|
|
100
|
+
them either way, so check that your page sets it.
|
|
101
|
+
|
|
102
|
+
Flags given explicitly override the preset, so
|
|
103
|
+
`--device iphone-17 --viewport 874x402 --safe-area T,R,B,L` is the same phone
|
|
104
|
+
turned sideways. Landscape insets were not measured and are not part of the
|
|
105
|
+
presets: put the values your device reports in place of `T,R,B,L`, because
|
|
106
|
+
without `--safe-area` the portrait insets still apply. The engine stays
|
|
107
|
+
Chromium, and the user agent stays the desktop one, so pages that branch on the
|
|
108
|
+
user-agent string still take their desktop path.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
printf '%s\n' \
|
|
112
|
+
'open http://127.0.0.1:3000/app' \
|
|
113
|
+
'assert-eval matchMedia("(pointer: coarse)").matches' \
|
|
114
|
+
'screenshot phone.png' \
|
|
115
|
+
| prism browser --headless --fast --ephemeral-profile --local-only --device iphone-17 pipe
|
|
116
|
+
```
|
|
117
|
+
|
|
83
118
|
## Multiple pages
|
|
84
119
|
|
|
85
120
|
A popup (OAuth, payment, print preview) opens a new page. `pages` lists them,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "20.21.
|
|
3
|
+
"version": "20.21.20",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-coder",
|
|
5
5
|
"description": "Persistent session memory for AI coding agents that never leaves your machine — including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
|
|
6
6
|
"module": "index.ts",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"hono": "^4.13.5",
|
|
84
84
|
"ip-address": "^10.2.0",
|
|
85
85
|
"protobufjs": "^7.6.5",
|
|
86
|
-
"sharp": "^0.35.
|
|
86
|
+
"sharp": "^0.35.5",
|
|
87
87
|
"tar": "^7.5.19",
|
|
88
88
|
"nanoid": "^3.3.17"
|
|
89
89
|
},
|
package/scripts/dev/browse.py
CHANGED
|
@@ -68,6 +68,19 @@ AUDIT_LOG_PATH = BROWSER_DATA_DIR / "audit.log"
|
|
|
68
68
|
DEFAULT_PROFILE = "default"
|
|
69
69
|
DEFAULT_TIMEOUT = 30000
|
|
70
70
|
DEFAULT_VIEWPORT = (1440, 900)
|
|
71
|
+
# iOS devices in portrait as a full-screen WKWebView page sees them: CSS
|
|
72
|
+
# viewport, devicePixelRatio, and env(safe-area-inset-*) as (top, right,
|
|
73
|
+
# bottom, left). Measured 2026-10-03 on iOS 26.5 simulators, each running a
|
|
74
|
+
# full-screen WKWebView app whose page reported these values. Landscape was not
|
|
75
|
+
# measured: pass --viewport and --safe-area for it.
|
|
76
|
+
DEVICE_PRESETS: dict[str, dict] = {
|
|
77
|
+
'iphone-se': {'viewport': (375, 667), 'scale': 2.0, 'safe_area': (20, 0, 0, 0)},
|
|
78
|
+
'iphone-17': {'viewport': (402, 874), 'scale': 3.0, 'safe_area': (62, 0, 34, 0)},
|
|
79
|
+
'iphone-air': {'viewport': (420, 912), 'scale': 3.0, 'safe_area': (68, 0, 34, 0)},
|
|
80
|
+
'iphone-17-pro-max': {'viewport': (440, 956), 'scale': 3.0, 'safe_area': (62, 0, 34, 0)},
|
|
81
|
+
'ipad-a16': {'viewport': (820, 1180), 'scale': 2.0, 'safe_area': (32, 0, 20, 0)},
|
|
82
|
+
'ipad-pro-13': {'viewport': (1032, 1376), 'scale': 2.0, 'safe_area': (32, 0, 20, 0)},
|
|
83
|
+
}
|
|
71
84
|
REPL_IDLE_TIMEOUT = 600 # 10 minutes — auto-close to prevent zombie Chromium
|
|
72
85
|
PIPE_IDLE_TIMEOUT = 600 # stdin held open with no commands — same protection
|
|
73
86
|
MAX_INIT_SCRIPT_BYTES = 256 * 1024
|
|
@@ -796,7 +809,8 @@ class StealthBrowserSession:
|
|
|
796
809
|
allow_degraded_stealth=False, ephemeral_profile=False,
|
|
797
810
|
storage_state_path=None, trace_path=None, video_dir=None,
|
|
798
811
|
har_path=None, grant_permissions=None, geolocation=None,
|
|
799
|
-
allow_http_error=False
|
|
812
|
+
allow_http_error=False, touch=False, device_scale_factor=None,
|
|
813
|
+
safe_area=None):
|
|
800
814
|
self.profile = validate_profile_name(profile)
|
|
801
815
|
self.headless = headless
|
|
802
816
|
self.timeout = timeout
|
|
@@ -814,6 +828,9 @@ class StealthBrowserSession:
|
|
|
814
828
|
self.grant_permissions = list(grant_permissions or [])
|
|
815
829
|
self.geolocation = geolocation
|
|
816
830
|
self.allow_http_error = allow_http_error
|
|
831
|
+
self.touch = touch
|
|
832
|
+
self.device_scale_factor = device_scale_factor
|
|
833
|
+
self.safe_area = safe_area # (top, right, bottom, left) CSS px, or None
|
|
817
834
|
self.stealth_degraded: list[str] = []
|
|
818
835
|
self.fingerprint: dict = {}
|
|
819
836
|
self._fingerprint_reverified = False
|
|
@@ -895,6 +912,13 @@ class StealthBrowserSession:
|
|
|
895
912
|
'latitude': self.geolocation[0],
|
|
896
913
|
'longitude': self.geolocation[1],
|
|
897
914
|
}
|
|
915
|
+
if self.touch:
|
|
916
|
+
# A phone or tablet: (pointer: coarse) and (hover: none) match, touch
|
|
917
|
+
# events exist, and the page's meta viewport is honoured.
|
|
918
|
+
launch_kwargs['has_touch'] = True
|
|
919
|
+
launch_kwargs['is_mobile'] = True
|
|
920
|
+
if self.device_scale_factor:
|
|
921
|
+
launch_kwargs['device_scale_factor'] = self.device_scale_factor
|
|
898
922
|
if self.video_dir:
|
|
899
923
|
launch_kwargs['record_video_dir'] = str(self.video_dir)
|
|
900
924
|
if self.har_path:
|
|
@@ -950,7 +974,8 @@ class StealthBrowserSession:
|
|
|
950
974
|
audit_log(
|
|
951
975
|
"session_start", f"profile={self.profile}",
|
|
952
976
|
f"stealth={self.stealth_level},headless={self.headless},"
|
|
953
|
-
f"local_only={self.local_only},degraded={len(self.stealth_degraded)}"
|
|
977
|
+
f"local_only={self.local_only},degraded={len(self.stealth_degraded)},"
|
|
978
|
+
f"touch={self.touch},scale={self.device_scale_factor},safe_area={self.safe_area}",
|
|
954
979
|
)
|
|
955
980
|
|
|
956
981
|
def _on_new_page(self, page):
|
|
@@ -990,6 +1015,29 @@ class StealthBrowserSession:
|
|
|
990
1015
|
self.diagnostics.attach(page)
|
|
991
1016
|
if self.stealth_level != "none":
|
|
992
1017
|
self._apply_cdp_user_agent(page)
|
|
1018
|
+
# After the user-agent override, never before: Emulation.setUserAgentOverride
|
|
1019
|
+
# resets an earlier safe-area override on the same page, and env() then
|
|
1020
|
+
# reads 0 under --stealth full.
|
|
1021
|
+
if self.safe_area:
|
|
1022
|
+
self._apply_safe_area(page)
|
|
1023
|
+
|
|
1024
|
+
def _apply_safe_area(self, page):
|
|
1025
|
+
"""
|
|
1026
|
+
Set env(safe-area-inset-*), which Playwright cannot emulate. Chromium
|
|
1027
|
+
applies it whether or not the page sets viewport-fit=cover; iOS reports
|
|
1028
|
+
0 without it. A runtime without the override fails the run: a silent 0
|
|
1029
|
+
would test the wrong layout.
|
|
1030
|
+
"""
|
|
1031
|
+
top, right, bottom, left = self.safe_area
|
|
1032
|
+
try:
|
|
1033
|
+
self._context.new_cdp_session(page).send('Emulation.setSafeAreaInsetsOverride', {
|
|
1034
|
+
'insets': {'top': top, 'right': right, 'bottom': bottom, 'left': left},
|
|
1035
|
+
})
|
|
1036
|
+
except Exception as exc:
|
|
1037
|
+
raise RuntimeError(
|
|
1038
|
+
f"--safe-area could not be applied ({type(exc).__name__}: {exc}). "
|
|
1039
|
+
"It needs a Chromium with Emulation.setSafeAreaInsetsOverride."
|
|
1040
|
+
) from exc
|
|
993
1041
|
|
|
994
1042
|
def _apply_cdp_user_agent(self, page):
|
|
995
1043
|
"""
|
|
@@ -2309,7 +2357,15 @@ def build_parser():
|
|
|
2309
2357
|
help='Delete screenshots captured in this run on exit (see --help notes)')
|
|
2310
2358
|
p.add_argument('--sanitize', action='store_true', help='Mask PHI patterns in text output')
|
|
2311
2359
|
p.add_argument('--timeout', type=int, default=DEFAULT_TIMEOUT, help='Timeout (ms)')
|
|
2312
|
-
p.add_argument('--viewport', default=
|
|
2360
|
+
p.add_argument('--viewport', default=None, help='Viewport WxH (default 1440x900, or the --device size)')
|
|
2361
|
+
p.add_argument('--device', choices=sorted(DEVICE_PRESETS),
|
|
2362
|
+
help='Emulate a measured iPhone or iPad in portrait: size, pixel ratio, touch and safe area')
|
|
2363
|
+
p.add_argument('--touch', action='store_true',
|
|
2364
|
+
help='Emulate a touchscreen: (pointer: coarse), (hover: none), touch events, meta viewport')
|
|
2365
|
+
p.add_argument('--device-scale-factor', metavar='N', help='Device pixel ratio, for example 3')
|
|
2366
|
+
p.add_argument('--safe-area', metavar='T,R,B,L',
|
|
2367
|
+
help='env(safe-area-inset-*) in CSS pixels, for example 62,0,34,0; unlike iOS, '
|
|
2368
|
+
'applied even when the page omits viewport-fit=cover')
|
|
2313
2369
|
p.add_argument('--stealth', choices=['full', 'light', 'none'], default='full',
|
|
2314
2370
|
help='Fingerprint level: full (stealth lib + CDP + JS), light (CDP + JS), none')
|
|
2315
2371
|
p.add_argument('--allow-degraded-stealth', action='store_true',
|
|
@@ -2422,6 +2478,54 @@ def parse_viewport(value: str) -> tuple[int, int]:
|
|
|
2422
2478
|
return int(match.group(1)), int(match.group(2))
|
|
2423
2479
|
|
|
2424
2480
|
|
|
2481
|
+
def parse_safe_area(value: str) -> tuple[int, int, int, int]:
|
|
2482
|
+
"""Parse T,R,B,L insets in CSS pixels, failing loudly like --viewport."""
|
|
2483
|
+
match = re.fullmatch(r'\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*', value or '')
|
|
2484
|
+
if not match:
|
|
2485
|
+
raise ValueError(
|
|
2486
|
+
f"Invalid --safe-area {value!r}. Expected T,R,B,L in CSS pixels, for example 62,0,34,0."
|
|
2487
|
+
)
|
|
2488
|
+
return tuple(int(group) for group in match.groups())
|
|
2489
|
+
|
|
2490
|
+
|
|
2491
|
+
def parse_device_scale_factor(value: str) -> float:
|
|
2492
|
+
"""Parse a device pixel ratio, failing loudly on anything outside 0.5 to 5."""
|
|
2493
|
+
try:
|
|
2494
|
+
scale = float(value)
|
|
2495
|
+
except (TypeError, ValueError):
|
|
2496
|
+
scale = 0.0
|
|
2497
|
+
if not 0.5 <= scale <= 5:
|
|
2498
|
+
raise ValueError(f"Invalid --device-scale-factor {value!r}. Expected a number from 0.5 to 5, for example 3.")
|
|
2499
|
+
return scale
|
|
2500
|
+
|
|
2501
|
+
|
|
2502
|
+
def resolve_device_options(device, viewport, touch, scale, safe_area):
|
|
2503
|
+
"""
|
|
2504
|
+
Combine --device with the individual flags. The preset supplies defaults;
|
|
2505
|
+
any flag given explicitly wins, so --device iphone-17 --viewport 874x402
|
|
2506
|
+
is the same phone turned sideways (pass its landscape --safe-area too).
|
|
2507
|
+
"""
|
|
2508
|
+
# "Given" means not None: an empty value (an unset shell variable) must fail
|
|
2509
|
+
# loudly, not quietly fall back to a desktop default.
|
|
2510
|
+
preset = DEVICE_PRESETS.get(device) if device is not None else None
|
|
2511
|
+
if device is not None and preset is None:
|
|
2512
|
+
raise ValueError(f"Unknown --device {device!r}. Known: {', '.join(sorted(DEVICE_PRESETS))}.")
|
|
2513
|
+
if viewport is not None:
|
|
2514
|
+
size = parse_viewport(viewport)
|
|
2515
|
+
elif preset:
|
|
2516
|
+
size = tuple(preset['viewport'])
|
|
2517
|
+
else:
|
|
2518
|
+
size = DEFAULT_VIEWPORT
|
|
2519
|
+
return {
|
|
2520
|
+
'viewport': size,
|
|
2521
|
+
'touch': bool(touch or preset),
|
|
2522
|
+
'device_scale_factor': (parse_device_scale_factor(scale) if scale is not None
|
|
2523
|
+
else (preset['scale'] if preset else None)),
|
|
2524
|
+
'safe_area': (parse_safe_area(safe_area) if safe_area is not None
|
|
2525
|
+
else (tuple(preset['safe_area']) if preset else None)),
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
|
|
2425
2529
|
def parse_geolocation(value: str) -> tuple[float, float]:
|
|
2426
2530
|
parts = (value or '').split(',')
|
|
2427
2531
|
if len(parts) != 2:
|
|
@@ -2447,9 +2551,12 @@ def main():
|
|
|
2447
2551
|
return 1
|
|
2448
2552
|
|
|
2449
2553
|
try:
|
|
2450
|
-
|
|
2554
|
+
device = resolve_device_options(
|
|
2555
|
+
args.device, args.viewport, args.touch, args.device_scale_factor, args.safe_area,
|
|
2556
|
+
)
|
|
2451
2557
|
except ValueError as exc:
|
|
2452
2558
|
parser.error(str(exc))
|
|
2559
|
+
viewport = device['viewport']
|
|
2453
2560
|
|
|
2454
2561
|
geolocation = None
|
|
2455
2562
|
if args.geolocation:
|
|
@@ -2488,6 +2595,9 @@ def main():
|
|
|
2488
2595
|
grant_permissions=args.grant,
|
|
2489
2596
|
geolocation=geolocation,
|
|
2490
2597
|
allow_http_error=args.allow_http_error,
|
|
2598
|
+
touch=device['touch'],
|
|
2599
|
+
device_scale_factor=device['device_scale_factor'],
|
|
2600
|
+
safe_area=device['safe_area'],
|
|
2491
2601
|
)
|
|
2492
2602
|
except (ValueError, StealthConfigurationError) as exc:
|
|
2493
2603
|
print(json.dumps({"status": "error", "message": str(exc)}), file=sys.stderr)
|