screenhand 0.4.5 → 0.4.7

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.
@@ -164,6 +164,48 @@ Docs: https://github.com/manushi4/Screenhand
164
164
  `);
165
165
  process.exit(0);
166
166
  }
167
+ // ── Startup cleanup: remove corrupt/empty state files ──
168
+ {
169
+ const stateDir = path.join(os.homedir(), ".screenhand", "state");
170
+ if (fs.existsSync(stateDir)) {
171
+ try {
172
+ const files = fs.readdirSync(stateDir).filter(f => f.endsWith(".json"));
173
+ let removed = 0;
174
+ for (const file of files) {
175
+ const filePath = path.join(stateDir, file);
176
+ try {
177
+ const stat = fs.statSync(filePath);
178
+ // Remove empty files
179
+ if (stat.size === 0) {
180
+ fs.unlinkSync(filePath);
181
+ removed++;
182
+ continue;
183
+ }
184
+ // Remove files older than 7 days
185
+ if (Date.now() - stat.mtimeMs > 7 * 24 * 60 * 60 * 1000) {
186
+ fs.unlinkSync(filePath);
187
+ removed++;
188
+ continue;
189
+ }
190
+ // Remove corrupt JSON (can't parse)
191
+ const content = fs.readFileSync(filePath, "utf-8");
192
+ JSON.parse(content);
193
+ }
194
+ catch {
195
+ try {
196
+ fs.unlinkSync(filePath);
197
+ removed++;
198
+ }
199
+ catch { /* ignore */ }
200
+ }
201
+ }
202
+ if (removed > 0) {
203
+ console.error(`[screenhand] Cleaned up ${removed} stale/corrupt state files from ${stateDir}`);
204
+ }
205
+ }
206
+ catch { /* ignore if dir can't be read */ }
207
+ }
208
+ }
167
209
  // ── Audit logging for dangerous tools ──
168
210
  const AUDIT_LOG_PATH = path.resolve(__dirname, ".audit-log.jsonl");
169
211
  function auditLog(tool, params) {
@@ -251,13 +251,28 @@ export class BridgeClient extends EventEmitter {
251
251
  return [...this.recentStderr];
252
252
  }
253
253
  async spawn() {
254
+ // Check binary exists before spawning — give actionable error instead of cryptic ENOENT
255
+ if (!fs.existsSync(this.binaryPath)) {
256
+ const platform = process.platform;
257
+ const hint = platform === "darwin"
258
+ ? `Run: npm run build:native (requires Xcode + Swift)`
259
+ : platform === "win32"
260
+ ? `Run: npm run build:native:windows (requires .NET 8 SDK)`
261
+ : `Native bridge is only available on macOS and Windows.`;
262
+ throw new Error(`ScreenHand native bridge not found at: ${this.binaryPath}\n` +
263
+ `${hint}\n` +
264
+ `If installed via npm, try: npm rebuild screenhand`);
265
+ }
254
266
  const child = spawn(this.binaryPath, [], {
255
267
  stdio: ["pipe", "pipe", "pipe"],
256
268
  });
257
269
  // Track which process this is so stale event handlers don't trigger restarts
258
270
  const spawnedProcess = child;
259
271
  child.on("error", (err) => {
260
- this.emit("error", err);
272
+ const msg = err.code === "ENOENT"
273
+ ? new Error(`ScreenHand native bridge binary not found: ${this.binaryPath}. Run: npm run build:native`)
274
+ : err;
275
+ this.emit("error", msg);
261
276
  // Only auto-restart if this is still the active process
262
277
  if (this.started && this.process === spawnedProcess) {
263
278
  this.restart().catch(() => { });
@@ -20,7 +20,7 @@
20
20
  "consistency": 6
21
21
  },
22
22
  "confidence": 0.13111111111111112,
23
- "lastValidated": "2026-03-25T10:10:16.511Z",
23
+ "lastValidated": "2026-03-25T10:15:29.651Z",
24
24
  "mapVersion": 1,
25
25
  "uiArchitecture": {
26
26
  "type": "other",
@@ -2849,7 +2849,7 @@
2849
2849
  "shortcutsUsed": 20,
2850
2850
  "playbooksExported": 0,
2851
2851
  "edgeCasesHandled": 2,
2852
- "lastRecomputed": "2026-03-25T10:10:09.981Z",
2852
+ "lastRecomputed": "2026-03-25T10:15:18.462Z",
2853
2853
  "visibilityConditions": [
2854
2854
  {
2855
2855
  "elementLabel": "iPhone 16 Plus",
@@ -2861,10 +2861,10 @@
2861
2861
  "Simulator"
2862
2862
  ],
2863
2863
  "absentOnPages": [],
2864
- "seenCount": 50,
2865
- "checkCount": 50,
2864
+ "seenCount": 51,
2865
+ "checkCount": 51,
2866
2866
  "visibilityRate": 1,
2867
- "lastSeen": "2026-03-25T10:06:30.136Z",
2867
+ "lastSeen": "2026-03-25T10:15:29.651Z",
2868
2868
  "firstSeen": "2026-03-25T05:44:52.135Z"
2869
2869
  },
2870
2870
  {
@@ -2921,10 +2921,10 @@
2921
2921
  "Simulator"
2922
2922
  ],
2923
2923
  "absentOnPages": [],
2924
- "seenCount": 18,
2925
- "checkCount": 18,
2924
+ "seenCount": 19,
2925
+ "checkCount": 19,
2926
2926
  "visibilityRate": 1,
2927
- "lastSeen": "2026-03-25T10:06:30.136Z",
2927
+ "lastSeen": "2026-03-25T10:15:29.651Z",
2928
2928
  "firstSeen": "2026-03-25T05:44:52.135Z"
2929
2929
  },
2930
2930
  {
@@ -4141,10 +4141,10 @@
4141
4141
  "Simulator"
4142
4142
  ],
4143
4143
  "absentOnPages": [],
4144
- "seenCount": 24,
4145
- "checkCount": 24,
4144
+ "seenCount": 25,
4145
+ "checkCount": 25,
4146
4146
  "visibilityRate": 1,
4147
- "lastSeen": "2026-03-25T10:06:30.136Z",
4147
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4148
4148
  "firstSeen": "2026-03-25T05:51:50.429Z"
4149
4149
  },
4150
4150
  {
@@ -4184,10 +4184,10 @@
4184
4184
  "Simulator"
4185
4185
  ],
4186
4186
  "absentOnPages": [],
4187
- "seenCount": 8,
4188
- "checkCount": 8,
4187
+ "seenCount": 9,
4188
+ "checkCount": 9,
4189
4189
  "visibilityRate": 1,
4190
- "lastSeen": "2026-03-25T10:06:30.136Z",
4190
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4191
4191
  "firstSeen": "2026-03-25T09:43:33.566Z"
4192
4192
  },
4193
4193
  {
@@ -4199,10 +4199,10 @@
4199
4199
  "Simulator"
4200
4200
  ],
4201
4201
  "absentOnPages": [],
4202
- "seenCount": 8,
4203
- "checkCount": 8,
4202
+ "seenCount": 9,
4203
+ "checkCount": 9,
4204
4204
  "visibilityRate": 1,
4205
- "lastSeen": "2026-03-25T10:06:30.136Z",
4205
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4206
4206
  "firstSeen": "2026-03-25T09:43:33.566Z"
4207
4207
  },
4208
4208
  {
@@ -4214,10 +4214,10 @@
4214
4214
  "Simulator"
4215
4215
  ],
4216
4216
  "absentOnPages": [],
4217
- "seenCount": 8,
4218
- "checkCount": 8,
4217
+ "seenCount": 9,
4218
+ "checkCount": 9,
4219
4219
  "visibilityRate": 1,
4220
- "lastSeen": "2026-03-25T10:06:30.136Z",
4220
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4221
4221
  "firstSeen": "2026-03-25T09:43:33.566Z"
4222
4222
  },
4223
4223
  {
@@ -4229,10 +4229,10 @@
4229
4229
  "Simulator"
4230
4230
  ],
4231
4231
  "absentOnPages": [],
4232
- "seenCount": 8,
4233
- "checkCount": 8,
4232
+ "seenCount": 9,
4233
+ "checkCount": 9,
4234
4234
  "visibilityRate": 1,
4235
- "lastSeen": "2026-03-25T10:06:30.136Z",
4235
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4236
4236
  "firstSeen": "2026-03-25T09:43:33.566Z"
4237
4237
  },
4238
4238
  {
@@ -4244,10 +4244,10 @@
4244
4244
  "Simulator"
4245
4245
  ],
4246
4246
  "absentOnPages": [],
4247
- "seenCount": 8,
4248
- "checkCount": 8,
4247
+ "seenCount": 9,
4248
+ "checkCount": 9,
4249
4249
  "visibilityRate": 1,
4250
- "lastSeen": "2026-03-25T10:06:30.136Z",
4250
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4251
4251
  "firstSeen": "2026-03-25T09:43:33.566Z"
4252
4252
  },
4253
4253
  {
@@ -4259,10 +4259,10 @@
4259
4259
  "Simulator"
4260
4260
  ],
4261
4261
  "absentOnPages": [],
4262
- "seenCount": 8,
4263
- "checkCount": 8,
4262
+ "seenCount": 9,
4263
+ "checkCount": 9,
4264
4264
  "visibilityRate": 1,
4265
- "lastSeen": "2026-03-25T10:06:30.136Z",
4265
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4266
4266
  "firstSeen": "2026-03-25T09:43:33.566Z"
4267
4267
  },
4268
4268
  {
@@ -4288,10 +4288,10 @@
4288
4288
  "Simulator"
4289
4289
  ],
4290
4290
  "absentOnPages": [],
4291
- "seenCount": 8,
4292
- "checkCount": 8,
4291
+ "seenCount": 9,
4292
+ "checkCount": 9,
4293
4293
  "visibilityRate": 1,
4294
- "lastSeen": "2026-03-25T10:06:30.136Z",
4294
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4295
4295
  "firstSeen": "2026-03-25T09:43:33.566Z"
4296
4296
  },
4297
4297
  {
@@ -4303,10 +4303,10 @@
4303
4303
  "Simulator"
4304
4304
  ],
4305
4305
  "absentOnPages": [],
4306
- "seenCount": 8,
4307
- "checkCount": 8,
4306
+ "seenCount": 9,
4307
+ "checkCount": 9,
4308
4308
  "visibilityRate": 1,
4309
- "lastSeen": "2026-03-25T10:06:30.136Z",
4309
+ "lastSeen": "2026-03-25T10:15:29.651Z",
4310
4310
  "firstSeen": "2026-03-25T09:43:33.566Z"
4311
4311
  },
4312
4312
  {
@@ -5101,10 +5101,10 @@
5101
5101
  "Simulator"
5102
5102
  ],
5103
5103
  "absentOnPages": [],
5104
- "seenCount": 2,
5105
- "checkCount": 2,
5104
+ "seenCount": 3,
5105
+ "checkCount": 3,
5106
5106
  "visibilityRate": 1,
5107
- "lastSeen": "2026-03-25T10:06:30.136Z",
5107
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5108
5108
  "firstSeen": "2026-03-25T10:01:02.431Z"
5109
5109
  },
5110
5110
  {
@@ -5115,10 +5115,10 @@
5115
5115
  "Simulator"
5116
5116
  ],
5117
5117
  "absentOnPages": [],
5118
- "seenCount": 2,
5119
- "checkCount": 2,
5118
+ "seenCount": 3,
5119
+ "checkCount": 3,
5120
5120
  "visibilityRate": 1,
5121
- "lastSeen": "2026-03-25T10:06:30.136Z",
5121
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5122
5122
  "firstSeen": "2026-03-25T10:01:02.431Z"
5123
5123
  },
5124
5124
  {
@@ -5129,10 +5129,10 @@
5129
5129
  "Simulator"
5130
5130
  ],
5131
5131
  "absentOnPages": [],
5132
- "seenCount": 2,
5133
- "checkCount": 2,
5132
+ "seenCount": 3,
5133
+ "checkCount": 3,
5134
5134
  "visibilityRate": 1,
5135
- "lastSeen": "2026-03-25T10:06:30.136Z",
5135
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5136
5136
  "firstSeen": "2026-03-25T10:01:02.431Z"
5137
5137
  },
5138
5138
  {
@@ -5143,10 +5143,10 @@
5143
5143
  "Simulator"
5144
5144
  ],
5145
5145
  "absentOnPages": [],
5146
- "seenCount": 2,
5147
- "checkCount": 2,
5146
+ "seenCount": 3,
5147
+ "checkCount": 3,
5148
5148
  "visibilityRate": 1,
5149
- "lastSeen": "2026-03-25T10:06:30.136Z",
5149
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5150
5150
  "firstSeen": "2026-03-25T10:01:02.431Z"
5151
5151
  },
5152
5152
  {
@@ -5157,10 +5157,10 @@
5157
5157
  "Simulator"
5158
5158
  ],
5159
5159
  "absentOnPages": [],
5160
- "seenCount": 2,
5161
- "checkCount": 2,
5160
+ "seenCount": 3,
5161
+ "checkCount": 3,
5162
5162
  "visibilityRate": 1,
5163
- "lastSeen": "2026-03-25T10:06:30.136Z",
5163
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5164
5164
  "firstSeen": "2026-03-25T10:01:02.431Z"
5165
5165
  },
5166
5166
  {
@@ -5185,10 +5185,10 @@
5185
5185
  "Simulator"
5186
5186
  ],
5187
5187
  "absentOnPages": [],
5188
- "seenCount": 2,
5189
- "checkCount": 2,
5188
+ "seenCount": 3,
5189
+ "checkCount": 3,
5190
5190
  "visibilityRate": 1,
5191
- "lastSeen": "2026-03-25T10:06:30.136Z",
5191
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5192
5192
  "firstSeen": "2026-03-25T10:01:02.431Z"
5193
5193
  },
5194
5194
  {
@@ -5204,6 +5204,34 @@
5204
5204
  "visibilityRate": 1,
5205
5205
  "lastSeen": "2026-03-25T10:06:30.136Z",
5206
5206
  "firstSeen": "2026-03-25T10:06:30.136Z"
5207
+ },
5208
+ {
5209
+ "elementLabel": "3:45",
5210
+ "conditionType": "unknown",
5211
+ "description": "Visibility rate: 100%",
5212
+ "seenOnPages": [
5213
+ "Simulator"
5214
+ ],
5215
+ "absentOnPages": [],
5216
+ "seenCount": 2,
5217
+ "checkCount": 2,
5218
+ "visibilityRate": 1,
5219
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5220
+ "firstSeen": "2026-03-25T10:15:29.651Z"
5221
+ },
5222
+ {
5223
+ "elementLabel": "6 PM",
5224
+ "conditionType": "unknown",
5225
+ "description": "Visibility rate: 100%",
5226
+ "seenOnPages": [
5227
+ "Simulator"
5228
+ ],
5229
+ "absentOnPages": [],
5230
+ "seenCount": 1,
5231
+ "checkCount": 1,
5232
+ "visibilityRate": 1,
5233
+ "lastSeen": "2026-03-25T10:15:29.651Z",
5234
+ "firstSeen": "2026-03-25T10:15:29.651Z"
5207
5235
  }
5208
5236
  ]
5209
5237
  }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "simulator-add-media",
3
+ "name": "Add Photos/Videos to Simulator",
4
+ "description": "Import photos or videos into the simulator's Photos library",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "photos", "media", "import"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl addmedia booted '{file_path}'\"",
14
+ "description": "Add photo/video to simulator Photos (replace {file_path} with /path/to/image.jpg or video.mp4)"
15
+ }
16
+ ],
17
+ "notes": "Supports: .jpg, .png, .heic, .mp4, .mov. Multiple files: xcrun simctl addmedia booted file1.jpg file2.png"
18
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "simulator-dark-mode",
3
+ "name": "Toggle Dark Mode",
4
+ "description": "Switch between light and dark appearance in iOS Simulator",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "dark-mode", "appearance"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "key",
18
+ "key": "shift+cmd+a",
19
+ "description": "Toggle between light and dark appearance"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "id": "simulator-device-controls",
3
+ "name": "Device Controls",
4
+ "description": "Home, lock, unlock, rotate, app switcher, shake — all Simulator hardware button actions",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "device", "controls"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "key",
18
+ "key": "shift+cmd+h",
19
+ "description": "Home — return to home screen"
20
+ },
21
+ {
22
+ "action": "key",
23
+ "key": "cmd+l",
24
+ "description": "Lock device"
25
+ },
26
+ {
27
+ "action": "applescript",
28
+ "script": "do shell script \"xcrun simctl spawn booted notifyutil -p com.apple.springboard.unlockdevice\"",
29
+ "description": "Unlock device (simctl method — most reliable)"
30
+ },
31
+ {
32
+ "action": "key",
33
+ "key": "shift+cmd+h",
34
+ "description": "Press Home after unlock to reach home screen"
35
+ }
36
+ ],
37
+ "notes": "Rotate: menu_click('Device/Rotate Left') or menu_click('Device/Rotate Right'). App Switcher: key('ctrl+shift+cmd+h'). Shake: key('ctrl+cmd+z'). Siri: key('option+shift+cmd+h')."
38
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "simulator-erase-device",
3
+ "name": "Erase Device (Factory Reset)",
4
+ "description": "Factory reset the simulator — erases all content and settings",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "erase", "reset"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl list devices booted -j\" ",
14
+ "description": "Get booted device UDID first"
15
+ },
16
+ {
17
+ "action": "applescript",
18
+ "script": "do shell script \"xcrun simctl shutdown booted && xcrun simctl erase {udid}\"",
19
+ "description": "Shutdown then erase device (replace {udid})"
20
+ },
21
+ {
22
+ "action": "applescript",
23
+ "script": "do shell script \"xcrun simctl boot {udid}\"",
24
+ "description": "Boot device back up after erase"
25
+ }
26
+ ],
27
+ "notes": "Device must be shut down before erase. Alternative: menu_click('Device/Erase All Content and Settings…') then confirm dialog."
28
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "simulator-face-id",
3
+ "name": "Test Face ID",
4
+ "description": "Simulate Face ID matching and non-matching scans for testing biometric flows",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "face-id", "biometrics", "testing"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "menu_click",
18
+ "path": "Features/Face ID/Enrolled",
19
+ "description": "Ensure Face ID is enrolled (must be checked first)"
20
+ },
21
+ {
22
+ "action": "key",
23
+ "key": "option+cmd+m",
24
+ "description": "Matching Face — triggers successful Face ID authentication"
25
+ }
26
+ ],
27
+ "notes": "For failed scan: key('option+cmd+n'). For Apple Pay: key('option+cmd+a'). Must enroll Face ID first via Features > Face ID > Enrolled."
28
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "simulator-grant-permissions",
3
+ "name": "Grant App Permissions",
4
+ "description": "Grant privacy permissions (camera, photos, location, etc.) to an iOS app without UI prompts",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "permissions", "privacy", "testing"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl privacy booted grant all {bundleId}\"",
14
+ "description": "Grant ALL permissions to app (replace {bundleId}). Or use specific: camera, photos, location, microphone, contacts, calendars, reminders, health, homekit, speech-recognition"
15
+ }
16
+ ],
17
+ "notes": "To revoke: xcrun simctl privacy booted revoke all {bundleId}. To reset: xcrun simctl privacy booted reset all {bundleId}"
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-install-app",
3
+ "name": "Install & Uninstall App",
4
+ "description": "Install a .app bundle or uninstall an app by bundle ID",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "install", "uninstall", "app"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl install booted '{path_to_app}'\"",
14
+ "description": "Install app (replace {path_to_app} with /path/to/MyApp.app)"
15
+ },
16
+ {
17
+ "action": "applescript",
18
+ "script": "do shell script \"xcrun simctl launch booted '{bundleId}'\"",
19
+ "description": "Launch the installed app"
20
+ }
21
+ ],
22
+ "notes": "To uninstall: xcrun simctl uninstall booted '{bundleId}'. To find app container: xcrun simctl get_app_container booted '{bundleId}'. To terminate: xcrun simctl terminate booted '{bundleId}'."
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-launch-app",
3
+ "name": "Launch iOS App in Simulator",
4
+ "description": "Launch any installed iOS app by bundle ID using simctl — fastest and most reliable method",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "launch", "simctl"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "applescript",
18
+ "script": "do shell script \"xcrun simctl launch booted {bundleId}\"",
19
+ "description": "Launch iOS app via simctl (replace {bundleId} with e.g. com.apple.mobilesafari)"
20
+ }
21
+ ],
22
+ "notes": "Common bundle IDs: Safari=com.apple.mobilesafari, Settings=com.apple.Preferences, Maps=com.apple.Maps, Photos=com.apple.mobileslideshow, Health=com.apple.Health, Messages=com.apple.MobileSMS, Files=com.apple.DocumentsApp"
23
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "simulator-location",
3
+ "name": "Simulate GPS Location",
4
+ "description": "Set custom GPS coordinates or simulate movement (city run, freeway drive)",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "location", "gps", "testing"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl location booted set {latitude},{longitude}\"",
14
+ "description": "Set custom GPS location (replace {latitude},{longitude} e.g. 37.7749,-122.4194 for San Francisco)"
15
+ }
16
+ ],
17
+ "notes": "Presets via menu: menu_click('Features/Location/City Run') for moving GPS, menu_click('Features/Location/Freeway Drive') for highway speed. menu_click('Features/Location/Custom Location…') for dialog input. menu_click('Features/Location/None') to clear."
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-memory-warning",
3
+ "name": "Simulate Memory Warning",
4
+ "description": "Send a memory pressure warning to the running iOS app for testing memory handling",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "memory", "debug", "testing"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "key",
18
+ "key": "shift+cmd+m",
19
+ "description": "Send memory warning to foreground app"
20
+ }
21
+ ],
22
+ "notes": "Use to test didReceiveMemoryWarning handlers. Also: menu_click('Debug/Slow Animations') to debug animation issues. key('cmd+/') to open system log."
23
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "simulator-navigate-url",
3
+ "name": "Navigate to URL in Safari",
4
+ "description": "Open a URL in iOS Safari — uses simctl openurl, never type URLs manually",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "safari", "url", "navigation"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"xcrun simctl openurl booted '{url}'\"",
14
+ "description": "Open URL in Safari (replace {url}). This is the ONLY reliable way — type_text is broken in Simulator"
15
+ }
16
+ ],
17
+ "notes": "Works for any URL scheme: https://, tel:, mailto:, maps:, shortcuts://, custom deep links"
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-push-notification",
3
+ "name": "Send Push Notification",
4
+ "description": "Send a test push notification to an iOS app in the Simulator via simctl",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "push", "notification", "testing"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "applescript",
13
+ "script": "do shell script \"echo '{\\\"aps\\\":{\\\"alert\\\":{\\\"title\\\":\\\"{title}\\\",\\\"body\\\":\\\"{body}\\\"},\\\"sound\\\":\\\"default\\\"}}' > /tmp/screenhand-push.json\"",
14
+ "description": "Create push notification payload (replace {title} and {body})"
15
+ },
16
+ {
17
+ "action": "applescript",
18
+ "script": "do shell script \"xcrun simctl push booted {bundleId} /tmp/screenhand-push.json\"",
19
+ "description": "Send push notification to app (replace {bundleId})"
20
+ }
21
+ ],
22
+ "notes": "App must be installed and have push notification capability. Common test: bundleId=com.apple.mobilesafari"
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-record-screen",
3
+ "name": "Record Screen Video",
4
+ "description": "Start/stop screen recording or save programmatically via simctl",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "recording", "video", "screen"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "key",
18
+ "key": "cmd+r",
19
+ "description": "Toggle screen recording (press again to stop)"
20
+ }
21
+ ],
22
+ "notes": "For programmatic recording: xcrun simctl io booted recordVideo /path/to/output.mp4 (Ctrl+C to stop). For screenshot: xcrun simctl io booted screenshot /path/to/output.png"
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "simulator-rotate-device",
3
+ "name": "Rotate Device",
4
+ "description": "Rotate between portrait and landscape orientations",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "rotate", "orientation"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "menu_click",
18
+ "path": "Device/Rotate Right",
19
+ "description": "Rotate to landscape (use menu — keyboard shortcut is unreliable)"
20
+ }
21
+ ],
22
+ "notes": "Rotate back: menu_click('Device/Rotate Left'). Keyboard shortcuts cmd+← and cmd+→ exist but are unreliable — always use menu_click."
23
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "simulator-screenshot-appstore",
3
+ "name": "App Store Screenshot",
4
+ "description": "Take a clean App Store screenshot with perfect status bar (9:41, full battery, full signal)",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "screenshot", "app-store"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "applescript",
18
+ "script": "do shell script \"xcrun simctl status_bar booted override --time '9:41' --batteryLevel 100 --cellularBars 4 --wifiBars 3\"",
19
+ "description": "Override status bar to App Store standard (9:41, full battery/signal)"
20
+ },
21
+ {
22
+ "action": "key",
23
+ "key": "cmd+s",
24
+ "description": "Save screenshot to Desktop"
25
+ },
26
+ {
27
+ "action": "applescript",
28
+ "script": "do shell script \"xcrun simctl status_bar booted clear\"",
29
+ "description": "Clear status bar override back to normal"
30
+ }
31
+ ],
32
+ "notes": "Screenshot saves to ~/Desktop. For programmatic saving: xcrun simctl io booted screenshot /path/to/output.png"
33
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "simulator-type-text",
3
+ "name": "Type Text in iOS Field",
4
+ "description": "Type text into iOS text fields using ui_set_value — the ONLY reliable method (type_text and clipboard are both broken)",
5
+ "platform": "simulator",
6
+ "version": "1.0.0",
7
+ "tags": ["simulator", "ios", "type", "text", "input"],
8
+ "successCount": 1,
9
+ "failCount": 0,
10
+ "steps": [
11
+ {
12
+ "action": "focus",
13
+ "bundleId": "com.apple.iphonesimulator",
14
+ "description": "Bring Simulator to front"
15
+ },
16
+ {
17
+ "action": "ui_set_value",
18
+ "identifier": "{field_title}",
19
+ "value": "{your text}",
20
+ "description": "Set text via AX directly — BEST METHOD. Replace {field_title} with the AX title of the text field"
21
+ }
22
+ ],
23
+ "notes": "BROKEN methods: type_text produces garbled chars (e.g. 'aaaaaa' instead of 'apple.com'). Clipboard cmd+v is unreliable. For URLs use simctl openurl instead. Ensure Connect Hardware Keyboard is ON: key('shift+cmd+k')."
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screenhand",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "mcpName": "io.github.manushi4/screenhand",
5
5
  "description": "Give AI eyes and hands on your desktop. ScreenHand is an open-source MCP server that lets Claude and other AI agents see your screen, click buttons, type text, and control any app on macOS and Windows.",
6
6
  "homepage": "https://screenhand.com",
@@ -70,6 +70,9 @@
70
70
  "ocr",
71
71
  "computer-use"
72
72
  ],
73
+ "engines": {
74
+ "node": ">=18"
75
+ },
73
76
  "dependencies": {
74
77
  "@anthropic-ai/sdk": "^0.78.0",
75
78
  "@modelcontextprotocol/sdk": "^1.27.1",