@tapflowio/ios-agent 0.1.0-alpha.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/bin/keyboard-helper +0 -0
  4. package/bin/rotation-helper +0 -0
  5. package/bin/screencapture-helper +0 -0
  6. package/bin/touch-helper +0 -0
  7. package/dist/DeviceChromeLoader.d.ts +49 -0
  8. package/dist/DeviceChromeLoader.d.ts.map +1 -0
  9. package/dist/DeviceChromeLoader.js +680 -0
  10. package/dist/DeviceChromeLoader.js.map +1 -0
  11. package/dist/IOSAgent.d.ts +46 -0
  12. package/dist/IOSAgent.d.ts.map +1 -0
  13. package/dist/IOSAgent.js +478 -0
  14. package/dist/IOSAgent.js.map +1 -0
  15. package/dist/KeyCodeMap.d.ts +3 -0
  16. package/dist/KeyCodeMap.d.ts.map +1 -0
  17. package/dist/KeyCodeMap.js +42 -0
  18. package/dist/KeyCodeMap.js.map +1 -0
  19. package/dist/KeyboardHelperDaemon.d.ts +13 -0
  20. package/dist/KeyboardHelperDaemon.d.ts.map +1 -0
  21. package/dist/KeyboardHelperDaemon.js +96 -0
  22. package/dist/KeyboardHelperDaemon.js.map +1 -0
  23. package/dist/MjpegStreamer.d.ts +10 -0
  24. package/dist/MjpegStreamer.d.ts.map +1 -0
  25. package/dist/MjpegStreamer.js +40 -0
  26. package/dist/MjpegStreamer.js.map +1 -0
  27. package/dist/ScreenCaptureStreamer.d.ts +7 -0
  28. package/dist/ScreenCaptureStreamer.d.ts.map +1 -0
  29. package/dist/ScreenCaptureStreamer.js +92 -0
  30. package/dist/ScreenCaptureStreamer.js.map +1 -0
  31. package/dist/SimctlWrapper.d.ts +21 -0
  32. package/dist/SimctlWrapper.d.ts.map +1 -0
  33. package/dist/SimctlWrapper.js +154 -0
  34. package/dist/SimctlWrapper.js.map +1 -0
  35. package/dist/TouchHelper.d.ts +21 -0
  36. package/dist/TouchHelper.d.ts.map +1 -0
  37. package/dist/TouchHelper.js +110 -0
  38. package/dist/TouchHelper.js.map +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +5 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/simctl.d.ts +6 -0
  44. package/dist/simctl.d.ts.map +1 -0
  45. package/dist/simctl.js +64 -0
  46. package/dist/simctl.js.map +1 -0
  47. package/package.json +66 -0
@@ -0,0 +1,42 @@
1
+ // USB HID Keyboard/Keypad Page (0x07) — KeyboardEvent.code → usage ID
2
+ export const KEY_CODE_MAP = {
3
+ // Letters
4
+ KeyA: 0x04, KeyB: 0x05, KeyC: 0x06, KeyD: 0x07, KeyE: 0x08,
5
+ KeyF: 0x09, KeyG: 0x0A, KeyH: 0x0B, KeyI: 0x0C, KeyJ: 0x0D,
6
+ KeyK: 0x0E, KeyL: 0x0F, KeyM: 0x10, KeyN: 0x11, KeyO: 0x12,
7
+ KeyP: 0x13, KeyQ: 0x14, KeyR: 0x15, KeyS: 0x16, KeyT: 0x17,
8
+ KeyU: 0x18, KeyV: 0x19, KeyW: 0x1A, KeyX: 0x1B, KeyY: 0x1C, KeyZ: 0x1D,
9
+ // Digits
10
+ Digit1: 0x1E, Digit2: 0x1F, Digit3: 0x20, Digit4: 0x21, Digit5: 0x22,
11
+ Digit6: 0x23, Digit7: 0x24, Digit8: 0x25, Digit9: 0x26, Digit0: 0x27,
12
+ // Control keys
13
+ Enter: 0x28, Escape: 0x29, Backspace: 0x2A, Tab: 0x2B, Space: 0x2C,
14
+ // Punctuation
15
+ Minus: 0x2D, Equal: 0x2E, BracketLeft: 0x2F, BracketRight: 0x30,
16
+ Backslash: 0x31, Semicolon: 0x33, Quote: 0x34, Backquote: 0x35,
17
+ Comma: 0x36, Period: 0x37, Slash: 0x38,
18
+ // Lock / function keys
19
+ CapsLock: 0x39,
20
+ F1: 0x3A, F2: 0x3B, F3: 0x3C, F4: 0x3D, F5: 0x3E, F6: 0x3F,
21
+ F7: 0x40, F8: 0x41, F9: 0x42, F10: 0x43, F11: 0x44, F12: 0x45,
22
+ // Navigation
23
+ Delete: 0x4C, Home: 0x4A, End: 0x4D, PageUp: 0x4B, PageDown: 0x4E,
24
+ ArrowRight: 0x4F, ArrowLeft: 0x50, ArrowDown: 0x51, ArrowUp: 0x52,
25
+ // Numpad
26
+ NumpadDivide: 0x54, NumpadMultiply: 0x55, NumpadSubtract: 0x56,
27
+ NumpadAdd: 0x57, NumpadEnter: 0x58,
28
+ Numpad1: 0x59, Numpad2: 0x5A, Numpad3: 0x5B, Numpad4: 0x5C,
29
+ Numpad5: 0x5D, Numpad6: 0x5E, Numpad7: 0x5F, Numpad8: 0x60,
30
+ Numpad9: 0x61, Numpad0: 0x62, NumpadDecimal: 0x63,
31
+ // IME / language toggle keys (Korean 한/영 = Lang1, Hanja = Lang2)
32
+ Lang1: 0x90, Lang2: 0x91,
33
+ // Modifiers
34
+ ControlLeft: 0xE0, ShiftLeft: 0xE1, AltLeft: 0xE2, MetaLeft: 0xE3,
35
+ ControlRight: 0xE4, ShiftRight: 0xE5, AltRight: 0xE6, MetaRight: 0xE7,
36
+ };
37
+ // USB HID modifier bitmap (bit position per key)
38
+ export const MODIFIER_BITS = {
39
+ ControlLeft: 0x01, ShiftLeft: 0x02, AltLeft: 0x04, MetaLeft: 0x08,
40
+ ControlRight: 0x10, ShiftRight: 0x20, AltRight: 0x40, MetaRight: 0x80,
41
+ };
42
+ //# sourceMappingURL=KeyCodeMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyCodeMap.js","sourceRoot":"","sources":["../src/KeyCodeMap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export declare class KeyboardHelperDaemon {
2
+ private proc;
3
+ private pending;
4
+ private queue;
5
+ private draining;
6
+ show(udid: string): Promise<void>;
7
+ hide(udid: string): Promise<void>;
8
+ stop(): void;
9
+ private enqueue;
10
+ private drain;
11
+ private ensureStarted;
12
+ }
13
+ //# sourceMappingURL=KeyboardHelperDaemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyboardHelperDaemon.d.ts","sourceRoot":"","sources":["../src/KeyboardHelperDaemon.ts"],"names":[],"mappings":"AAcA,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,QAAQ,CAAQ;IAExB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEjC,IAAI,IAAI,IAAI;IAWZ,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,aAAa;CAgDtB"}
@@ -0,0 +1,96 @@
1
+ import { spawn } from 'child_process';
2
+ import { createInterface } from 'readline';
3
+ import { join } from 'path';
4
+ import { createLogger } from '@tapflowio/agent-core';
5
+ const logger = createLogger('ios-agent:keyboard-helper');
6
+ const BINARY = join(import.meta.dirname, '..', 'bin', 'keyboard-helper');
7
+ export class KeyboardHelperDaemon {
8
+ proc = null;
9
+ pending = null;
10
+ queue = [];
11
+ draining = false;
12
+ show(udid) { return this.enqueue(`show ${udid}`); }
13
+ hide(udid) { return this.enqueue(`hide ${udid}`); }
14
+ stop() {
15
+ this.proc?.kill();
16
+ this.proc = null;
17
+ const err = new Error('keyboard-helper daemon stopped');
18
+ this.pending?.reject(err);
19
+ this.pending = null;
20
+ for (const item of this.queue)
21
+ item.reject(err);
22
+ this.queue = [];
23
+ this.draining = false;
24
+ }
25
+ enqueue(cmd) {
26
+ return new Promise((resolve, reject) => {
27
+ this.queue.push({ cmd, resolve, reject });
28
+ this.drain();
29
+ });
30
+ }
31
+ drain() {
32
+ if (this.draining || this.queue.length === 0)
33
+ return;
34
+ this.draining = true;
35
+ const item = this.queue.shift();
36
+ this.pending = { resolve: item.resolve, reject: item.reject };
37
+ this.ensureStarted();
38
+ if (!this.proc?.stdin?.writable) {
39
+ const err = new Error('keyboard-helper stdin not writable');
40
+ this.pending.reject(err);
41
+ this.pending = null;
42
+ this.draining = false;
43
+ this.drain();
44
+ return;
45
+ }
46
+ this.proc.stdin.write(item.cmd + '\n');
47
+ }
48
+ ensureStarted() {
49
+ if (this.proc !== null)
50
+ return;
51
+ const proc = spawn(BINARY, ['--daemon'], { stdio: ['pipe', 'pipe', 'pipe'] });
52
+ this.proc = proc;
53
+ const rl = createInterface({ input: proc.stdout });
54
+ rl.on('line', (line) => {
55
+ const pending = this.pending;
56
+ this.pending = null;
57
+ this.draining = false;
58
+ if (!pending)
59
+ return;
60
+ if (line.startsWith('ok')) {
61
+ pending.resolve();
62
+ }
63
+ else {
64
+ pending.reject(new Error(line.replace(/^err\s*/, '') || 'keyboard-helper error'));
65
+ }
66
+ this.drain();
67
+ });
68
+ proc.stderr?.on('data', (d) => {
69
+ logger.error(d.toString().trim());
70
+ });
71
+ proc.on('exit', (code) => {
72
+ logger.error(`exited with code ${code ?? 'null'}`);
73
+ this.proc = null;
74
+ // reject any in-flight pending (rl 'line' won't fire after exit)
75
+ const pending = this.pending;
76
+ this.pending = null;
77
+ this.draining = false;
78
+ if (pending) {
79
+ pending.reject(new Error(`keyboard-helper exited with code ${code}`));
80
+ this.drain();
81
+ }
82
+ });
83
+ proc.on('error', (e) => {
84
+ logger.error(`spawn error: ${e.message}`);
85
+ this.proc = null;
86
+ const pending = this.pending;
87
+ this.pending = null;
88
+ this.draining = false;
89
+ if (pending) {
90
+ pending.reject(e);
91
+ this.drain();
92
+ }
93
+ });
94
+ }
95
+ }
96
+ //# sourceMappingURL=KeyboardHelperDaemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyboardHelperDaemon.js","sourceRoot":"","sources":["../src/KeyboardHelperDaemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SimctlWrapper } from './SimctlWrapper.js';
2
+ type Screenshottable = Pick<SimctlWrapper, 'screenshot'>;
3
+ export declare class MjpegStreamer {
4
+ private readonly simctl;
5
+ private readonly intervalMs;
6
+ constructor(simctl: Screenshottable, intervalMs?: number);
7
+ start(): ReadableStream<Buffer>;
8
+ }
9
+ export {};
10
+ //# sourceMappingURL=MjpegStreamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MjpegStreamer.d.ts","sourceRoot":"","sources":["../src/MjpegStreamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,KAAK,eAAe,GAAG,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAA;AAExD,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,MAAM,EAAE,eAAe,EACvB,UAAU,GAAE,MAAY;IAG3C,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC;CA8BhC"}
@@ -0,0 +1,40 @@
1
+ export class MjpegStreamer {
2
+ simctl;
3
+ intervalMs;
4
+ constructor(simctl, intervalMs = 100) {
5
+ this.simctl = simctl;
6
+ this.intervalMs = intervalMs;
7
+ }
8
+ start() {
9
+ let timer = null;
10
+ let capturing = false;
11
+ return new ReadableStream({
12
+ start: (controller) => {
13
+ const capture = async () => {
14
+ if (capturing)
15
+ return;
16
+ capturing = true;
17
+ try {
18
+ const frame = await this.simctl.screenshot();
19
+ controller.enqueue(frame);
20
+ }
21
+ catch (err) {
22
+ controller.error(err);
23
+ }
24
+ finally {
25
+ capturing = false;
26
+ }
27
+ };
28
+ void capture();
29
+ timer = setInterval(capture, this.intervalMs);
30
+ },
31
+ cancel: () => {
32
+ if (timer !== null) {
33
+ clearInterval(timer);
34
+ timer = null;
35
+ }
36
+ },
37
+ });
38
+ }
39
+ }
40
+ //# sourceMappingURL=MjpegStreamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MjpegStreamer.js","sourceRoot":"","sources":["../src/MjpegStreamer.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,MAAuB,EACvB,aAAqB,GAAG;QADxB,WAAM,GAAN,MAAM,CAAiB;QACvB,eAAU,GAAV,UAAU,CAAc;IACxC,CAAC;IAEJ,KAAK;QACH,IAAI,KAAK,GAA0C,IAAI,CAAA;QACvD,IAAI,SAAS,GAAG,KAAK,CAAA;QAErB,OAAO,IAAI,cAAc,CAAS;YAChC,KAAK,EAAE,CAAC,UAAU,EAAE,EAAE;gBACpB,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;oBACzB,IAAI,SAAS;wBAAE,OAAM;oBACrB,SAAS,GAAG,IAAI,CAAA;oBAChB,IAAI,CAAC;wBACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAA;wBAC5C,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;oBAC3B,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;oBACvB,CAAC;4BAAS,CAAC;wBACT,SAAS,GAAG,KAAK,CAAA;oBACnB,CAAC;gBACH,CAAC,CAAA;gBAED,KAAK,OAAO,EAAE,CAAA;gBACd,KAAK,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;YAC/C,CAAC;YACD,MAAM,EAAE,GAAG,EAAE;gBACX,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,aAAa,CAAC,KAAK,CAAC,CAAA;oBACpB,KAAK,GAAG,IAAI,CAAA;gBACd,CAAC;YACH,CAAC;SACF,CAAC,CAAA;IACJ,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export declare class ScreenCaptureStreamer {
2
+ private readonly fps;
3
+ private readonly udid;
4
+ constructor(fps?: number, udid?: string);
5
+ start(): ReadableStream<Buffer>;
6
+ }
7
+ //# sourceMappingURL=ScreenCaptureStreamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenCaptureStreamer.d.ts","sourceRoot":"","sources":["../src/ScreenCaptureStreamer.ts"],"names":[],"mappings":"AAiCA,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,GAAE,MAAW,EAChB,IAAI,GAAE,MAAiB;IAG1C,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC;CAuDhC"}
@@ -0,0 +1,92 @@
1
+ import { spawn, execFileSync } from 'child_process';
2
+ import { existsSync, statSync, unlinkSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { createLogger } from '@tapflowio/agent-core';
5
+ const logger = createLogger('ios-agent:screencapture');
6
+ const SWIFT_SRC = join(import.meta.dirname, '..', 'src', 'screencapture-helper.swift');
7
+ const BINARY = join(import.meta.dirname, '..', 'bin', 'screencapture-helper');
8
+ function ensureCompiled() {
9
+ if (existsSync(BINARY)) {
10
+ // Swift source is not included in the published package — skip recompilation check
11
+ if (!existsSync(SWIFT_SRC))
12
+ return;
13
+ const srcMtime = statSync(SWIFT_SRC).mtimeMs;
14
+ const binMtime = statSync(BINARY).mtimeMs;
15
+ if (binMtime >= srcMtime)
16
+ return;
17
+ logger.info('Swift source changed, recompiling...');
18
+ unlinkSync(BINARY);
19
+ }
20
+ if (!existsSync(SWIFT_SRC)) {
21
+ throw new Error('screencapture-helper binary missing and Swift source not found — reinstall @tapflowio/ios-agent');
22
+ }
23
+ logger.info('compiling screencapture-helper...');
24
+ execFileSync('swiftc', [
25
+ SWIFT_SRC,
26
+ '-o', BINARY,
27
+ '-framework', 'CoreVideo',
28
+ '-framework', 'ImageIO',
29
+ ], { stdio: ['ignore', 'ignore', 'inherit'] });
30
+ logger.info('compiled OK');
31
+ }
32
+ export class ScreenCaptureStreamer {
33
+ fps;
34
+ udid;
35
+ constructor(fps = 30, udid = 'booted') {
36
+ this.fps = fps;
37
+ this.udid = udid;
38
+ }
39
+ start() {
40
+ ensureCompiled();
41
+ const proc = spawn(BINARY, [String(this.fps), this.udid], {
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ });
44
+ proc.stderr.on('data', (d) => process.stderr.write(d));
45
+ // done is hoisted so both start() and cancel() share the same flag
46
+ let done = false;
47
+ return new ReadableStream({
48
+ start(controller) {
49
+ let buf = Buffer.alloc(0);
50
+ const close = () => {
51
+ if (done)
52
+ return;
53
+ done = true;
54
+ controller.close();
55
+ };
56
+ const error = (e) => {
57
+ if (done)
58
+ return;
59
+ done = true;
60
+ controller.error(e);
61
+ };
62
+ proc.stdout.on('data', (chunk) => {
63
+ if (done)
64
+ return;
65
+ buf = Buffer.concat([buf, chunk]);
66
+ while (buf.length >= 4) {
67
+ const frameLen = buf.readUInt32BE(0);
68
+ if (buf.length < 4 + frameLen)
69
+ break;
70
+ controller.enqueue(buf.subarray(4, 4 + frameLen));
71
+ buf = buf.subarray(4 + frameLen);
72
+ }
73
+ });
74
+ proc.stdout.on('end', close);
75
+ proc.on('error', error);
76
+ proc.on('exit', (code) => {
77
+ if (code !== null && code !== 0)
78
+ error(new Error(`[ScreenCaptureStreamer] exited with code ${code}`));
79
+ else
80
+ close();
81
+ });
82
+ },
83
+ cancel() {
84
+ done = true;
85
+ proc.kill('SIGTERM');
86
+ const killTimer = setTimeout(() => proc.kill('SIGKILL'), 1000);
87
+ proc.once('exit', () => clearTimeout(killTimer));
88
+ },
89
+ });
90
+ }
91
+ }
92
+ //# sourceMappingURL=ScreenCaptureStreamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenCaptureStreamer.js","sourceRoot":"","sources":["../src/ScreenCaptureStreamer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,MAAM,MAAM,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAA;AAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,4BAA4B,CAAC,CAAA;AACtF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,sBAAsB,CAAC,CAAA;AAE7E,SAAS,cAAc;IACrB,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvB,mFAAmF;QACnF,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAM;QAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAA;QAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,CAAA;QACzC,IAAI,QAAQ,IAAI,QAAQ;YAAE,OAAM;QAChC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;QACnD,UAAU,CAAC,MAAM,CAAC,CAAA;IACpB,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,iGAAiG,CAAC,CAAA;IACpH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAA;IAChD,YAAY,CAAC,QAAQ,EAAE;QACrB,SAAS;QACT,IAAI,EAAE,MAAM;QACZ,YAAY,EAAE,WAAW;QACzB,YAAY,EAAE,SAAS;KACxB,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC,CAAA;IAC9C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,OAAO,qBAAqB;IAEb;IACA;IAFnB,YACmB,MAAc,EAAE,EAChB,OAAe,QAAQ;QADvB,QAAG,GAAH,GAAG,CAAa;QAChB,SAAI,GAAJ,IAAI,CAAmB;IACvC,CAAC;IAEJ,KAAK;QACH,cAAc,EAAE,CAAA;QAEhB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;YACxD,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAA;QAEF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAE9D,mEAAmE;QACnE,IAAI,IAAI,GAAG,KAAK,CAAA;QAEhB,OAAO,IAAI,cAAc,CAAS;YAChC,KAAK,CAAC,UAAU;gBACd,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBAEzB,MAAM,KAAK,GAAG,GAAG,EAAE;oBACjB,IAAI,IAAI;wBAAE,OAAM;oBAChB,IAAI,GAAG,IAAI,CAAA;oBACX,UAAU,CAAC,KAAK,EAAE,CAAA;gBACpB,CAAC,CAAA;gBACD,MAAM,KAAK,GAAG,CAAC,CAAQ,EAAE,EAAE;oBACzB,IAAI,IAAI;wBAAE,OAAM;oBAChB,IAAI,GAAG,IAAI,CAAA;oBACX,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBACrB,CAAC,CAAA;gBAED,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;oBACvC,IAAI,IAAI;wBAAE,OAAM;oBAChB,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;oBACjC,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;wBACvB,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;wBACpC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,QAAQ;4BAAE,MAAK;wBACpC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAA;wBACjD,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAA;oBAClC,CAAC;gBACH,CAAC,CAAC,CAAA;gBAEF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;gBAC5B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;gBACvB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBACvB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC;wBAC7B,KAAK,CAAC,IAAI,KAAK,CAAC,4CAA4C,IAAI,EAAE,CAAC,CAAC,CAAA;;wBAEpE,KAAK,EAAE,CAAA;gBACX,CAAC,CAAC,CAAA;YACJ,CAAC;YACD,MAAM;gBACJ,IAAI,GAAG,IAAI,CAAA;gBACX,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;gBACpB,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAA;gBAC9D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;YAClD,CAAC;SACF,CAAC,CAAA;IACJ,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ import type { Device } from '@tapflowio/agent-core';
2
+ import { type SimctlRunner } from './simctl.js';
3
+ export declare class SimctlWrapper {
4
+ private readonly runner;
5
+ private readonly kbd;
6
+ constructor(runner?: SimctlRunner);
7
+ listDevices(): Promise<Device[]>;
8
+ boot(deviceId: string): Promise<void>;
9
+ shutdown(deviceId: string): Promise<void>;
10
+ erase(deviceId: string): Promise<void>;
11
+ uninstallApp(bundleId: string): Promise<void>;
12
+ installApp(appPath: string): Promise<void>;
13
+ launchApp(bundleId: string): Promise<void>;
14
+ screenshot(): Promise<Buffer>;
15
+ syncKeyboardsFromLanguages(udid: string): Promise<void>;
16
+ rotate(udid: string, orientation: 'portrait' | 'landscapeLeft' | 'landscapeRight' | 'portraitUpsideDown'): Promise<void>;
17
+ showSoftwareKeyboard(udid: string): Promise<void>;
18
+ hideSoftwareKeyboard(udid: string): Promise<void>;
19
+ stopKeyboardDaemon(): void;
20
+ }
21
+ //# sourceMappingURL=SimctlWrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SimctlWrapper.d.ts","sourceRoot":"","sources":["../src/SimctlWrapper.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AA4B9D,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;gBAEpB,MAAM,GAAE,YAA4B;IAE3D,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBhC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAc7B,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BvD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAG,eAAe,GAAG,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxH,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,kBAAkB,IAAI,IAAI;CAG3B"}
@@ -0,0 +1,154 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { execFile } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { promises as fs } from 'fs';
5
+ import { tmpdir } from 'os';
6
+ import { join } from 'path';
7
+ const execFileAsync = promisify(execFile);
8
+ const ROTATION_HELPER = join(import.meta.dirname, '..', 'bin', 'rotation-helper');
9
+ // Language code → iOS AppleKeyboards entry string with hw=Automatic.
10
+ // hw=Automatic lets iOS switch the hardware layout when the input source changes via LANG1/CapsLock.
11
+ // hw=Korean (previous approach) locked the hardware layout to Korean regardless of active input source,
12
+ // which caused the toggle HUD to appear but not actually change key output.
13
+ const LANG_KEYBOARD_MAP = {
14
+ ko: 'ko_KR@sw=Korean;hw=Automatic',
15
+ ja: 'ja_JP@sw=Japanese-Kana;hw=Automatic',
16
+ zh: 'zh_Hans_CN@sw=ChineseSimplified-Pinyin;hw=Automatic',
17
+ fr: 'fr_FR@sw=French;hw=Automatic',
18
+ de: 'de_DE@sw=German;hw=Automatic',
19
+ es: 'es_ES@sw=Spanish;hw=Automatic',
20
+ it: 'it_IT@sw=Italian;hw=Automatic',
21
+ pt: 'pt_BR@sw=Portuguese;hw=Automatic',
22
+ ru: 'ru_RU@sw=Russian;hw=Automatic',
23
+ ar: 'ar@sw=Arabic;hw=Automatic',
24
+ th: 'th_TH@sw=Thai;hw=Automatic',
25
+ };
26
+ function langToKeyboard(lang) {
27
+ const code = lang.split('-')[0].toLowerCase();
28
+ return LANG_KEYBOARD_MAP[code] ?? 'en_US@sw=QWERTY;hw=Automatic';
29
+ }
30
+ import { defaultRunner } from './simctl.js';
31
+ import { KeyboardHelperDaemon } from './KeyboardHelperDaemon.js';
32
+ function toDeviceStatus(state) {
33
+ if (state === 'Booted')
34
+ return 'booted';
35
+ if (state === 'Shutdown')
36
+ return 'shutdown';
37
+ return 'unknown';
38
+ }
39
+ // "com.apple.CoreSimulator.SimRuntime.iOS-18-3" → "iOS 18.3"
40
+ function parseOsVersion(runtimeKey) {
41
+ const m = runtimeKey.match(/\.([A-Za-z]+)-(\d+(?:-\d+)*)$/);
42
+ if (!m)
43
+ return undefined;
44
+ return `${m[1]} ${m[2].replace(/-/g, '.')}`;
45
+ }
46
+ export class SimctlWrapper {
47
+ runner;
48
+ kbd = new KeyboardHelperDaemon();
49
+ constructor(runner = defaultRunner) {
50
+ this.runner = runner;
51
+ }
52
+ async listDevices() {
53
+ const output = await this.runner.exec('list', 'devices', '--json');
54
+ const parsed = JSON.parse(output);
55
+ const devices = [];
56
+ for (const [runtimeKey, runtimeDevices] of Object.entries(parsed.devices)) {
57
+ const osVersion = parseOsVersion(runtimeKey);
58
+ for (const d of runtimeDevices) {
59
+ if (!d.isAvailable)
60
+ continue;
61
+ devices.push({
62
+ id: d.udid,
63
+ name: d.name,
64
+ platform: 'ios',
65
+ status: toDeviceStatus(d.state),
66
+ typeId: d.deviceTypeIdentifier,
67
+ osVersion,
68
+ });
69
+ }
70
+ }
71
+ return devices;
72
+ }
73
+ async boot(deviceId) {
74
+ try {
75
+ await this.runner.exec('boot', deviceId);
76
+ }
77
+ catch (err) {
78
+ const stderr = err.stderr ?? '';
79
+ // already booted is not an error
80
+ if (stderr.includes('Unable to boot device in current state: Booted'))
81
+ return;
82
+ throw err;
83
+ }
84
+ // Xcode 14+ auto-opens Simulator.app on `simctl boot`.
85
+ // Quitting kills the simulator runtime, so just hide the app window instead.
86
+ execFile('osascript', ['-e', 'tell application "Simulator" to set visible to false'], () => { });
87
+ }
88
+ async shutdown(deviceId) {
89
+ await this.runner.exec('shutdown', deviceId);
90
+ }
91
+ async erase(deviceId) {
92
+ await this.runner.exec('erase', deviceId);
93
+ }
94
+ async uninstallApp(bundleId) {
95
+ await this.runner.exec('uninstall', 'booted', bundleId);
96
+ }
97
+ async installApp(appPath) {
98
+ await this.runner.exec('install', 'booted', appPath);
99
+ }
100
+ async launchApp(bundleId) {
101
+ await this.runner.exec('launch', 'booted', bundleId);
102
+ }
103
+ async screenshot() {
104
+ const tmpPath = `${tmpdir()}/tapflow-${randomUUID()}.png`;
105
+ try {
106
+ await this.runner.exec('io', 'booted', 'screenshot', tmpPath);
107
+ return await fs.readFile(tmpPath);
108
+ }
109
+ finally {
110
+ await fs.unlink(tmpPath).catch(() => { });
111
+ }
112
+ }
113
+ // Reads AppleLanguages from the simulator's Global Domain and rewrites AppleKeyboards
114
+ // so each entry uses hw=Automatic. This lets iOS switch hardware key layout when the
115
+ // user toggles the input source (via LANG1/CapsLock), fixing the iOS 15+ bug where
116
+ // hw=Korean locks the hardware layout regardless of the active input source.
117
+ async syncKeyboardsFromLanguages(udid) {
118
+ let languages;
119
+ try {
120
+ const out = await this.runner.exec('spawn', udid, 'defaults', 'read', '-g', 'AppleLanguages');
121
+ // Output: (\n "ko-KR",\n "en-US"\n)
122
+ languages = [...out.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
123
+ }
124
+ catch {
125
+ languages = [];
126
+ }
127
+ if (languages.length === 0)
128
+ return;
129
+ // Always include English as a fallback so the English QWERTY keyboard is available.
130
+ const hasEnglish = languages.some((l) => l.toLowerCase().startsWith('en'));
131
+ const allLangs = hasEnglish ? languages : [...languages, 'en-US'];
132
+ const keyboards = [...new Set([...allLangs.map(langToKeyboard), 'emoji@sw=Emoji'])];
133
+ await this.runner.exec('spawn', udid, 'defaults', 'write', '-g', 'AppleKeyboards', '-array', ...keyboards);
134
+ // Restart the keyboard daemon so it picks up the new settings immediately.
135
+ // Errors are silently ignored — changes will take effect on next text field focus if the daemon isn't running yet.
136
+ try {
137
+ await this.runner.exec('spawn', udid, 'launchctl', 'kickstart', '-k', 'system/com.apple.kbd');
138
+ }
139
+ catch { /* expected on some iOS versions */ }
140
+ }
141
+ async rotate(udid, orientation) {
142
+ await execFileAsync(ROTATION_HELPER, [orientation, udid]);
143
+ }
144
+ async showSoftwareKeyboard(udid) {
145
+ await this.kbd.show(udid);
146
+ }
147
+ async hideSoftwareKeyboard(udid) {
148
+ await this.kbd.hide(udid);
149
+ }
150
+ stopKeyboardDaemon() {
151
+ this.kbd.stop();
152
+ }
153
+ }
154
+ //# sourceMappingURL=SimctlWrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SimctlWrapper.js","sourceRoot":"","sources":["../src/SimctlWrapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare class TouchHelper {
2
+ private readonly udid;
3
+ private proc;
4
+ private lastX;
5
+ private lastY;
6
+ constructor(udid: string);
7
+ start(): void;
8
+ stop(): void;
9
+ touchStart(x: number, y: number): void;
10
+ touchMove(x: number, y: number): void;
11
+ touchEnd(): void;
12
+ pressButton(usagePage: number, usage: number): void;
13
+ pressLegacyButton(code: number): void;
14
+ pinchStart(x1: number, y1: number, x2: number, y2: number): void;
15
+ pinchMove(x1: number, y1: number, x2: number, y2: number): void;
16
+ pinchEnd(): void;
17
+ sendKey(usage: number, modifiers?: number): void;
18
+ private writeTwoFinger;
19
+ private write;
20
+ }
21
+ //# sourceMappingURL=TouchHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TouchHelper.d.ts","sourceRoot":"","sources":["../src/TouchHelper.ts"],"names":[],"mappings":"AAQA,qBAAa,WAAW;IAKV,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAI;gBAEY,IAAI,EAAE,MAAM;IAEzC,KAAK,IAAI,IAAI;IAgBb,IAAI,IAAI,IAAI;IAKZ,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAMtC,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAMrC,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAUnD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASrC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAIhE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAI/D,QAAQ,IAAI,IAAI;IAOhB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,IAAI;IAY3C,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,KAAK;CAQd"}