@tapflowio/audiotap-helper 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tapflow contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @tapflowio/audiotap-helper
2
+
3
+ Shared macOS Core Audio process-tap helper for [tapflow](https://github.com/jo-duchan/tapflow).
4
+
5
+ Taps a host process's audio by PID — no device routing, no dylib injection, no host-output hijack. Used internally by `@tapflowio/ios-agent` (iOS simulator audio capture) and `@tapflowio/android-agent` (Android emulator host-mute). Ships as a prebuilt, ad-hoc-signed `.app` so it holds its own one-time audio-recording (TCC) grant.
6
+
7
+ ## Requirements
8
+
9
+ - macOS 14.2+ (Core Audio process taps)
10
+ - Node.js ≥ 20
11
+
12
+ ## Usage
13
+
14
+ Internal building block of tapflow — not intended for standalone use. The agents call it via the exported helpers:
15
+
16
+ ```ts
17
+ import { ensureHelperApp, launchAudioHelper, launchMuteOnlyTap, isAudioSupported } from '@tapflowio/audiotap-helper'
18
+ ```
19
+
20
+ See [contributing/simulator-audio.md](https://github.com/jo-duchan/tapflow/blob/main/contributing/simulator-audio.md) for the design.
21
+
22
+ ## License
23
+
24
+ [MIT](LICENSE) — part of the [tapflow](https://github.com/jo-duchan/tapflow) project.
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0"><dict>
4
+ <key>com.apple.security.device.audio-input</key><true/>
5
+ </dict></plist>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0"><dict>
4
+ <key>CFBundleExecutable</key><string>audiotap-helper</string>
5
+ <key>CFBundleIdentifier</key><string>io.tapflow.audiotap-helper</string>
6
+ <key>CFBundleName</key><string>audiotap-helper</string>
7
+ <key>CFBundlePackageType</key><string>APPL</string>
8
+ <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
9
+ <key>LSUIElement</key><true/>
10
+ <key>LSMinimumSystemVersion</key><string>14.2</string>
11
+ <key>NSAudioCaptureUsageDescription</key><string>tapflow taps the simulator/emulator audio to stream it to the browser.</string>
12
+ <key>NSMicrophoneUsageDescription</key><string>tapflow taps the simulator/emulator audio to stream it to the browser.</string>
13
+ </dict></plist>
@@ -0,0 +1,115 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>files</key>
6
+ <dict/>
7
+ <key>files2</key>
8
+ <dict/>
9
+ <key>rules</key>
10
+ <dict>
11
+ <key>^Resources/</key>
12
+ <true/>
13
+ <key>^Resources/.*\.lproj/</key>
14
+ <dict>
15
+ <key>optional</key>
16
+ <true/>
17
+ <key>weight</key>
18
+ <real>1000</real>
19
+ </dict>
20
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
21
+ <dict>
22
+ <key>omit</key>
23
+ <true/>
24
+ <key>weight</key>
25
+ <real>1100</real>
26
+ </dict>
27
+ <key>^Resources/Base\.lproj/</key>
28
+ <dict>
29
+ <key>weight</key>
30
+ <real>1010</real>
31
+ </dict>
32
+ <key>^version.plist$</key>
33
+ <true/>
34
+ </dict>
35
+ <key>rules2</key>
36
+ <dict>
37
+ <key>.*\.dSYM($|/)</key>
38
+ <dict>
39
+ <key>weight</key>
40
+ <real>11</real>
41
+ </dict>
42
+ <key>^(.*/)?\.DS_Store$</key>
43
+ <dict>
44
+ <key>omit</key>
45
+ <true/>
46
+ <key>weight</key>
47
+ <real>2000</real>
48
+ </dict>
49
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
50
+ <dict>
51
+ <key>nested</key>
52
+ <true/>
53
+ <key>weight</key>
54
+ <real>10</real>
55
+ </dict>
56
+ <key>^.*</key>
57
+ <true/>
58
+ <key>^Info\.plist$</key>
59
+ <dict>
60
+ <key>omit</key>
61
+ <true/>
62
+ <key>weight</key>
63
+ <real>20</real>
64
+ </dict>
65
+ <key>^PkgInfo$</key>
66
+ <dict>
67
+ <key>omit</key>
68
+ <true/>
69
+ <key>weight</key>
70
+ <real>20</real>
71
+ </dict>
72
+ <key>^Resources/</key>
73
+ <dict>
74
+ <key>weight</key>
75
+ <real>20</real>
76
+ </dict>
77
+ <key>^Resources/.*\.lproj/</key>
78
+ <dict>
79
+ <key>optional</key>
80
+ <true/>
81
+ <key>weight</key>
82
+ <real>1000</real>
83
+ </dict>
84
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
85
+ <dict>
86
+ <key>omit</key>
87
+ <true/>
88
+ <key>weight</key>
89
+ <real>1100</real>
90
+ </dict>
91
+ <key>^Resources/Base\.lproj/</key>
92
+ <dict>
93
+ <key>weight</key>
94
+ <real>1010</real>
95
+ </dict>
96
+ <key>^[^/]+$</key>
97
+ <dict>
98
+ <key>nested</key>
99
+ <true/>
100
+ <key>weight</key>
101
+ <real>10</real>
102
+ </dict>
103
+ <key>^embedded\.provisionprofile$</key>
104
+ <dict>
105
+ <key>weight</key>
106
+ <real>20</real>
107
+ </dict>
108
+ <key>^version\.plist$</key>
109
+ <dict>
110
+ <key>weight</key>
111
+ <real>20</real>
112
+ </dict>
113
+ </dict>
114
+ </dict>
115
+ </plist>
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Build (mtime-gated) the audiotap-helper as a signed `.app` and return its path.
3
+ *
4
+ * WHY a `.app`, not a bare binary like the other helpers: a Core Audio process tap returns silence
5
+ * unless the *responsible process* holds the audio-recording TCC grant. A CLI child the agent spawns
6
+ * inherits the agent's (ungranted) responsibility. Launched via `open` (LaunchServices), the helper
7
+ * is its own responsible process with its own one-time grant — so it must be a bundle. Steady state
8
+ * (unchanged source) skips the rebuild/re-sign, so the grant persists (TCC keys on the cdhash).
9
+ */
10
+ export declare function ensureHelperApp(): string;
11
+ /**
12
+ * Launch the helper bundle via LaunchServices, pointed at the agent's loopback port and the simulator
13
+ * process PID(s) to tap (iOS capture). `-g` keeps it backgrounded (no focus steal). `-n` forces a NEW
14
+ * instance per simulator — without it `open -a` reuses the first sim's running helper, so a second
15
+ * concurrent sim never gets its own tap and its audio silently breaks. LaunchServices makes each its
16
+ * own TCC-responsible process; it connects back to `port` and streams PCM.
17
+ */
18
+ export declare function launchAudioHelper(appPath: string, port: number, pids: number[]): void;
19
+ /**
20
+ * Launch the helper in mute-only mode (Android host-mute symmetry, #341): it holds a `.muted` process
21
+ * tap on `pids` (the emulator/qemu process) to silence their host output, but captures/streams nothing
22
+ * — Android captures audio via gRPC. No port/socket. The helper self-exits once every target pid is
23
+ * gone (emulator stopped). `-n` forces a fresh instance so it never collides with the iOS capture helper.
24
+ */
25
+ export declare function launchMuteOnlyTap(appPath: string, pids: number[]): void;
26
+ export declare function isAudioSupported(): boolean;
27
+ /**
28
+ * Prime the audio-capture TCC grant up front (from `tapflow setup ios` / `tapflow agent start`) so the
29
+ * operator approves it while present — not at first simulator boot, which a headless operator would miss.
30
+ *
31
+ * Runs the helper's --request-permission mode: a global tap whose *capture start* raises the same
32
+ * audio-capture prompt a per-pid tap needs (the grant keys on the app cdhash + service, not the tap
33
+ * shape), so no port/pid/booted simulator is required. The grant isn't readable back, so the caller
34
+ * treats a clean return as "prompt shown / answered" and leaves approve-vs-deny to the operator.
35
+ *
36
+ * `wait` (default true, for `tapflow setup ios`): `open -W` blocks until the modal is answered.
37
+ * `wait=false` (for `tapflow agent start`): fire-and-forget so agent startup isn't blocked — if the
38
+ * grant exists the helper exits silently, otherwise the modal pops for the operator to allow.
39
+ */
40
+ export declare function requestAudioPermission(wait?: boolean): void;
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4CA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,CA6BxC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAGrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAGvE;AAGD,wBAAgB,gBAAgB,IAAI,OAAO,CAG1C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,UAAO,GAAG,IAAI,CASxD"}
package/dist/index.js ADDED
@@ -0,0 +1,131 @@
1
+ // Shared macOS Core Audio process-tap helper utilities.
2
+ //
3
+ // The helper itself (audiotap-helper.swift) taps host processes by PID. tapflow uses it two ways:
4
+ // - iOS: capture a simulator's audio and stream it to the browser (launchAudioHelper)
5
+ // - Android: mute the emulator (qemu) process's host output while gRPC does the capture (launchMuteOnlyTap)
6
+ //
7
+ // Both platforms share the same signed .app so they share one audio-capture TCC grant (keyed on the
8
+ // app's cdhash). This package owns the bundle build, launch, and permission-priming; the per-platform
9
+ // capture/stream logic lives in ios-agent / android-agent.
10
+ import os from 'node:os';
11
+ import { execFileSync } from 'node:child_process';
12
+ import { existsSync, statSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { createLogger, PlatformError } from '@tapflowio/agent-core';
15
+ const logger = createLogger('audiotap-helper');
16
+ const HELPER_SRC = join(import.meta.dirname, '..', 'src', 'audiotap-helper.swift');
17
+ const HELPER_APP = join(import.meta.dirname, '..', 'bin', 'audiotap-helper.app');
18
+ const HELPER_BIN = join(HELPER_APP, 'Contents', 'MacOS', 'audiotap-helper');
19
+ // LSUIElement → no Dock icon (background agent). NSAudioCaptureUsageDescription → the one-time TCC
20
+ // "audio recording" prompt. LSMinimumSystemVersion 14.2 → Core Audio process taps.
21
+ const INFO_PLIST = `<?xml version="1.0" encoding="UTF-8"?>
22
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
23
+ <plist version="1.0"><dict>
24
+ <key>CFBundleExecutable</key><string>audiotap-helper</string>
25
+ <key>CFBundleIdentifier</key><string>io.tapflow.audiotap-helper</string>
26
+ <key>CFBundleName</key><string>audiotap-helper</string>
27
+ <key>CFBundlePackageType</key><string>APPL</string>
28
+ <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
29
+ <key>LSUIElement</key><true/>
30
+ <key>LSMinimumSystemVersion</key><string>14.2</string>
31
+ <key>NSAudioCaptureUsageDescription</key><string>tapflow taps the simulator/emulator audio to stream it to the browser.</string>
32
+ <key>NSMicrophoneUsageDescription</key><string>tapflow taps the simulator/emulator audio to stream it to the browser.</string>
33
+ </dict></plist>
34
+ `;
35
+ const ENTITLEMENTS = `<?xml version="1.0" encoding="UTF-8"?>
36
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
37
+ <plist version="1.0"><dict>
38
+ <key>com.apple.security.device.audio-input</key><true/>
39
+ </dict></plist>
40
+ `;
41
+ /**
42
+ * Build (mtime-gated) the audiotap-helper as a signed `.app` and return its path.
43
+ *
44
+ * WHY a `.app`, not a bare binary like the other helpers: a Core Audio process tap returns silence
45
+ * unless the *responsible process* holds the audio-recording TCC grant. A CLI child the agent spawns
46
+ * inherits the agent's (ungranted) responsibility. Launched via `open` (LaunchServices), the helper
47
+ * is its own responsible process with its own one-time grant — so it must be a bundle. Steady state
48
+ * (unchanged source) skips the rebuild/re-sign, so the grant persists (TCC keys on the cdhash).
49
+ */
50
+ export function ensureHelperApp() {
51
+ if (existsSync(HELPER_BIN)) {
52
+ if (!existsSync(HELPER_SRC))
53
+ return HELPER_APP; // source not shipped — trust the prebuilt bundle
54
+ if (statSync(HELPER_BIN).mtimeMs >= statSync(HELPER_SRC).mtimeMs)
55
+ return HELPER_APP;
56
+ logger.info('audiotap-helper source changed, rebuilding...');
57
+ }
58
+ if (!existsSync(HELPER_SRC)) {
59
+ throw new PlatformError('audiotap-helper.app missing and source not found — reinstall @tapflowio/audiotap-helper');
60
+ }
61
+ logger.info('building audiotap-helper.app...');
62
+ // Build + sign as a unit. If any step fails (e.g. swiftc OK but codesign fails), remove the bundle
63
+ // so a later call doesn't return a half-built/unsigned .app from the mtime gate above (an unsigned
64
+ // bundle silently fails the TCC grant). Next call rebuilds from scratch.
65
+ try {
66
+ mkdirSync(join(HELPER_APP, 'Contents', 'MacOS'), { recursive: true });
67
+ execFileSync('swiftc', [
68
+ HELPER_SRC, '-o', HELPER_BIN, '-framework', 'CoreAudio', '-framework', 'AudioToolbox',
69
+ ], { stdio: ['ignore', 'ignore', 'inherit'] });
70
+ writeFileSync(join(HELPER_APP, 'Contents', 'Info.plist'), INFO_PLIST);
71
+ const entPath = join(import.meta.dirname, '..', 'bin', '.audiotap.entitlements');
72
+ writeFileSync(entPath, ENTITLEMENTS);
73
+ execFileSync('codesign', ['--force', '--sign', '-', '--entitlements', entPath, HELPER_APP], { stdio: ['ignore', 'ignore', 'inherit'] });
74
+ }
75
+ catch (e) {
76
+ rmSync(HELPER_APP, { recursive: true, force: true });
77
+ throw e;
78
+ }
79
+ logger.info('built audiotap-helper.app');
80
+ return HELPER_APP;
81
+ }
82
+ /**
83
+ * Launch the helper bundle via LaunchServices, pointed at the agent's loopback port and the simulator
84
+ * process PID(s) to tap (iOS capture). `-g` keeps it backgrounded (no focus steal). `-n` forces a NEW
85
+ * instance per simulator — without it `open -a` reuses the first sim's running helper, so a second
86
+ * concurrent sim never gets its own tap and its audio silently breaks. LaunchServices makes each its
87
+ * own TCC-responsible process; it connects back to `port` and streams PCM.
88
+ */
89
+ export function launchAudioHelper(appPath, port, pids) {
90
+ execFileSync('open', ['-g', '-n', '-a', appPath, '--args', String(port), ...pids.map(String)], { stdio: ['ignore', 'ignore', 'ignore'] });
91
+ }
92
+ /**
93
+ * Launch the helper in mute-only mode (Android host-mute symmetry, #341): it holds a `.muted` process
94
+ * tap on `pids` (the emulator/qemu process) to silence their host output, but captures/streams nothing
95
+ * — Android captures audio via gRPC. No port/socket. The helper self-exits once every target pid is
96
+ * gone (emulator stopped). `-n` forces a fresh instance so it never collides with the iOS capture helper.
97
+ */
98
+ export function launchMuteOnlyTap(appPath, pids) {
99
+ execFileSync('open', ['-g', '-n', '-a', appPath, '--args', '--mute-only', ...pids.map(String)], { stdio: ['ignore', 'ignore', 'ignore'] });
100
+ }
101
+ // Core Audio process taps need macOS 14.2+ (Darwin 23.2+). os.release() → "<darwinMajor>.<minor>.…".
102
+ export function isAudioSupported() {
103
+ const [maj, min] = os.release().split('.').map(Number);
104
+ return maj > 23 || (maj === 23 && min >= 2);
105
+ }
106
+ /**
107
+ * Prime the audio-capture TCC grant up front (from `tapflow setup ios` / `tapflow agent start`) so the
108
+ * operator approves it while present — not at first simulator boot, which a headless operator would miss.
109
+ *
110
+ * Runs the helper's --request-permission mode: a global tap whose *capture start* raises the same
111
+ * audio-capture prompt a per-pid tap needs (the grant keys on the app cdhash + service, not the tap
112
+ * shape), so no port/pid/booted simulator is required. The grant isn't readable back, so the caller
113
+ * treats a clean return as "prompt shown / answered" and leaves approve-vs-deny to the operator.
114
+ *
115
+ * `wait` (default true, for `tapflow setup ios`): `open -W` blocks until the modal is answered.
116
+ * `wait=false` (for `tapflow agent start`): fire-and-forget so agent startup isn't blocked — if the
117
+ * grant exists the helper exits silently, otherwise the modal pops for the operator to allow.
118
+ */
119
+ export function requestAudioPermission(wait = true) {
120
+ try {
121
+ const app = ensureHelperApp();
122
+ const flags = wait ? ['-W', '-n'] : ['-g', '-n'];
123
+ execFileSync('open', [...flags, '-a', app, '--args', '--request-permission'], { stdio: ['ignore', 'ignore', 'ignore'] });
124
+ }
125
+ catch (e) {
126
+ if (wait)
127
+ throw e; // setup wants to surface it; agent-start priming is best-effort (never block startup)
128
+ logger.warn(`audio permission priming skipped: ${e instanceof Error ? e.message : String(e)}`);
129
+ }
130
+ }
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tapflowio/audiotap-helper",
3
+ "version": "0.1.0",
4
+ "description": "Shared macOS Core Audio process-tap helper for tapflow — iOS simulator audio capture and Android emulator host-mute",
5
+ "keywords": [
6
+ "tapflow",
7
+ "macos",
8
+ "core-audio",
9
+ "process-tap",
10
+ "audio",
11
+ "qa"
12
+ ],
13
+ "homepage": "https://github.com/jo-duchan/tapflow",
14
+ "bugs": "https://github.com/jo-duchan/tapflow/issues",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/jo-duchan/tapflow.git",
18
+ "directory": "packages/audiotap-helper"
19
+ },
20
+ "license": "MIT",
21
+ "author": "tapflow contributors",
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "source": "./src/index.ts",
29
+ "tsx": "./src/index.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "bin",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20.12.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "postinstall": "node -e \"if (process.platform === 'darwin') { try { require('fs').chmodSync('bin/audiotap-helper.app/Contents/MacOS/audiotap-helper', 0o755) } catch {} }\"",
48
+ "build": "tsc -b",
49
+ "typecheck": "tsc -b",
50
+ "test": "vitest run",
51
+ "lint": "eslint src"
52
+ },
53
+ "dependencies": {
54
+ "@tapflowio/agent-core": "workspace:*"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^20.19.43",
58
+ "typescript": "^5.0.0",
59
+ "vitest": "^4.1.9"
60
+ }
61
+ }