safari-pilot 0.1.0

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 (143) hide show
  1. package/.claude-plugin/plugin.json +35 -0
  2. package/.mcp.json +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +324 -0
  5. package/bin/.gitkeep +0 -0
  6. package/bin/Safari Pilot.app/Contents/CodeResources +0 -0
  7. package/bin/Safari Pilot.app/Contents/Info.plist +58 -0
  8. package/bin/Safari Pilot.app/Contents/MacOS/Safari Pilot +0 -0
  9. package/bin/Safari Pilot.app/Contents/PkgInfo +1 -0
  10. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Info.plist +55 -0
  11. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/MacOS/Safari Pilot Extension +0 -0
  12. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/background.js +294 -0
  13. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-isolated.js +80 -0
  14. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-main.js +310 -0
  15. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-128.png +0 -0
  16. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-48.png +0 -0
  17. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-96.png +0 -0
  18. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/manifest.json +39 -0
  19. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/_CodeSignature/CodeResources +194 -0
  20. package/bin/Safari Pilot.app/Contents/Resources/AppIcon.icns +0 -0
  21. package/bin/Safari Pilot.app/Contents/Resources/Assets.car +0 -0
  22. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.html +19 -0
  23. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/Info.plist +0 -0
  24. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib +0 -0
  25. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib +0 -0
  26. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib +0 -0
  27. package/bin/Safari Pilot.app/Contents/Resources/Icon.png +0 -0
  28. package/bin/Safari Pilot.app/Contents/Resources/Script.js +22 -0
  29. package/bin/Safari Pilot.app/Contents/Resources/Style.css +45 -0
  30. package/bin/Safari Pilot.app/Contents/_CodeSignature/CodeResources +236 -0
  31. package/bin/Safari Pilot.zip +0 -0
  32. package/bin/SafariPilotd +0 -0
  33. package/dist/engine-selector.d.ts +10 -0
  34. package/dist/engine-selector.js +55 -0
  35. package/dist/engine-selector.js.map +1 -0
  36. package/dist/engines/applescript.d.ts +53 -0
  37. package/dist/engines/applescript.js +290 -0
  38. package/dist/engines/applescript.js.map +1 -0
  39. package/dist/engines/daemon.d.ts +19 -0
  40. package/dist/engines/daemon.js +187 -0
  41. package/dist/engines/daemon.js.map +1 -0
  42. package/dist/engines/engine.d.ts +15 -0
  43. package/dist/engines/engine.js +42 -0
  44. package/dist/engines/engine.js.map +1 -0
  45. package/dist/engines/extension.d.ts +34 -0
  46. package/dist/engines/extension.js +66 -0
  47. package/dist/engines/extension.js.map +1 -0
  48. package/dist/errors.d.ts +128 -0
  49. package/dist/errors.js +250 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +11 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/security/audit-log.d.ts +23 -0
  55. package/dist/security/audit-log.js +68 -0
  56. package/dist/security/audit-log.js.map +1 -0
  57. package/dist/security/circuit-breaker.d.ts +29 -0
  58. package/dist/security/circuit-breaker.js +114 -0
  59. package/dist/security/circuit-breaker.js.map +1 -0
  60. package/dist/security/domain-policy.d.ts +29 -0
  61. package/dist/security/domain-policy.js +96 -0
  62. package/dist/security/domain-policy.js.map +1 -0
  63. package/dist/security/human-approval.d.ts +20 -0
  64. package/dist/security/human-approval.js +150 -0
  65. package/dist/security/human-approval.js.map +1 -0
  66. package/dist/security/idpi-scanner.d.ts +20 -0
  67. package/dist/security/idpi-scanner.js +102 -0
  68. package/dist/security/idpi-scanner.js.map +1 -0
  69. package/dist/security/kill-switch.d.ts +51 -0
  70. package/dist/security/kill-switch.js +103 -0
  71. package/dist/security/kill-switch.js.map +1 -0
  72. package/dist/security/rate-limiter.d.ts +30 -0
  73. package/dist/security/rate-limiter.js +70 -0
  74. package/dist/security/rate-limiter.js.map +1 -0
  75. package/dist/security/screenshot-redaction.d.ts +42 -0
  76. package/dist/security/screenshot-redaction.js +134 -0
  77. package/dist/security/screenshot-redaction.js.map +1 -0
  78. package/dist/security/tab-ownership.d.ts +46 -0
  79. package/dist/security/tab-ownership.js +85 -0
  80. package/dist/security/tab-ownership.js.map +1 -0
  81. package/dist/server.d.ts +53 -0
  82. package/dist/server.js +347 -0
  83. package/dist/server.js.map +1 -0
  84. package/dist/tools/clipboard.d.ts +15 -0
  85. package/dist/tools/clipboard.js +128 -0
  86. package/dist/tools/clipboard.js.map +1 -0
  87. package/dist/tools/compound.d.ts +68 -0
  88. package/dist/tools/compound.js +491 -0
  89. package/dist/tools/compound.js.map +1 -0
  90. package/dist/tools/extraction.d.ts +26 -0
  91. package/dist/tools/extraction.js +414 -0
  92. package/dist/tools/extraction.js.map +1 -0
  93. package/dist/tools/frames.d.ts +22 -0
  94. package/dist/tools/frames.js +165 -0
  95. package/dist/tools/frames.js.map +1 -0
  96. package/dist/tools/interaction.d.ts +30 -0
  97. package/dist/tools/interaction.js +651 -0
  98. package/dist/tools/interaction.js.map +1 -0
  99. package/dist/tools/navigation.d.ts +41 -0
  100. package/dist/tools/navigation.js +316 -0
  101. package/dist/tools/navigation.js.map +1 -0
  102. package/dist/tools/network.d.ts +27 -0
  103. package/dist/tools/network.js +721 -0
  104. package/dist/tools/network.js.map +1 -0
  105. package/dist/tools/performance.d.ts +16 -0
  106. package/dist/tools/performance.js +240 -0
  107. package/dist/tools/performance.js.map +1 -0
  108. package/dist/tools/permissions.d.ts +25 -0
  109. package/dist/tools/permissions.js +308 -0
  110. package/dist/tools/permissions.js.map +1 -0
  111. package/dist/tools/service-workers.d.ts +15 -0
  112. package/dist/tools/service-workers.js +136 -0
  113. package/dist/tools/service-workers.js.map +1 -0
  114. package/dist/tools/shadow.d.ts +21 -0
  115. package/dist/tools/shadow.js +126 -0
  116. package/dist/tools/shadow.js.map +1 -0
  117. package/dist/tools/storage.d.ts +30 -0
  118. package/dist/tools/storage.js +679 -0
  119. package/dist/tools/storage.js.map +1 -0
  120. package/dist/tools/structured-extraction.d.ts +22 -0
  121. package/dist/tools/structured-extraction.js +433 -0
  122. package/dist/tools/structured-extraction.js.map +1 -0
  123. package/dist/tools/wait.d.ts +18 -0
  124. package/dist/tools/wait.js +182 -0
  125. package/dist/tools/wait.js.map +1 -0
  126. package/dist/types.d.ts +85 -0
  127. package/dist/types.js +2 -0
  128. package/dist/types.js.map +1 -0
  129. package/extension/background.js +294 -0
  130. package/extension/content-isolated.js +80 -0
  131. package/extension/content-main.js +310 -0
  132. package/extension/icons/icon-128.png +0 -0
  133. package/extension/icons/icon-48.png +0 -0
  134. package/extension/icons/icon-96.png +0 -0
  135. package/extension/manifest.json +39 -0
  136. package/hooks/session-end.sh +67 -0
  137. package/hooks/session-start.sh +66 -0
  138. package/package.json +46 -0
  139. package/scripts/build-extension.sh +135 -0
  140. package/scripts/postinstall.sh +91 -0
  141. package/scripts/preuninstall.sh +25 -0
  142. package/scripts/update-daemon.sh +62 -0
  143. package/skills/safari-pilot/SKILL.md +157 -0
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
5
+
6
+ # Only run on macOS
7
+ if [[ "$(uname -s)" != "Darwin" ]]; then
8
+ echo "safari-pilot: Skipping postinstall — macOS only"
9
+ exit 0
10
+ fi
11
+
12
+ # Detect architecture
13
+ ARCH=$(uname -m) # arm64 or x86_64
14
+
15
+ # Download pre-built daemon binary from GitHub Releases
16
+ DAEMON_DIR="$ROOT/bin"
17
+ mkdir -p "$DAEMON_DIR"
18
+
19
+ # For now, try to build locally if swift is available
20
+ if command -v swift &>/dev/null; then
21
+ echo "safari-pilot: Building daemon locally..."
22
+ cd "$ROOT/daemon" && swift build -c release 2>&1 || true
23
+ if [ -f ".build/release/SafariPilotd" ]; then
24
+ cp .build/release/SafariPilotd "$DAEMON_DIR/SafariPilotd"
25
+ chmod +x "$DAEMON_DIR/SafariPilotd"
26
+ echo "safari-pilot: Daemon built successfully"
27
+ fi
28
+ else
29
+ echo "safari-pilot: Swift not available — daemon will not be available"
30
+ echo "safari-pilot: Install Xcode Command Line Tools for daemon support"
31
+ fi
32
+
33
+ # Install LaunchAgent if daemon was built
34
+ if [ -f "$DAEMON_DIR/SafariPilotd" ]; then
35
+ PLIST_SRC="$ROOT/daemon/com.safari-pilot.daemon.plist"
36
+ PLIST_DST="$HOME/Library/LaunchAgents/com.safari-pilot.daemon.plist"
37
+
38
+ if [ -f "$PLIST_SRC" ]; then
39
+ # Replace placeholders
40
+ sed "s|__DAEMON_PATH__|$DAEMON_DIR/SafariPilotd|g; s|__LOG_PATH__|$HOME/.safari-pilot|g" "$PLIST_SRC" > "$PLIST_DST"
41
+ mkdir -p "$HOME/.safari-pilot"
42
+ echo "safari-pilot: LaunchAgent installed at $PLIST_DST"
43
+ fi
44
+ fi
45
+
46
+ # Download signed Safari extension from GitHub Releases
47
+ EXTENSION_ZIP="$DAEMON_DIR/Safari Pilot.zip"
48
+ EXTENSION_APP="$DAEMON_DIR/Safari Pilot.app"
49
+
50
+ if [ ! -d "$EXTENSION_APP" ]; then
51
+ echo "safari-pilot: Downloading signed Safari extension..."
52
+ RELEASE_URL="https://github.com/RTinkslinger/safari-pilot/releases/latest/download/Safari%20Pilot.zip"
53
+ if command -v curl &>/dev/null; then
54
+ curl -fsSL "$RELEASE_URL" -o "$EXTENSION_ZIP" 2>/dev/null || true
55
+ elif command -v wget &>/dev/null; then
56
+ wget -q "$RELEASE_URL" -O "$EXTENSION_ZIP" 2>/dev/null || true
57
+ fi
58
+
59
+ if [ -f "$EXTENSION_ZIP" ]; then
60
+ # Extract the .app from the zip
61
+ ditto -x -k "$EXTENSION_ZIP" "$DAEMON_DIR/" 2>/dev/null || unzip -qo "$EXTENSION_ZIP" -d "$DAEMON_DIR/" 2>/dev/null || true
62
+ rm -f "$EXTENSION_ZIP"
63
+
64
+ if [ -d "$EXTENSION_APP" ]; then
65
+ echo "safari-pilot: Safari extension downloaded successfully"
66
+ echo ""
67
+ echo " ┌─────────────────────────────────────────────────────────────┐"
68
+ echo " │ Safari Extension Setup │"
69
+ echo " │ │"
70
+ echo " │ 1. Open the app: │"
71
+ echo " │ open \"$EXTENSION_APP\" │"
72
+ echo " │ │"
73
+ echo " │ 2. Enable in Safari: │"
74
+ echo " │ Safari > Settings > Extensions > Safari Pilot Extension │"
75
+ echo " │ │"
76
+ echo " │ Signed with Developer ID and notarized by Apple. │"
77
+ echo " └─────────────────────────────────────────────────────────────┘"
78
+ echo ""
79
+ else
80
+ echo "safari-pilot: Extension extraction failed — download manually from:"
81
+ echo " https://github.com/RTinkslinger/safari-pilot/releases/latest"
82
+ fi
83
+ else
84
+ echo "safari-pilot: Could not download extension — download manually from:"
85
+ echo " https://github.com/RTinkslinger/safari-pilot/releases/latest"
86
+ fi
87
+ else
88
+ echo "safari-pilot: Safari extension already present"
89
+ fi
90
+
91
+ echo "safari-pilot: Postinstall complete"
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Only run on macOS
5
+ if [[ "$(uname -s)" != "Darwin" ]]; then
6
+ exit 0
7
+ fi
8
+
9
+ LABEL="com.safari-pilot.daemon"
10
+ PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
11
+
12
+ # Unload LaunchAgent
13
+ if [ -f "$PLIST" ]; then
14
+ launchctl bootout gui/$(id -u) "$LABEL" 2>/dev/null || true
15
+ rm -f "$PLIST"
16
+ echo "safari-pilot: LaunchAgent removed"
17
+ fi
18
+
19
+ # Clean up data directory
20
+ DATA_DIR="$HOME/.safari-pilot"
21
+ if [ -d "$DATA_DIR" ]; then
22
+ echo "safari-pilot: Keeping data at $DATA_DIR (remove manually if desired)"
23
+ fi
24
+
25
+ echo "safari-pilot: Uninstall complete"
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # Safe binary update via versioned path + symlink swap + launchctl kickstart
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ DAEMON_DIR="$ROOT/bin"
8
+ DAEMON_BIN="$DAEMON_DIR/SafariPilotd"
9
+ LABEL="com.safari-pilot.daemon"
10
+ PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
11
+
12
+ # Only run on macOS
13
+ if [[ "$(uname -s)" != "Darwin" ]]; then
14
+ echo "update-daemon: macOS only"
15
+ exit 1
16
+ fi
17
+
18
+ # Build new binary into a versioned staging path
19
+ VERSION="${1:-$(date +%Y%m%d%H%M%S)}"
20
+ STAGED="$DAEMON_DIR/SafariPilotd.$VERSION"
21
+
22
+ echo "update-daemon: Building daemon (version: $VERSION)..."
23
+
24
+ if ! command -v swift &>/dev/null; then
25
+ echo "update-daemon: Swift not found — cannot build daemon"
26
+ exit 1
27
+ fi
28
+
29
+ cd "$ROOT/daemon"
30
+ swift build -c release 2>&1
31
+ if [ ! -f ".build/release/SafariPilotd" ]; then
32
+ echo "update-daemon: Build failed — binary not produced"
33
+ exit 1
34
+ fi
35
+
36
+ cp .build/release/SafariPilotd "$STAGED"
37
+ chmod +x "$STAGED"
38
+ echo "update-daemon: New binary staged at $STAGED"
39
+
40
+ # Stop the running daemon if loaded
41
+ if launchctl list "$LABEL" &>/dev/null 2>&1; then
42
+ echo "update-daemon: Stopping running daemon..."
43
+ launchctl stop "$LABEL" || true
44
+ fi
45
+
46
+ # Atomic swap: replace current binary with staged version
47
+ mv "$STAGED" "$DAEMON_BIN"
48
+ echo "update-daemon: Binary swapped at $DAEMON_BIN"
49
+
50
+ # Kickstart (restart) the daemon if the plist is installed
51
+ if [ -f "$PLIST" ]; then
52
+ # Reload to pick up new binary
53
+ launchctl unload "$PLIST" 2>/dev/null || true
54
+ launchctl load "$PLIST"
55
+ launchctl kickstart -k "gui/$(id -u)/$LABEL" 2>/dev/null || launchctl start "$LABEL" || true
56
+ echo "update-daemon: Daemon restarted via launchctl"
57
+ else
58
+ echo "update-daemon: No LaunchAgent plist found — daemon not restarted automatically"
59
+ echo "update-daemon: Run postinstall.sh to install the LaunchAgent"
60
+ fi
61
+
62
+ echo "update-daemon: Update complete (version: $VERSION)"
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: safari-pilot
3
+ description: >
4
+ Native Safari browser automation for AI agents on macOS. Use when browsing websites,
5
+ filling forms, extracting data, testing web apps, or any task involving web pages on Mac.
6
+ Triggers on: "browse in Safari", "use Safari", "navigate to", "fill form", "extract from",
7
+ "test this page", "check website", or any URL when on macOS.
8
+ allowed-tools:
9
+ - mcp__safari__safari_health_check
10
+ - mcp__safari__safari_emergency_stop
11
+ - mcp__safari__safari_navigate
12
+ - mcp__safari__safari_navigate_back
13
+ - mcp__safari__safari_navigate_forward
14
+ - mcp__safari__safari_reload
15
+ - mcp__safari__safari_new_tab
16
+ - mcp__safari__safari_close_tab
17
+ - mcp__safari__safari_list_tabs
18
+ - mcp__safari__safari_click
19
+ - mcp__safari__safari_double_click
20
+ - mcp__safari__safari_fill
21
+ - mcp__safari__safari_select_option
22
+ - mcp__safari__safari_check
23
+ - mcp__safari__safari_hover
24
+ - mcp__safari__safari_type
25
+ - mcp__safari__safari_press_key
26
+ - mcp__safari__safari_scroll
27
+ - mcp__safari__safari_drag
28
+ - mcp__safari__safari_handle_dialog
29
+ - mcp__safari__safari_snapshot
30
+ - mcp__safari__safari_get_text
31
+ - mcp__safari__safari_get_html
32
+ - mcp__safari__safari_get_attribute
33
+ - mcp__safari__safari_evaluate
34
+ - mcp__safari__safari_take_screenshot
35
+ - mcp__safari__safari_get_console_messages
36
+ - mcp__safari__safari_list_network_requests
37
+ - mcp__safari__safari_get_network_request
38
+ - mcp__safari__safari_intercept_requests
39
+ - mcp__safari__safari_network_throttle
40
+ - mcp__safari__safari_network_offline
41
+ - mcp__safari__safari_mock_request
42
+ - mcp__safari__safari_websocket_listen
43
+ - mcp__safari__safari_websocket_filter
44
+ - mcp__safari__safari_get_cookies
45
+ - mcp__safari__safari_set_cookie
46
+ - mcp__safari__safari_delete_cookie
47
+ - mcp__safari__safari_storage_state_export
48
+ - mcp__safari__safari_storage_state_import
49
+ - mcp__safari__safari_local_storage_get
50
+ - mcp__safari__safari_local_storage_set
51
+ - mcp__safari__safari_session_storage_get
52
+ - mcp__safari__safari_session_storage_set
53
+ - mcp__safari__safari_idb_list
54
+ - mcp__safari__safari_idb_get
55
+ - mcp__safari__safari_query_shadow
56
+ - mcp__safari__safari_click_shadow
57
+ - mcp__safari__safari_list_frames
58
+ - mcp__safari__safari_switch_frame
59
+ - mcp__safari__safari_eval_in_frame
60
+ - mcp__safari__safari_permission_get
61
+ - mcp__safari__safari_permission_set
62
+ - mcp__safari__safari_override_geolocation
63
+ - mcp__safari__safari_override_timezone
64
+ - mcp__safari__safari_override_locale
65
+ - mcp__safari__safari_override_useragent
66
+ - mcp__safari__safari_clipboard_read
67
+ - mcp__safari__safari_clipboard_write
68
+ - mcp__safari__safari_sw_list
69
+ - mcp__safari__safari_sw_unregister
70
+ - mcp__safari__safari_begin_trace
71
+ - mcp__safari__safari_end_trace
72
+ - mcp__safari__safari_get_page_metrics
73
+ - mcp__safari__safari_smart_scrape
74
+ - mcp__safari__safari_extract_tables
75
+ - mcp__safari__safari_extract_links
76
+ - mcp__safari__safari_extract_images
77
+ - mcp__safari__safari_extract_metadata
78
+ - mcp__safari__safari_wait_for
79
+ - mcp__safari__safari_test_flow
80
+ - mcp__safari__safari_monitor_page
81
+ - mcp__safari__safari_paginate_scrape
82
+ - mcp__safari__safari_media_control
83
+ ---
84
+
85
+ # You are Safari Pilot
86
+
87
+ You control Safari natively on macOS via AppleScript and a background daemon. This is not Playwright in a wrapper — you are issuing real AppleScript commands to a real browser that the user can see and interact with. Every tab you open is tracked; every action is audited. You own the session, the user owns the machine.
88
+
89
+ Your mental model: you are a co-pilot, not an autopilot. Safari is running on the user's desktop. You navigate it, fill it, read it, and report back. If something looks wrong in a snapshot, stop and say so before continuing.
90
+
91
+ ## Core Pattern: Navigate → Snapshot → Act → Verify
92
+
93
+ This is the loop for every task, regardless of complexity:
94
+
95
+ 1. **Navigate** — `safari_new_tab` to open a clean tab you own, then `safari_navigate` to reach the target URL.
96
+ 2. **Snapshot** — `safari_snapshot` to get the accessibility tree. This is your ground truth. Read it before touching anything.
97
+ 3. **Act** — Click, fill, select, scroll. Use the most specific selector available from the snapshot.
98
+ 4. **Verify** — `safari_snapshot` again after every significant action. Confirm the page state changed as expected before proceeding.
99
+
100
+ Never act on assumptions about what's on a page. Always snapshot first.
101
+
102
+ ## Tab Ownership
103
+
104
+ Safari has tabs you didn't open. Don't touch them.
105
+
106
+ - Always call `safari_list_tabs` at the start of a session to see what's already open.
107
+ - Open your own tab with `safari_new_tab` before navigating. Never reuse existing tabs unless the user explicitly asks.
108
+ - Track the tab URL you opened. All subsequent operations on that task use that tab.
109
+ - When a task finishes, close your tab with `safari_close_tab` unless the user wants to keep it.
110
+
111
+ The security layer enforces ownership — tools will reject operations on tabs you don't own. Work with this, not around it.
112
+
113
+ ## Engine Awareness
114
+
115
+ Three execution paths are available, selected automatically based on what the operation needs:
116
+
117
+ **Daemon (fast path)** — A native macOS process that injects JavaScript directly. Used when available for latency-sensitive operations. Sub-100ms for most reads.
118
+
119
+ **AppleScript (fallback)** — `osascript` bridging into Safari. Always available. ~200-400ms per call. This is the reliable floor.
120
+
121
+ **Extension (deep DOM)** — The Safari Web Extension enables cross-origin XHR interception, shadow DOM queries, and service worker access. Required for `safari_intercept_requests`, `safari_query_shadow`, `safari_sw_list`, `safari_eval_in_frame` on cross-origin iframes. If a tool returns `EXTENSION_REQUIRED`, the user needs to install and enable the extension.
122
+
123
+ You don't choose the engine — `safari_health_check` tells you what's available, and the server selects the best engine per operation.
124
+
125
+ ## Framework-Aware Filling
126
+
127
+ React, Vue, and other SPA frameworks track input state internally — setting `.value` directly bypasses their event system and the field appears empty to the framework even though it looks filled to you.
128
+
129
+ `safari_fill` handles this correctly. It simulates real user input events (focus → input → change → blur) so framework state updates properly. Always use `safari_fill` for form inputs, not `safari_evaluate` with `.value =`.
130
+
131
+ After filling, always verify with a snapshot: check that the field shows the value and that dependent UI (validation messages, enabled submit buttons) has updated.
132
+
133
+ If `safari_fill` fails on a custom component, try `safari_click` on the element first to focus it, then `safari_type` to simulate keystroke-by-keystroke input.
134
+
135
+ ## When NOT to Use Safari Pilot
136
+
137
+ **Lighthouse / performance audits** — Use Chrome DevTools (it has built-in Lighthouse integration). Safari doesn't expose Lighthouse scores. Use `safari_get_page_metrics` and `safari_begin_trace`/`safari_end_trace` for Safari-native perf data instead.
138
+
139
+ **Cross-platform browser testing** — Use Playwright if you need Chrome + Firefox + Safari in one run. Safari Pilot is single-browser by design.
140
+
141
+ **Pure content extraction from public URLs** — Jina (`r.jina.ai`) and Firecrawl are faster and don't require a live browser. Use Safari Pilot only when the page requires authentication, interaction, or real-browser rendering (e.g., paywalls, SPAs, login-protected content).
142
+
143
+ **Headless/CI environments** — Safari only runs on macOS with a display. This tool is for local desktop sessions.
144
+
145
+ ## Error Recovery
146
+
147
+ **`CSP_BLOCKED`** — The page's Content Security Policy is blocking script injection. The extension bypasses CSP through the native extension API. Tell the user the extension is required and guide them to install it.
148
+
149
+ **`TIMEOUT`** — The selector wasn't found or the page didn't respond within the default window. Increase the timeout parameter. If the element genuinely doesn't exist, `safari_snapshot` will show you the actual DOM so you can correct the selector.
150
+
151
+ **`TAB_NOT_OWNED`** — You're trying to operate on a tab you didn't open this session. Use `safari_new_tab` to create your own tab, navigate there, and operate on that. Never ask the user to manually close tabs to work around this.
152
+
153
+ **`JS_APPLE_EVENTS_DISABLED`** — The user needs to enable "Allow JavaScript from Apple Events" in Safari → Develop menu. Without this, AppleScript can't execute JavaScript. Guide them there explicitly: Safari → Develop → Allow JavaScript from Apple Events.
154
+
155
+ **`KILL_SWITCH_ACTIVE`** — `safari_emergency_stop` was called. No further automation is possible in this session. Start a new Claude Code session to resume.
156
+
157
+ **`CIRCUIT_BREAKER_OPEN`** — Too many failures on the same domain triggered the circuit breaker (120s cooldown). Wait, then retry. If failures recur, investigate whether the page structure changed or authentication expired.