safari-pilot 0.1.0 → 0.1.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.
@@ -4,7 +4,7 @@
4
4
  "description": "Native Safari browser automation for AI agents on macOS",
5
5
  "author": "Aakash Kumar",
6
6
  "license": "MIT",
7
- "homepage": "https://github.com/aakashkumar/safari-pilot",
7
+ "homepage": "https://github.com/RTinkslinger/safari-pilot",
8
8
  "components": {
9
9
  "mcpServers": {
10
10
  "safari": {
package/README.md CHANGED
@@ -23,48 +23,71 @@ Safari Pilot gives Claude Code direct control of Safari through AppleScript and
23
23
  ### As a Claude Code Plugin (Recommended)
24
24
 
25
25
  ```bash
26
- claude plugin add safari-pilot
26
+ claude plugin add --from npm safari-pilot
27
27
  ```
28
28
 
29
- ### Manual Installation
29
+ This installs the MCP server, Swift daemon, and skill definition. The plugin activates automatically on macOS.
30
+
31
+ ### From npm (standalone)
32
+
33
+ ```bash
34
+ npm install -g safari-pilot
35
+ ```
36
+
37
+ ### From Source
30
38
 
31
39
  ```bash
32
- git clone https://github.com/aakashkumar/safari-pilot.git
40
+ git clone https://github.com/RTinkslinger/safari-pilot.git
33
41
  cd safari-pilot
34
42
  npm install
35
43
  npm run build
44
+ cd daemon && swift build -c release && cp .build/release/SafariPilotd ../bin/
36
45
  ```
37
46
 
38
- ## Prerequisites
39
-
40
- - **macOS 12.0 (Monterey)** or later
41
- - **Safari** (pre-installed on every Mac)
42
- - **Node.js 20+**
43
-
44
- ### Required Safari Setting
47
+ ## Setup
45
48
 
46
- Enable "Allow JavaScript from Apple Events":
49
+ ### 1. Enable JavaScript from Apple Events (Required, one-time)
47
50
 
48
51
  1. Open **Safari > Settings > Advanced**
49
52
  2. Check **"Show features for web developers"**
50
53
  3. Go to **Safari > Develop** menu
51
54
  4. Check **"Allow JavaScript from Apple Events"**
52
55
 
53
- This is a one-time setting that persists across Safari restarts.
56
+ This persists across Safari restarts.
54
57
 
55
- ### Optional: Safari Web Extension
58
+ ### 2. Install the Safari Extension (Recommended)
56
59
 
57
- For advanced features (closed Shadow DOM traversal, CSP bypass, dialog interception, network mocking), install the bundled Safari extension:
60
+ The extension unlocks advanced features that are impossible without it.
58
61
 
59
- ```bash
60
- # Build the extension (requires Xcode)
61
- bash scripts/build-extension.sh
62
+ **Download the signed, notarized extension** from the [latest GitHub Release](https://github.com/RTinkslinger/safari-pilot/releases/latest):
62
63
 
63
- # Open the app to trigger Safari's extension enable prompt
64
- open "bin/Safari Pilot.app"
65
- ```
64
+ 1. Download `Safari Pilot.zip`
65
+ 2. Extract it
66
+ 3. Open `Safari Pilot.app`
67
+ 4. Go to **Safari > Settings > Extensions**
68
+ 5. Enable **Safari Pilot**
69
+ 6. Set to **"Allow on all websites"** when prompted
70
+ 7. Click **"Manage Profiles"** and enable for your active profile
71
+
72
+ The extension is signed with Developer ID and notarized by Apple — it persists permanently across Safari restarts. No "Allow Unsigned Extensions" needed.
73
+
74
+ **What the extension adds:**
75
+
76
+ | Feature | Without Extension | With Extension |
77
+ |---|---|---|
78
+ | Closed Shadow DOM | Invisible | Full traversal via `queryShadow` |
79
+ | Strict CSP sites (GitHub, etc.) | JS execution blocked | Bypassed via MAIN world |
80
+ | alert()/confirm()/prompt() | Blocks JS forever | Intercepted, returns instantly |
81
+ | Network request capture | Read-only via Performance API | Full intercept, mock, throttle |
82
+ | React/Vue internal state | Basic native setter | Deep framework manipulation |
66
83
 
67
- Then enable it in **Safari > Settings > Extensions > Safari Pilot**.
84
+ Without the extension, Safari Pilot still works for ~80% of use cases (navigation, form filling, text extraction, screenshots, cookies, tab management).
85
+
86
+ ### System Requirements
87
+
88
+ - **macOS 12.0 (Monterey)** or later
89
+ - **Safari** (pre-installed on every Mac)
90
+ - **Node.js 20+**
68
91
 
69
92
  ## Quick Start
70
93
 
@@ -86,6 +109,10 @@ Test the checkout flow on staging.mystore.com — add to cart, fill payment, ver
86
109
  Monitor news.ycombinator.com for any post about our company
87
110
  ```
88
111
 
112
+ ```
113
+ Open my X.com bookmarks and extract the top 5 posts with author profiles
114
+ ```
115
+
89
116
  ## Tool Catalog (74 Tools)
90
117
 
91
118
  ### Navigation (7)
@@ -153,8 +180,14 @@ Claude Code
153
180
  | | (deep DOM)| | (1ms p50) | | (fallback) | |
154
181
  | +-----------+ +-----------+ +----------------+ |
155
182
  +--------------------------------------------------+
156
- | | |
157
- v v v
183
+ | | |
184
+ v v v
185
+ +--------------------------------------------------+
186
+ | Safari Web Extension Swift Daemon osascript|
187
+ | (MAIN world access) (persistent) (fallback) |
188
+ +--------------------------------------------------+
189
+ | | |
190
+ v v v
158
191
  +--------------------------------------------------+
159
192
  | Safari (macOS native) |
160
193
  | Your real browser with all your sessions |
@@ -169,7 +202,7 @@ Claude Code
169
202
  | **Swift Daemon** | **1ms p50** | All AppleScript capabilities, persistent process | Default when daemon is running |
170
203
  | **AppleScript (osascript)** | ~90ms | Basic navigation, forms, extraction, screenshots | Fallback when daemon unavailable |
171
204
 
172
- The engine selector automatically picks the best available engine for each command. If the daemon isn't running, it falls back to raw AppleScript. If a command needs Shadow DOM access and the extension isn't installed, it returns a clear error with instructions.
205
+ The engine selector automatically picks the best available engine for each command. Each tier falls back gracefully to the next no configuration needed.
173
206
 
174
207
  ## Performance
175
208
 
@@ -187,65 +220,27 @@ Over a 500-command session, the daemon saves ~40 seconds of pure overhead vs raw
187
220
 
188
221
  Safari Pilot runs on your local machine with access to your real browser sessions. The security model is defense-in-depth:
189
222
 
190
- ### Tab Ownership
191
- The agent can **only** interact with tabs it created via `safari_new_tab`. Your existing tabs (banking, email, personal) are untouchable. Ownership is enforced at the server level — there is no bypass.
223
+ **Tab Ownership** — The agent can **only** interact with tabs it created via `safari_new_tab`. Your existing tabs (banking, email, personal) are untouchable. Enforced at the server level — no bypass.
192
224
 
193
- ### Domain Policy
194
- Per-domain rate limits prevent runaway automation. Banking and financial domains are flagged as untrusted by default.
225
+ **Domain Policy** — Per-domain rate limits prevent runaway automation. Banking and financial domains flagged as untrusted by default.
195
226
 
196
- ### Rate Limiter + Circuit Breaker
197
- Global limit of 120 actions/minute. Per-domain limits configurable. Circuit breaker trips after 5 consecutive errors, backs off for 120 seconds.
227
+ **Rate Limiter + Circuit Breaker** — Global limit of 120 actions/minute. Circuit breaker trips after 5 consecutive errors, backs off for 120 seconds.
198
228
 
199
- ### IDPI Scanner
200
- Indirect Prompt Injection defense. Scans extracted text for 9 known injection patterns (role reassignment, fake system prompts, base64 payloads, hidden text, etc.).
229
+ **IDPI Scanner** — Indirect Prompt Injection defense. Scans extracted text for 9 known injection patterns.
201
230
 
202
- ### Kill Switch
203
- `safari_emergency_stop` immediately halts all automation and blocks further calls. One command, full stop.
231
+ **Kill Switch** — `safari_emergency_stop` immediately halts all automation. One command, full stop.
204
232
 
205
- ### Human Approval
206
- Sensitive actions (OAuth consent, financial forms, downloads) are flagged and require explicit approval.
233
+ **Human Approval** — Sensitive actions (OAuth consent, financial forms, downloads) flagged for explicit approval.
207
234
 
208
- ### Audit Logging
209
- Every tool call is logged with timestamp, tool name, URL, parameters (passwords redacted), result, and latency. Session-end hook produces a summary.
235
+ **Audit Logging** — Every tool call logged with timestamp, tool name, URL, parameters (passwords redacted), result, and latency.
210
236
 
211
- ### Screenshot Redaction
212
- Cross-origin iframes are blurred in screenshots. Password fields are redacted.
237
+ **Screenshot Redaction** — Cross-origin iframes blurred. Password fields redacted.
213
238
 
214
- ### No Credential Access
215
- Safari Pilot **never** accesses the macOS Keychain. Authentication works through real browser interaction, same as you clicking.
239
+ **No Credential Access** — Safari Pilot **never** accesses the macOS Keychain. Authentication works through real browser interaction.
216
240
 
217
241
  ## Development
218
242
 
219
- ### Project Structure
220
-
221
- ```
222
- safari-pilot/
223
- ├── src/ # TypeScript MCP server
224
- │ ├── server.ts # Main server + tool registration
225
- │ ├── engines/ # AppleScript, Daemon, Extension engines
226
- │ ├── security/ # 9 security modules
227
- │ └── tools/ # 14 tool category modules
228
- ├── daemon/ # Swift persistent daemon
229
- │ ├── Sources/ # Swift source code
230
- │ └── Tests/ # Swift tests (custom runner)
231
- ├── extension/ # Safari Web Extension
232
- │ ├── manifest.json # Manifest V3
233
- │ ├── content-main.js # MAIN world (Shadow DOM, React filling)
234
- │ ├── content-isolated.js # ISOLATED world relay
235
- │ └── background.js # Service worker + native messaging
236
- ├── skills/ # Claude Code skill definition
237
- ├── hooks/ # Session start/end hooks
238
- ├── scripts/ # Install, update, build scripts
239
- └── test/ # 1,167 tests
240
- ├── unit/ # 705 unit tests
241
- ├── integration/ # 372 integration tests
242
- ├── security/ # 27 security tests
243
- ├── e2e/ # 31 E2E tests (real Safari)
244
- ├── canary/ # Deployment canary test
245
- └── fixtures/ # HTML test pages
246
- ```
247
-
248
- ### Building
243
+ ### Building from Source
249
244
 
250
245
  ```bash
251
246
  # TypeScript server
@@ -315,10 +310,13 @@ Safari Pilot is built from scratch — no code from third-party Safari MCP packa
315
310
  **Q: Does the Swift daemon run all the time?**
316
311
  Only when Claude Code is active. The LaunchAgent starts the daemon on demand and it shuts down with the session.
317
312
 
313
+ **Q: Do I need the Safari extension?**
314
+ No — Safari Pilot works without it for ~80% of use cases. The extension adds Shadow DOM traversal, CSP bypass, dialog interception, and network mocking. Install it from the [GitHub Release](https://github.com/RTinkslinger/safari-pilot/releases/latest) if you need those features.
315
+
318
316
  ## License
319
317
 
320
318
  MIT — see [LICENSE](LICENSE).
321
319
 
322
320
  ## Author
323
321
 
324
- Built by [Aakash Kumar](https://github.com/aakashkumar) with Claude.
322
+ Built by [Aakash Kumar](https://github.com/RTinkslinger) with Claude.
@@ -11,13 +11,17 @@
11
11
  // Bundle ID of the containing macOS app — Safari routes
12
12
  // sendNativeMessage calls to the app's web extension handler.
13
13
  const APP_BUNDLE_ID = 'com.safari-pilot.app';
14
- const POLL_INTERVAL_MS = 200;
14
+ const POLL_IDLE_MS = 5000;
15
+ const POLL_ACTIVE_MS = 200;
16
+ const ACTIVE_COOLDOWN_MS = 10000;
15
17
 
16
18
  // ─── State ────────────────────────────────────────────────────────────────
17
19
 
18
20
  let isConnected = false;
19
21
  const activeTabs = new Map(); // tabId → { url, status }
20
22
  let pollTimerId = null;
23
+ let currentPollInterval = POLL_IDLE_MS;
24
+ let lastCommandTime = 0;
21
25
 
22
26
  // ─── Native Messaging (sendNativeMessage-based) ───────────────────────────
23
27
 
@@ -39,14 +43,35 @@
39
43
  const response = await sendNativeRequest({ type: 'poll' });
40
44
 
41
45
  if (response && response.command && response.command !== null) {
46
+ lastCommandTime = Date.now();
47
+ switchToActivePolling();
42
48
  const cmd = response.command;
43
49
  await executeAndReturnResult(cmd);
50
+ } else if (currentPollInterval === POLL_ACTIVE_MS &&
51
+ Date.now() - lastCommandTime > ACTIVE_COOLDOWN_MS) {
52
+ switchToIdlePolling();
44
53
  }
45
54
  } catch (e) {
46
55
  console.warn('[SafariPilot] Poll error:', e);
47
56
  }
48
57
  }
49
58
 
59
+ function switchToActivePolling() {
60
+ if (currentPollInterval === POLL_ACTIVE_MS) return;
61
+ currentPollInterval = POLL_ACTIVE_MS;
62
+ stopPolling();
63
+ pollTimerId = setInterval(pollForCommands, POLL_ACTIVE_MS);
64
+ console.log('[SafariPilot] Switched to active polling (200ms)');
65
+ }
66
+
67
+ function switchToIdlePolling() {
68
+ if (currentPollInterval === POLL_IDLE_MS) return;
69
+ currentPollInterval = POLL_IDLE_MS;
70
+ stopPolling();
71
+ pollTimerId = setInterval(pollForCommands, POLL_IDLE_MS);
72
+ console.log('[SafariPilot] Switched to idle polling (5s)');
73
+ }
74
+
50
75
  /**
51
76
  * Execute a command received from the daemon (via poll) and send the
52
77
  * result back through the native handler.
@@ -102,8 +127,9 @@
102
127
 
103
128
  function startPolling() {
104
129
  if (pollTimerId != null) return;
105
- pollTimerId = setInterval(pollForCommands, POLL_INTERVAL_MS);
106
- console.log('[SafariPilot] Polling started');
130
+ currentPollInterval = POLL_IDLE_MS;
131
+ pollTimerId = setInterval(pollForCommands, POLL_IDLE_MS);
132
+ console.log('[SafariPilot] Polling started (idle: 5s)');
107
133
  }
108
134
 
109
135
  function stopPolling() {
@@ -247,7 +273,22 @@
247
273
  browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
248
274
  // Health check
249
275
  if (message && message.type === 'ping') {
250
- sendResponse({ ok: true, type: 'pong', extensionVersion: '0.1.0' });
276
+ sendResponse({ ok: true, type: 'pong', extensionVersion: '0.1.1' });
277
+ return false;
278
+ }
279
+
280
+ // MCP server session signal — switch to active polling
281
+ if (message && message.type === 'session_start') {
282
+ lastCommandTime = Date.now();
283
+ switchToActivePolling();
284
+ sendResponse({ ok: true, polling: 'active' });
285
+ return false;
286
+ }
287
+
288
+ // MCP server session end — switch to idle polling
289
+ if (message && message.type === 'session_end') {
290
+ switchToIdlePolling();
291
+ sendResponse({ ok: true, polling: 'idle' });
251
292
  return false;
252
293
  }
253
294
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Safari Pilot",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Native Safari automation for AI agents",
6
6
  "permissions": [
7
7
  "activeTab",
@@ -6,7 +6,7 @@
6
6
  <dict>
7
7
  <key>Resources/background.js</key>
8
8
  <data>
9
- IIGHqCWhaR5xSkkHbk6g1j6mywE=
9
+ Yf5ESv+koRTEyqQ0lrN34bNprVA=
10
10
  </data>
11
11
  <key>Resources/content-isolated.js</key>
12
12
  <data>
@@ -30,7 +30,7 @@
30
30
  </data>
31
31
  <key>Resources/manifest.json</key>
32
32
  <data>
33
- faqyT7ozgTX6yxruffIzdIDGx5o=
33
+ QjWAEfk/0LZsMZn/195jgOahj5M=
34
34
  </data>
35
35
  </dict>
36
36
  <key>files2</key>
@@ -39,7 +39,7 @@
39
39
  <dict>
40
40
  <key>hash2</key>
41
41
  <data>
42
- LvhT32gMdjJT/baOfo8UpVM7Geu8zPW0nuyzd+2v5X0=
42
+ j/Injy0/XGM9j2ND0hWwUDDnkY/a/+NSpc6gG1M0dzE=
43
43
  </data>
44
44
  </dict>
45
45
  <key>Resources/content-isolated.js</key>
@@ -81,7 +81,7 @@
81
81
  <dict>
82
82
  <key>hash2</key>
83
83
  <data>
84
- XUi1juiB1sdwbFL879wo8rcVO/GHryZs0DfXc5/FvKY=
84
+ A13Wg0RV6m8VaBpEMa9C8G8s++ZG4tlOCKjDo1WzlbM=
85
85
  </data>
86
86
  </dict>
87
87
  </dict>
@@ -18,15 +18,15 @@
18
18
  </data>
19
19
  <key>Resources/Base.lproj/Main.storyboardc/Info.plist</key>
20
20
  <data>
21
- fsSXicvXuSgX4uqNLVj2Cc0jt0o=
21
+ z8DOn+PGU5ss7IcA1E+1Lni8Hfo=
22
22
  </data>
23
23
  <key>Resources/Base.lproj/Main.storyboardc/MainMenu.nib</key>
24
24
  <data>
25
- Ydw0ENnnCVTPKhxR12vF9bh5K90=
25
+ m6dpidgdJI81r64bzwNhqF0TdmY=
26
26
  </data>
27
27
  <key>Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib</key>
28
28
  <data>
29
- Am4HlE4kpa5zGAJAjaHJzmEbKXc=
29
+ DaNLcAEwjC0i0Da2VPl5/eCPuFs=
30
30
  </data>
31
31
  <key>Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib</key>
32
32
  <data>
@@ -51,7 +51,7 @@
51
51
  <dict>
52
52
  <key>cdhash</key>
53
53
  <data>
54
- cTYpRmTuK4nMu0eyyBx63jp4hzs=
54
+ yEwje1XDJNMr/HJ3JOgzH1quMLo=
55
55
  </data>
56
56
  <key>requirement</key>
57
57
  <string>identifier "com.safari-pilot.app.Extension" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = V37WLKRXUJ</string>
@@ -81,21 +81,21 @@
81
81
  <dict>
82
82
  <key>hash2</key>
83
83
  <data>
84
- DqnfX1/47/7tF4tqFw4faFQQ4ZY22JXxC6R6Tq1lz4Y=
84
+ nYaxkzmg1//wFsddjDY4LOK5DsLV15qYrs3mYej1w2U=
85
85
  </data>
86
86
  </dict>
87
87
  <key>Resources/Base.lproj/Main.storyboardc/MainMenu.nib</key>
88
88
  <dict>
89
89
  <key>hash2</key>
90
90
  <data>
91
- iP4l9avYR1vMxXTZXeN07PLg9l+mTly0rAUHrsrTK58=
91
+ QsPW1DpbkxSuis1gkkz9fMXkjqIrrpEOjsv4ZEk+MXE=
92
92
  </data>
93
93
  </dict>
94
94
  <key>Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib</key>
95
95
  <dict>
96
96
  <key>hash2</key>
97
97
  <data>
98
- sLOJ8XsI4tMsytNqVBJt51fEdOQh9LVou7/WD9OWoVk=
98
+ G/Cq4XOcnVUM3hKsueePQK+NQnksmI9LonAofrpa+qY=
99
99
  </data>
100
100
  </dict>
101
101
  <key>Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib</key>
Binary file
@@ -11,13 +11,17 @@
11
11
  // Bundle ID of the containing macOS app — Safari routes
12
12
  // sendNativeMessage calls to the app's web extension handler.
13
13
  const APP_BUNDLE_ID = 'com.safari-pilot.app';
14
- const POLL_INTERVAL_MS = 200;
14
+ const POLL_IDLE_MS = 5000;
15
+ const POLL_ACTIVE_MS = 200;
16
+ const ACTIVE_COOLDOWN_MS = 10000;
15
17
 
16
18
  // ─── State ────────────────────────────────────────────────────────────────
17
19
 
18
20
  let isConnected = false;
19
21
  const activeTabs = new Map(); // tabId → { url, status }
20
22
  let pollTimerId = null;
23
+ let currentPollInterval = POLL_IDLE_MS;
24
+ let lastCommandTime = 0;
21
25
 
22
26
  // ─── Native Messaging (sendNativeMessage-based) ───────────────────────────
23
27
 
@@ -39,14 +43,35 @@
39
43
  const response = await sendNativeRequest({ type: 'poll' });
40
44
 
41
45
  if (response && response.command && response.command !== null) {
46
+ lastCommandTime = Date.now();
47
+ switchToActivePolling();
42
48
  const cmd = response.command;
43
49
  await executeAndReturnResult(cmd);
50
+ } else if (currentPollInterval === POLL_ACTIVE_MS &&
51
+ Date.now() - lastCommandTime > ACTIVE_COOLDOWN_MS) {
52
+ switchToIdlePolling();
44
53
  }
45
54
  } catch (e) {
46
55
  console.warn('[SafariPilot] Poll error:', e);
47
56
  }
48
57
  }
49
58
 
59
+ function switchToActivePolling() {
60
+ if (currentPollInterval === POLL_ACTIVE_MS) return;
61
+ currentPollInterval = POLL_ACTIVE_MS;
62
+ stopPolling();
63
+ pollTimerId = setInterval(pollForCommands, POLL_ACTIVE_MS);
64
+ console.log('[SafariPilot] Switched to active polling (200ms)');
65
+ }
66
+
67
+ function switchToIdlePolling() {
68
+ if (currentPollInterval === POLL_IDLE_MS) return;
69
+ currentPollInterval = POLL_IDLE_MS;
70
+ stopPolling();
71
+ pollTimerId = setInterval(pollForCommands, POLL_IDLE_MS);
72
+ console.log('[SafariPilot] Switched to idle polling (5s)');
73
+ }
74
+
50
75
  /**
51
76
  * Execute a command received from the daemon (via poll) and send the
52
77
  * result back through the native handler.
@@ -102,8 +127,9 @@
102
127
 
103
128
  function startPolling() {
104
129
  if (pollTimerId != null) return;
105
- pollTimerId = setInterval(pollForCommands, POLL_INTERVAL_MS);
106
- console.log('[SafariPilot] Polling started');
130
+ currentPollInterval = POLL_IDLE_MS;
131
+ pollTimerId = setInterval(pollForCommands, POLL_IDLE_MS);
132
+ console.log('[SafariPilot] Polling started (idle: 5s)');
107
133
  }
108
134
 
109
135
  function stopPolling() {
@@ -247,7 +273,22 @@
247
273
  browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
248
274
  // Health check
249
275
  if (message && message.type === 'ping') {
250
- sendResponse({ ok: true, type: 'pong', extensionVersion: '0.1.0' });
276
+ sendResponse({ ok: true, type: 'pong', extensionVersion: '0.1.1' });
277
+ return false;
278
+ }
279
+
280
+ // MCP server session signal — switch to active polling
281
+ if (message && message.type === 'session_start') {
282
+ lastCommandTime = Date.now();
283
+ switchToActivePolling();
284
+ sendResponse({ ok: true, polling: 'active' });
285
+ return false;
286
+ }
287
+
288
+ // MCP server session end — switch to idle polling
289
+ if (message && message.type === 'session_end') {
290
+ switchToIdlePolling();
291
+ sendResponse({ ok: true, polling: 'idle' });
251
292
  return false;
252
293
  }
253
294
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Safari Pilot",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Native Safari automation for AI agents",
6
6
  "permissions": [
7
7
  "activeTab",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-pilot",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Native Safari browser automation for AI agents on macOS",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -111,10 +111,9 @@ codesign --force --options runtime --timestamp \
111
111
  --sign "$SIGN_IDENTITY" \
112
112
  "$APP_PATH"
113
113
 
114
- # Step 8: Verify signature
115
- echo "Verifying signatures..."
114
+ # Step 8: Verify codesign (spctl check deferred until after notarization)
115
+ echo "Verifying code signature..."
116
116
  codesign --verify --deep --strict --verbose=2 "$APP_PATH"
117
- spctl -a -t exec -vv "$APP_PATH"
118
117
 
119
118
  echo "=== Notarizing ==="
120
119
 
@@ -132,4 +131,8 @@ xcrun stapler staple "$APP_PATH"
132
131
  rm "$ROOT/bin/Safari Pilot.zip"
133
132
  ditto -c -k --keepParent "$APP_PATH" "$ROOT/bin/Safari Pilot.zip"
134
133
 
134
+ # Step 13: Verify with Gatekeeper (now notarized)
135
+ echo "Verifying Gatekeeper acceptance..."
136
+ spctl -a -t exec -vv "$APP_PATH"
137
+
135
138
  echo "=== Signed, Notarized, and Stapled ==="