mirroir-mcp 0.18.0 → 0.20.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/README.md +1 -2
- package/bin/mirroir-mcp +3 -21
- package/install-cmd.js +9 -136
- package/install.js +3 -10
- package/package.json +1 -2
- package/bin/com.jfarcand.mirroir-helper.plist +0 -18
- package/setup.js +0 -237
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ This npm package downloads the pre-built macOS binary from [GitHub releases](htt
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
9
|
- macOS 15+ with iPhone Mirroring
|
|
10
|
-
- [Karabiner-Elements](https://karabiner-elements.pqrs.org/) installed and activated
|
|
11
10
|
- iPhone connected via iPhone Mirroring
|
|
12
11
|
|
|
13
12
|
## Install
|
|
@@ -28,7 +27,7 @@ Then add to your MCP client config:
|
|
|
28
27
|
}
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
See the [full documentation](https://github.com/jfarcand/mirroir-mcp) for
|
|
30
|
+
See the [full documentation](https://github.com/jfarcand/mirroir-mcp) for details.
|
|
32
31
|
|
|
33
32
|
## License
|
|
34
33
|
|
package/bin/mirroir-mcp
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ABOUTME: Thin wrapper that launches the native MCP server binary.
|
|
3
|
-
// ABOUTME: Routes "
|
|
3
|
+
// ABOUTME: Routes "install" subcommand to the interactive MCP client configuration script.
|
|
4
4
|
|
|
5
5
|
const { execFileSync } = require("child_process");
|
|
6
6
|
const fs = require("fs");
|
|
@@ -9,10 +9,9 @@ const path = require("path");
|
|
|
9
9
|
const binDir = __dirname;
|
|
10
10
|
const pkgDir = path.dirname(binDir);
|
|
11
11
|
const binary = path.join(binDir, "mirroir-mcp-native");
|
|
12
|
-
const setupScript = path.join(pkgDir, "setup.js");
|
|
13
12
|
const installScript = path.join(pkgDir, "install-cmd.js");
|
|
14
13
|
|
|
15
|
-
// Handle "install" subcommand —
|
|
14
|
+
// Handle "install" subcommand — interactive MCP client configuration
|
|
16
15
|
if (process.argv[2] === "install") {
|
|
17
16
|
if (!fs.existsSync(installScript)) {
|
|
18
17
|
console.error("Install script not found. Run: npm rebuild mirroir-mcp");
|
|
@@ -27,24 +26,7 @@ if (process.argv[2] === "install") {
|
|
|
27
26
|
process.exit(0);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
if (process.argv[2] === "setup") {
|
|
32
|
-
if (!fs.existsSync(setupScript)) {
|
|
33
|
-
console.error("Setup script not found. Run: npm rebuild mirroir-mcp");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
execFileSync("node", [setupScript], { stdio: "inherit" });
|
|
38
|
-
} catch (e) {
|
|
39
|
-
console.error("Setup failed. See https://github.com/jfarcand/mirroir-mcp for manual install.");
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Run the MCP server — starts regardless of helper daemon state.
|
|
46
|
-
// If the helper isn't running, tools return helpful error messages
|
|
47
|
-
// telling the user to run: npx mirroir-mcp setup
|
|
29
|
+
// Run the MCP server
|
|
48
30
|
try {
|
|
49
31
|
execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
50
32
|
} catch (e) {
|
package/install-cmd.js
CHANGED
|
@@ -1,100 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ABOUTME: Interactive one-command installer for mirroir-mcp.
|
|
3
|
-
// ABOUTME:
|
|
3
|
+
// ABOUTME: Builds from source if in a checkout, then configures the MCP client.
|
|
4
4
|
|
|
5
|
-
const { execSync
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const readline = require("readline");
|
|
9
9
|
|
|
10
|
-
const HELPER_SOCK = "/var/run/mirroir-helper.sock";
|
|
11
|
-
const KARABINER_APP = "/Applications/Karabiner-Elements.app";
|
|
12
|
-
const DRIVERKIT_MANAGER = "/Applications/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager";
|
|
13
|
-
const DRIVERKIT_VERSION = "6.10.0";
|
|
14
|
-
const DRIVERKIT_PKG = `Karabiner-DriverKit-VirtualHIDDevice-${DRIVERKIT_VERSION}.pkg`;
|
|
15
|
-
const DRIVERKIT_URL = `https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/releases/download/v${DRIVERKIT_VERSION}/${DRIVERKIT_PKG}`;
|
|
16
|
-
const KARABINER_SOCK_DIR = "/Library/Application Support/org.pqrs/tmp/rootonly/vhidd_server";
|
|
17
|
-
const SETUP_SCRIPT = path.join(__dirname, "setup.js");
|
|
18
10
|
const BIN_DIR = path.join(__dirname, "bin");
|
|
19
11
|
const REPO_ROOT = path.dirname(__dirname);
|
|
20
12
|
|
|
21
|
-
// --- DriverKit ---
|
|
22
|
-
|
|
23
|
-
function hasVhiddSocket() {
|
|
24
|
-
try {
|
|
25
|
-
const result = spawnSync("sudo", ["bash", "-c",
|
|
26
|
-
`ls '${KARABINER_SOCK_DIR}'/*.sock`
|
|
27
|
-
], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
28
|
-
return result.status === 0;
|
|
29
|
-
} catch (checkErr) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function ensureDriverKit() {
|
|
35
|
-
if (hasVhiddSocket()) {
|
|
36
|
-
const provider = fs.existsSync(KARABINER_APP) ? "Karabiner-Elements" : "standalone DriverKit";
|
|
37
|
-
console.log(`[1/3] DriverKit virtual HID is running (${provider}).`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (fs.existsSync(KARABINER_APP)) {
|
|
42
|
-
console.log("[1/3] Karabiner-Elements is installed but the DriverKit extension is not running.");
|
|
43
|
-
console.log(" Approve the extension in System Settings:");
|
|
44
|
-
console.log(" System Settings > General > Login Items & Extensions");
|
|
45
|
-
console.log(" Enable all toggles under Karabiner-Elements");
|
|
46
|
-
console.log("");
|
|
47
|
-
console.log("Press Enter once you have approved the extension...");
|
|
48
|
-
spawnSync("bash", ["-c", "read -r"], { stdio: "inherit" });
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (fs.existsSync(DRIVERKIT_MANAGER)) {
|
|
53
|
-
console.log("[1/3] Standalone DriverKit is installed but the extension is not running.");
|
|
54
|
-
console.log(" Activating...");
|
|
55
|
-
try {
|
|
56
|
-
execSync(`"${DRIVERKIT_MANAGER}" activate`, { stdio: "inherit" });
|
|
57
|
-
} catch (activateErr) {
|
|
58
|
-
// Activation may fail if already activated — continue
|
|
59
|
-
}
|
|
60
|
-
console.log(" Approve the extension in System Settings > General > Login Items & Extensions.");
|
|
61
|
-
console.log("");
|
|
62
|
-
console.log("Press Enter once you have approved the extension...");
|
|
63
|
-
spawnSync("bash", ["-c", "read -r"], { stdio: "inherit" });
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.log("[1/3] Installing standalone Karabiner DriverKit package...");
|
|
68
|
-
|
|
69
|
-
const tmpPkg = `/tmp/${DRIVERKIT_PKG}`;
|
|
70
|
-
try {
|
|
71
|
-
execSync(`curl -fSL -o "${tmpPkg}" "${DRIVERKIT_URL}"`, { stdio: "inherit" });
|
|
72
|
-
execSync(`sudo installer -pkg "${tmpPkg}" -target /`, { stdio: "inherit" });
|
|
73
|
-
execSync(`rm -f "${tmpPkg}"`, { stdio: "ignore" });
|
|
74
|
-
} catch (installErr) {
|
|
75
|
-
console.error("Failed to install DriverKit package.");
|
|
76
|
-
console.error(`Download manually from: https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/releases`);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
console.log("");
|
|
81
|
-
console.log("Activating DriverKit system extension...");
|
|
82
|
-
try {
|
|
83
|
-
execSync(`"${DRIVERKIT_MANAGER}" activate`, { stdio: "inherit" });
|
|
84
|
-
} catch (activateErr) {
|
|
85
|
-
// Continue — user will approve in System Settings
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log("");
|
|
89
|
-
console.log("Approve the system extension in System Settings:");
|
|
90
|
-
console.log(" System Settings > General > Login Items & Extensions");
|
|
91
|
-
console.log(" Enable the Karabiner-DriverKit-VirtualHIDDevice toggle");
|
|
92
|
-
console.log("");
|
|
93
|
-
console.log("Press Enter once you have approved the extension...");
|
|
94
|
-
|
|
95
|
-
spawnSync("bash", ["-c", "read -r"], { stdio: "inherit" });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
13
|
// --- Source Build ---
|
|
99
14
|
|
|
100
15
|
function isSourceCheckout() {
|
|
@@ -106,56 +21,10 @@ function buildFromSource() {
|
|
|
106
21
|
execSync("swift build -c release", { cwd: REPO_ROOT, stdio: "inherit" });
|
|
107
22
|
|
|
108
23
|
const releaseBin = path.join(REPO_ROOT, ".build", "release");
|
|
109
|
-
const plistSrc = path.join(REPO_ROOT, "Resources", "com.jfarcand.mirroir-helper.plist");
|
|
110
24
|
|
|
111
25
|
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
112
|
-
fs.copyFileSync(path.join(releaseBin, "mirroir-helper"), path.join(BIN_DIR, "mirroir-helper"));
|
|
113
|
-
fs.chmodSync(path.join(BIN_DIR, "mirroir-helper"), 0o755);
|
|
114
26
|
fs.copyFileSync(path.join(releaseBin, "mirroir-mcp"), path.join(BIN_DIR, "mirroir-mcp-native"));
|
|
115
27
|
fs.chmodSync(path.join(BIN_DIR, "mirroir-mcp-native"), 0o755);
|
|
116
|
-
fs.copyFileSync(plistSrc, path.join(BIN_DIR, "com.jfarcand.mirroir-helper.plist"));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- Helper Daemon ---
|
|
120
|
-
|
|
121
|
-
function isHelperRunning() {
|
|
122
|
-
if (!fs.existsSync(HELPER_SOCK)) return false;
|
|
123
|
-
try {
|
|
124
|
-
const result = spawnSync("bash", ["-c",
|
|
125
|
-
`echo '{"action":"status"}' | nc -U ${HELPER_SOCK} 2>/dev/null`
|
|
126
|
-
], { timeout: 3000 });
|
|
127
|
-
const output = result.stdout ? result.stdout.toString() : "";
|
|
128
|
-
return output.includes('"ok"');
|
|
129
|
-
} catch (socketErr) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function ensureHelper() {
|
|
135
|
-
if (isHelperRunning()) {
|
|
136
|
-
console.log("[2/3] Helper daemon is running.");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log("[2/3] Helper daemon not running. Setting up...");
|
|
141
|
-
|
|
142
|
-
// If helper binary is missing and we're in a source checkout, build first
|
|
143
|
-
const helperBin = path.join(BIN_DIR, "mirroir-helper");
|
|
144
|
-
if (!fs.existsSync(helperBin) && isSourceCheckout()) {
|
|
145
|
-
buildFromSource();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!fs.existsSync(SETUP_SCRIPT)) {
|
|
149
|
-
console.error("Setup script not found. Run: npm rebuild mirroir-mcp");
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
execFileSync("node", [SETUP_SCRIPT], { stdio: "inherit" });
|
|
155
|
-
} catch (setupErr) {
|
|
156
|
-
console.error("Helper setup failed. See https://github.com/jfarcand/mirroir-mcp for manual install.");
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
28
|
}
|
|
160
29
|
|
|
161
30
|
// --- MCP Client Configuration ---
|
|
@@ -174,7 +43,7 @@ function ask(question) {
|
|
|
174
43
|
}
|
|
175
44
|
|
|
176
45
|
async function configureMcpClient() {
|
|
177
|
-
console.log("[
|
|
46
|
+
console.log("[1/1] Configure MCP client");
|
|
178
47
|
console.log("");
|
|
179
48
|
console.log(" 1) Claude Code");
|
|
180
49
|
console.log(" 2) Cursor");
|
|
@@ -377,8 +246,12 @@ async function main() {
|
|
|
377
246
|
console.log("=== mirroir-mcp installer ===");
|
|
378
247
|
console.log("");
|
|
379
248
|
|
|
380
|
-
|
|
381
|
-
|
|
249
|
+
// Build from source if in a development checkout
|
|
250
|
+
const nativeBin = path.join(BIN_DIR, "mirroir-mcp-native");
|
|
251
|
+
if (!fs.existsSync(nativeBin) && isSourceCheckout()) {
|
|
252
|
+
buildFromSource();
|
|
253
|
+
}
|
|
254
|
+
|
|
382
255
|
await configureMcpClient();
|
|
383
256
|
|
|
384
257
|
console.log("");
|
package/install.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Downloads the pre-built mirroir-mcp
|
|
3
|
-
// plus the LaunchDaemon plist from GitHub releases.
|
|
2
|
+
// Downloads the pre-built mirroir-mcp binary from GitHub releases.
|
|
4
3
|
// Only supports macOS (darwin) since iPhone Mirroring is a macOS feature.
|
|
5
4
|
|
|
6
5
|
const https = require("https");
|
|
@@ -9,7 +8,7 @@ const fs = require("fs");
|
|
|
9
8
|
const path = require("path");
|
|
10
9
|
const { execSync } = require("child_process");
|
|
11
10
|
|
|
12
|
-
const VERSION = "0.
|
|
11
|
+
const VERSION = "0.20.0";
|
|
13
12
|
const REPO = "jfarcand/mirroir-mcp";
|
|
14
13
|
const BINARY = "mirroir-mcp";
|
|
15
14
|
|
|
@@ -48,7 +47,7 @@ function main() {
|
|
|
48
47
|
|
|
49
48
|
download(url, tmpFile, () => {
|
|
50
49
|
verifyChecksum(tarball, tmpFile, () => {
|
|
51
|
-
// Extract
|
|
50
|
+
// Extract mirroir-mcp binary
|
|
52
51
|
execSync(`tar xzf "${tmpFile}" -C "${binDir}"`, { stdio: "inherit" });
|
|
53
52
|
|
|
54
53
|
// Rename native binary to -native to avoid conflicting with the JS wrapper
|
|
@@ -62,12 +61,6 @@ function main() {
|
|
|
62
61
|
fs.unlinkSync(jsWrapperBackup);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// Make helper executable
|
|
66
|
-
const helperBin = path.join(binDir, "mirroir-helper");
|
|
67
|
-
if (fs.existsSync(helperBin)) {
|
|
68
|
-
fs.chmodSync(helperBin, 0o755);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
64
|
fs.unlinkSync(tmpFile);
|
|
72
65
|
console.log(`Installed binaries to ${binDir}`);
|
|
73
66
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mirroir-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "MCP server that controls a real iPhone through macOS iPhone Mirroring. Screenshot, tap, swipe, type — from any MCP client.",
|
|
5
5
|
"mcpName": "io.github.jfarcand/mirroir-mcp",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"bin/",
|
|
31
31
|
"install.js",
|
|
32
32
|
"install-cmd.js",
|
|
33
|
-
"setup.js",
|
|
34
33
|
"prompts/",
|
|
35
34
|
"agents/",
|
|
36
35
|
"README.md"
|
|
@@ -1,18 +0,0 @@
|
|
|
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>Label</key>
|
|
6
|
-
<string>com.jfarcand.mirroir-helper</string>
|
|
7
|
-
<key>ProgramArguments</key>
|
|
8
|
-
<array>
|
|
9
|
-
<string>/usr/local/bin/mirroir-helper</string>
|
|
10
|
-
</array>
|
|
11
|
-
<key>RunAtLoad</key>
|
|
12
|
-
<true/>
|
|
13
|
-
<key>KeepAlive</key>
|
|
14
|
-
<true/>
|
|
15
|
-
<key>StandardErrorPath</key>
|
|
16
|
-
<string>/var/log/mirroir-helper.log</string>
|
|
17
|
-
</dict>
|
|
18
|
-
</plist>
|
package/setup.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Sets up the mirroir-helper daemon as a macOS LaunchDaemon.
|
|
3
|
-
// Requires sudo for copying the helper binary and plist to system directories.
|
|
4
|
-
|
|
5
|
-
const { execSync, spawnSync } = require("child_process");
|
|
6
|
-
const fs = require("fs");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
|
|
9
|
-
const PLIST_NAME = "com.jfarcand.mirroir-helper";
|
|
10
|
-
const HELPER_SOCK = "/var/run/mirroir-helper.sock";
|
|
11
|
-
const HELPER_DEST = "/usr/local/bin/mirroir-helper";
|
|
12
|
-
const PLIST_DEST = `/Library/LaunchDaemons/${PLIST_NAME}.plist`;
|
|
13
|
-
const KARABINER_SOCK_DIR = "/Library/Application Support/org.pqrs/tmp/rootonly/vhidd_server";
|
|
14
|
-
const DRIVERKIT_MANAGER = "/Applications/.Karabiner-VirtualHIDDevice-Manager.app/Contents/MacOS/Karabiner-VirtualHIDDevice-Manager";
|
|
15
|
-
const DRIVERKIT_VERSION = "6.10.0";
|
|
16
|
-
const DRIVERKIT_URL = `https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/releases/download/v${DRIVERKIT_VERSION}/Karabiner-DriverKit-VirtualHIDDevice-${DRIVERKIT_VERSION}.pkg`;
|
|
17
|
-
|
|
18
|
-
function main() {
|
|
19
|
-
const binDir = path.join(__dirname, "bin");
|
|
20
|
-
const helperSrc = path.join(binDir, "mirroir-helper");
|
|
21
|
-
const plistSrc = path.join(binDir, `${PLIST_NAME}.plist`);
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(helperSrc)) {
|
|
24
|
-
console.error("Helper binary not found. Run: npm rebuild mirroir-mcp");
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(plistSrc)) {
|
|
29
|
-
console.error("LaunchDaemon plist not found. Run: npm rebuild mirroir-mcp");
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Check if DriverKit virtual HID is available (works for both standalone and Karabiner-Elements)
|
|
34
|
-
if (!hasVhiddSocket()) {
|
|
35
|
-
if (fs.existsSync("/Applications/Karabiner-Elements.app")) {
|
|
36
|
-
console.log("");
|
|
37
|
-
console.log("Karabiner-Elements is installed but the DriverKit extension is not running.");
|
|
38
|
-
console.log("Approve the extension in System Settings:");
|
|
39
|
-
console.log(" System Settings > General > Login Items & Extensions");
|
|
40
|
-
console.log(" Enable all toggles under Karabiner-Elements");
|
|
41
|
-
console.log("");
|
|
42
|
-
console.log("After that, re-run setup.");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
} else if (fs.existsSync(DRIVERKIT_MANAGER)) {
|
|
45
|
-
console.log("");
|
|
46
|
-
console.log("Standalone DriverKit is installed but the extension is not running.");
|
|
47
|
-
console.log("Activate it:");
|
|
48
|
-
console.log(` ${DRIVERKIT_MANAGER} activate`);
|
|
49
|
-
console.log("");
|
|
50
|
-
console.log("Then approve in System Settings > General > Login Items & Extensions.");
|
|
51
|
-
console.log("After that, re-run setup.");
|
|
52
|
-
process.exit(1);
|
|
53
|
-
} else {
|
|
54
|
-
console.log("");
|
|
55
|
-
console.log("A DriverKit virtual HID device is required for tap and swipe input.");
|
|
56
|
-
console.log("");
|
|
57
|
-
console.log("Install the standalone Karabiner DriverKit package:");
|
|
58
|
-
console.log(` curl -fSL -o /tmp/driverkit.pkg "${DRIVERKIT_URL}"`);
|
|
59
|
-
console.log(" sudo installer -pkg /tmp/driverkit.pkg -target /");
|
|
60
|
-
console.log(` ${DRIVERKIT_MANAGER} activate`);
|
|
61
|
-
console.log("");
|
|
62
|
-
console.log("Then approve the extension in System Settings > General > Login Items & Extensions.");
|
|
63
|
-
console.log("After that, re-run setup.");
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
console.log("=== Setting up mirroir-helper daemon ===");
|
|
69
|
-
console.log("");
|
|
70
|
-
console.log("This requires administrator privileges to install the helper daemon.");
|
|
71
|
-
console.log("");
|
|
72
|
-
|
|
73
|
-
// Stop existing daemon if running
|
|
74
|
-
try {
|
|
75
|
-
execSync(`sudo launchctl bootout system/${PLIST_NAME} 2>/dev/null`, { stdio: "inherit" });
|
|
76
|
-
spawnSync("sleep", ["1"]);
|
|
77
|
-
} catch (ignoreNotRunning) {
|
|
78
|
-
// Daemon wasn't loaded, safe to continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Copy helper binary
|
|
82
|
-
execSync(`sudo cp "${helperSrc}" "${HELPER_DEST}"`, { stdio: "inherit" });
|
|
83
|
-
execSync(`sudo chmod 755 "${HELPER_DEST}"`, { stdio: "inherit" });
|
|
84
|
-
|
|
85
|
-
// Copy and configure plist
|
|
86
|
-
execSync(`sudo cp "${plistSrc}" "${PLIST_DEST}"`, { stdio: "inherit" });
|
|
87
|
-
execSync(`sudo chown root:wheel "${PLIST_DEST}"`, { stdio: "inherit" });
|
|
88
|
-
execSync(`sudo chmod 644 "${PLIST_DEST}"`, { stdio: "inherit" });
|
|
89
|
-
|
|
90
|
-
// Start daemon
|
|
91
|
-
execSync(`sudo launchctl bootstrap system "${PLIST_DEST}"`, { stdio: "inherit" });
|
|
92
|
-
|
|
93
|
-
// Configure Karabiner ignore rule only when Karabiner-Elements is installed
|
|
94
|
-
// (the ignore rule prevents the grabber from intercepting our virtual keyboard;
|
|
95
|
-
// standalone DriverKit has no grabber)
|
|
96
|
-
if (fs.existsSync("/Applications/Karabiner-Elements.app")) {
|
|
97
|
-
configureKarabiner();
|
|
98
|
-
} else {
|
|
99
|
-
console.log("Using standalone DriverKit (no Karabiner grabber, ignore rule not needed).");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Wait for helper to become ready
|
|
103
|
-
console.log("");
|
|
104
|
-
console.log("Waiting for helper daemon...");
|
|
105
|
-
let ready = false;
|
|
106
|
-
for (let i = 0; i < 15; i++) {
|
|
107
|
-
spawnSync("sleep", ["2"]);
|
|
108
|
-
if (checkHelper()) {
|
|
109
|
-
ready = true;
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
console.log("");
|
|
115
|
-
if (ready) {
|
|
116
|
-
console.log("=== Helper daemon is running ===");
|
|
117
|
-
} else {
|
|
118
|
-
console.log("Helper daemon started but may need more time to initialize.");
|
|
119
|
-
console.log("Check: echo '{\"action\":\"status\"}' | nc -U " + HELPER_SOCK);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Install prompts and agent profiles to global config dir
|
|
123
|
-
installPromptsAndAgents();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function installPromptsAndAgents() {
|
|
127
|
-
const globalConfigDir = path.join(process.env.HOME || "", ".mirroir-mcp");
|
|
128
|
-
const promptsDir = path.join(globalConfigDir, "prompts");
|
|
129
|
-
const agentsDir = path.join(globalConfigDir, "agents");
|
|
130
|
-
|
|
131
|
-
fs.mkdirSync(promptsDir, { recursive: true });
|
|
132
|
-
fs.mkdirSync(agentsDir, { recursive: true });
|
|
133
|
-
|
|
134
|
-
// Source directories are in the npm package alongside setup.js
|
|
135
|
-
const srcPromptsDir = path.join(__dirname, "prompts");
|
|
136
|
-
const srcAgentsDir = path.join(__dirname, "agents");
|
|
137
|
-
|
|
138
|
-
// Copy prompts (skip if user has customized)
|
|
139
|
-
if (fs.existsSync(srcPromptsDir)) {
|
|
140
|
-
for (const file of fs.readdirSync(srcPromptsDir)) {
|
|
141
|
-
if (!file.endsWith(".md")) continue;
|
|
142
|
-
const dest = path.join(promptsDir, file);
|
|
143
|
-
if (!fs.existsSync(dest)) {
|
|
144
|
-
fs.copyFileSync(path.join(srcPromptsDir, file), dest);
|
|
145
|
-
console.log(` Installed: ${dest}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Copy agent profiles (skip if user has customized)
|
|
151
|
-
if (fs.existsSync(srcAgentsDir)) {
|
|
152
|
-
for (const file of fs.readdirSync(srcAgentsDir)) {
|
|
153
|
-
if (!file.endsWith(".yaml")) continue;
|
|
154
|
-
const dest = path.join(agentsDir, file);
|
|
155
|
-
if (!fs.existsSync(dest)) {
|
|
156
|
-
fs.copyFileSync(path.join(srcAgentsDir, file), dest);
|
|
157
|
-
console.log(` Installed: ${dest}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function checkHelper() {
|
|
164
|
-
if (!fs.existsSync(HELPER_SOCK)) return false;
|
|
165
|
-
try {
|
|
166
|
-
const result = spawnSync("bash", ["-c",
|
|
167
|
-
`echo '{"action":"status"}' | nc -U ${HELPER_SOCK} 2>/dev/null`
|
|
168
|
-
], { timeout: 3000 });
|
|
169
|
-
const output = result.stdout ? result.stdout.toString() : "";
|
|
170
|
-
return output.includes('"ok"');
|
|
171
|
-
} catch (socketError) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function hasVhiddSocket() {
|
|
177
|
-
try {
|
|
178
|
-
// Socket dir is mode 700 — glob expansion needs sudo bash -c
|
|
179
|
-
const result = spawnSync("sudo", ["bash", "-c",
|
|
180
|
-
`ls '${KARABINER_SOCK_DIR}'/*.sock`
|
|
181
|
-
], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
182
|
-
return result.status === 0;
|
|
183
|
-
} catch (checkError) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function configureKarabiner() {
|
|
189
|
-
const configPath = path.join(
|
|
190
|
-
process.env.HOME || "",
|
|
191
|
-
".config/karabiner/karabiner.json"
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
if (!fs.existsSync(configPath)) {
|
|
195
|
-
// Create minimal config with ignore rule
|
|
196
|
-
const configDir = path.dirname(configPath);
|
|
197
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
198
|
-
const config = {
|
|
199
|
-
profiles: [{
|
|
200
|
-
devices: [{
|
|
201
|
-
identifiers: { is_keyboard: true, product_id: 592, vendor_id: 1452 },
|
|
202
|
-
ignore: true
|
|
203
|
-
}],
|
|
204
|
-
name: "Default profile",
|
|
205
|
-
selected: true,
|
|
206
|
-
virtual_hid_keyboard: { keyboard_type_v2: "ansi" }
|
|
207
|
-
}]
|
|
208
|
-
};
|
|
209
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
210
|
-
console.log("Created Karabiner config with device ignore rule.");
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Check if rule already exists
|
|
215
|
-
const content = fs.readFileSync(configPath, "utf8");
|
|
216
|
-
if (content.includes('"product_id": 592') || content.includes('"product_id":592')) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Add ignore rule to first profile
|
|
221
|
-
try {
|
|
222
|
-
const config = JSON.parse(content);
|
|
223
|
-
const profile = config.profiles[0];
|
|
224
|
-
if (!profile.devices) profile.devices = [];
|
|
225
|
-
profile.devices.push({
|
|
226
|
-
identifiers: { is_keyboard: true, product_id: 592, vendor_id: 1452 },
|
|
227
|
-
ignore: true
|
|
228
|
-
});
|
|
229
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
230
|
-
console.log("Added device ignore rule to Karabiner config.");
|
|
231
|
-
} catch (parseError) {
|
|
232
|
-
console.log("Warning: Could not update Karabiner config automatically.");
|
|
233
|
-
console.log("Add this device ignore rule manually: product_id 592, vendor_id 1452");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
main();
|