keyspy 1.0.3 → 1.0.5
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 +62 -71
- package/package.json +9 -8
- package/scripts/download-binaries.js +79 -79
package/README.md
CHANGED
@@ -1,19 +1,27 @@
|
|
1
|
-
#
|
1
|
+
# keyspy 🕵️
|
2
2
|
|
3
3
|
> A powerful, cross-platform keyboard and mouse event listener for Node.js
|
4
4
|
|
5
|
-
|
5
|
+
keyspy is a modern, lightweight library that provides global keyboard and mouse event monitoring across Windows, macOS, and Linux. Unlike other solutions, keyspy uses pre-compiled native binaries and a multi-process architecture for maximum stability and compatibility.
|
6
|
+
|
7
|
+
## 🚀 Improvements over Original
|
8
|
+
|
9
|
+
This project is a modernized version of [node-global-key-listener](https://github.com/LaunchMenu/node-global-key-listener) with significant enhancements:
|
10
|
+
|
11
|
+
- **🚀 Zero Setup**: Pre-compiled binaries downloaded automatically, no compilation required
|
12
|
+
- **📦 Smaller Package**: Binaries downloaded on-demand (not bundled), reducing package size by ~90%
|
13
|
+
- **📱 Universal macOS**: ARM64 + x86_64 universal binaries for all Apple Silicon and Intel Macs
|
14
|
+
- **🔄 Modern Stack**: TypeScript, automated testing, and modern development tools
|
15
|
+
- **🏗️ Automated Releases**: GitHub Actions handle cross-platform compilation and publishing
|
6
16
|
|
7
17
|
## ✨ Features
|
8
18
|
|
9
|
-
- 🌍 **Cross-platform**:
|
10
|
-
-
|
11
|
-
- 🔒 **System-level capture**: Can intercept system shortcuts like Ctrl+Alt+Delete, Cmd+Space
|
19
|
+
- 🌍 **Cross-platform**: Windows, macOS, and Linux (X11) support
|
20
|
+
- 🔒 **System-level capture**: Intercept any key combination, including OS shortcuts
|
12
21
|
- 🎯 **Event blocking**: Prevent captured events from reaching other applications
|
13
|
-
- 📦 **TypeScript ready**: Full
|
14
|
-
-
|
22
|
+
- 📦 **TypeScript ready**: Full type definitions included
|
23
|
+
- 🛡️ **Stable architecture**: Multi-process design prevents crashes
|
15
24
|
- ⚡ **High performance**: Optimized native implementations for each platform
|
16
|
-
- 📥 **Smart installation**: Automatically downloads platform-specific binaries from GitHub Releases
|
17
25
|
|
18
26
|
## 🔧 Platform Support
|
19
27
|
|
@@ -106,22 +114,16 @@ keyspy.kill(); // Removes all listeners and stops the key server
|
|
106
114
|
```bash
|
107
115
|
npm install keyspy
|
108
116
|
# or
|
109
|
-
pnpm add keyspy
|
110
|
-
# or
|
111
117
|
yarn add keyspy
|
112
118
|
```
|
113
119
|
|
114
|
-
|
115
|
-
1. 📦 Downloads the main package (TypeScript code)
|
116
|
-
2. 🔍 Detects your platform (Windows/macOS/Linux) and architecture
|
117
|
-
3. 📥 Automatically downloads the appropriate pre-compiled binary from GitHub Releases
|
118
|
-
4. ✅ Ready to use immediately!
|
120
|
+
The package automatically detects your platform and downloads the appropriate pre-compiled binary during installation. No compilation required!
|
119
121
|
|
120
|
-
## 🤔 Why
|
122
|
+
## 🤔 Why keyspy?
|
121
123
|
|
122
|
-
Choosing the right keyboard listener for your Node.js project can be challenging. Here's how
|
124
|
+
Choosing the right keyboard listener for your Node.js project can be challenging. Here's how keyspy compares to other popular solutions:
|
123
125
|
|
124
|
-
| Feature | Electron globalShortcut | IOHook | **
|
126
|
+
| Feature | Electron globalShortcut | IOHook | **keyspy** |
|
125
127
|
|---------|------------------------|--------|------------|
|
126
128
|
| **Setup Complexity** | Simple | Complex (node-gyp) | **Simple** |
|
127
129
|
| **System Shortcuts** | ❌ Limited | ✅ Full | **✅ Full** |
|
@@ -131,7 +133,7 @@ Choosing the right keyboard listener for your Node.js project can be challenging
|
|
131
133
|
| **Arbitrary Key Support** | ❌ Limited | ⚠️ Limited | **✅ Full** |
|
132
134
|
| **Process Architecture** | In-process | In-process | **Multi-process** |
|
133
135
|
|
134
|
-
### 🎯 **
|
136
|
+
### 🎯 **keyspy Advantages**
|
135
137
|
|
136
138
|
- **🔧 Zero Setup**: Pre-compiled binaries work out of the box
|
137
139
|
- **🌐 Universal**: Compatible with all Node.js versions (14+)
|
@@ -148,47 +150,60 @@ Choosing the right keyboard listener for your Node.js project can be challenging
|
|
148
150
|
|
149
151
|
## 🛠️ Development
|
150
152
|
|
151
|
-
### Quick Development Setup
|
152
|
-
|
153
153
|
```bash
|
154
154
|
# Clone and setup
|
155
155
|
git clone https://github.com/teomyth/keyspy.git
|
156
156
|
cd keyspy
|
157
|
-
|
157
|
+
npm install
|
158
158
|
|
159
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
159
|
+
# Build native binary for development
|
160
|
+
npm run build:native
|
161
|
+
|
162
|
+
# Development commands
|
163
|
+
npm run dev # TypeScript watch mode
|
164
|
+
npm run build # Production build
|
165
|
+
npm test # Run all tests
|
166
|
+
npm run test:manual # Interactive testing
|
164
167
|
```
|
165
168
|
|
166
169
|
### Building Native Binaries
|
167
170
|
|
168
|
-
|
171
|
+
For development, you can rebuild the native binaries:
|
169
172
|
|
170
173
|
```bash
|
171
|
-
#
|
172
|
-
|
173
|
-
|
174
|
-
|
174
|
+
# Auto-detect your platform
|
175
|
+
npm run build:native
|
176
|
+
|
177
|
+
# Force specific platform
|
178
|
+
npm run build:native -- --platform darwin # macOS (requires Xcode)
|
179
|
+
npm run build:native -- --platform linux # Linux (requires X11 dev libraries)
|
180
|
+
npm run build:native -- --platform win32 # Windows (requires MinGW)
|
181
|
+
|
182
|
+
# Show detailed build output
|
183
|
+
npm run build:native -- --verbose
|
175
184
|
```
|
176
185
|
|
177
186
|
### Code Quality
|
178
187
|
|
179
188
|
```bash
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
189
|
+
npm run lint # Check code quality with Biome
|
190
|
+
npm run lint:fix # Auto-fix issues
|
191
|
+
npm run format # Format code
|
192
|
+
npm run clean # Remove build artifacts
|
193
|
+
|
194
|
+
# Release management
|
195
|
+
npm run release:patch # Bump patch version (1.0.0 → 1.0.1)
|
196
|
+
npm run release:minor # Bump minor version (1.0.0 → 1.1.0)
|
197
|
+
npm run release:major # Bump major version (1.0.0 → 2.0.0)
|
198
|
+
npm run release:dry # Preview release without publishing
|
184
199
|
```
|
185
200
|
|
186
201
|
### Testing
|
187
202
|
|
188
203
|
```bash
|
189
|
-
|
190
|
-
|
191
|
-
|
204
|
+
npm run test:unit # Unit tests
|
205
|
+
npm run test:integration # Integration tests
|
206
|
+
npm run test:manual # Manual interactive testing
|
192
207
|
```
|
193
208
|
|
194
209
|
## 📋 API Reference
|
@@ -236,36 +251,17 @@ interface IGlobalKeyEvent {
|
|
236
251
|
|
237
252
|
## 🔧 Troubleshooting
|
238
253
|
|
239
|
-
###
|
240
|
-
|
241
|
-
If the automatic binary download fails during installation:
|
242
|
-
|
243
|
-
```bash
|
244
|
-
# Option 1: Manual download from GitHub Releases
|
245
|
-
# Visit: https://github.com/teomyth/keyspy/releases
|
246
|
-
# Download the appropriate file for your platform
|
247
|
-
|
248
|
-
# Option 2: Build from source
|
249
|
-
npm run build:swift # macOS
|
250
|
-
npm run build:x11 # Linux
|
251
|
-
npm run build:win # Windows
|
252
|
-
|
253
|
-
# Option 3: Skip download in CI environments
|
254
|
-
export KEYSPY_SKIP_DOWNLOAD=true
|
255
|
-
npm install keyspy
|
256
|
-
```
|
257
|
-
|
258
|
-
### Platform Support
|
254
|
+
### Common Issues
|
259
255
|
|
260
|
-
|
261
|
-
|
262
|
-
|
256
|
+
1. **Binary download fails**: Visit [GitHub Releases](https://github.com/teomyth/keyspy/releases) to download manually, or build from source with `npm run build:native`
|
257
|
+
2. **Permission denied**: Make sure the binary has execute permissions (`chmod +x bin/*`)
|
258
|
+
3. **CI environments**: Set `KEYSPY_SKIP_DOWNLOAD=true` to skip binary download
|
263
259
|
|
264
|
-
###
|
260
|
+
### Platform Requirements
|
265
261
|
|
266
|
-
|
267
|
-
|
268
|
-
|
262
|
+
- **macOS**: macOS 10.14+, supports both Intel and Apple Silicon
|
263
|
+
- **Linux**: X11 display server, x64 architecture
|
264
|
+
- **Windows**: Windows 10+, x64 architecture
|
269
265
|
|
270
266
|
## 🤝 Contributing
|
271
267
|
|
@@ -280,8 +276,3 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
280
276
|
## 📄 License
|
281
277
|
|
282
278
|
MIT License - see [LICENSE](LICENSE) file for details.
|
283
|
-
|
284
|
-
## 🙏 Acknowledgments
|
285
|
-
|
286
|
-
- Original concept inspired by [LaunchMenu](https://github.com/LaunchMenu/LaunchMenu)
|
287
|
-
- Built with modern tooling: TypeScript, Biome, Jest, and Turbo
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "keyspy",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.5",
|
4
4
|
"description": "A cross-platform global keyboard and mouse listener for Node.js",
|
5
5
|
"types": "build/index.d.ts",
|
6
6
|
"main": "build/index.js",
|
@@ -13,28 +13,29 @@
|
|
13
13
|
"engines": {
|
14
14
|
"node": ">=14.0.0"
|
15
15
|
},
|
16
|
-
"packageManager": "pnpm@8.0.0",
|
17
16
|
"scripts": {
|
18
17
|
"dev": "tsc --watch",
|
19
18
|
"build": "tsc",
|
20
19
|
"postinstall": "node scripts/download-binaries.js",
|
21
|
-
"build:
|
22
|
-
"build:mac": "mkdir -p bin && swiftc src/bin/MacKeyServer/main.swift -o bin/MacKeyServer",
|
23
|
-
"build:win": ".\\scripts\\compile-win.bat",
|
24
|
-
"build:x11": "scripts/compile-x11.sh",
|
20
|
+
"build:native": "node scripts/build-native.js",
|
25
21
|
"test": "jest",
|
26
22
|
"test:unit": "jest tests/unit",
|
27
23
|
"test:integration": "jest tests/integration",
|
24
|
+
"test:coverage": "jest --coverage",
|
25
|
+
"test:watch": "jest --watch",
|
28
26
|
"test:manual": "npx ts-node examples/manual-test.ts",
|
29
27
|
"lint": "biome lint .",
|
30
28
|
"lint:fix": "biome lint --write .",
|
31
29
|
"format": "biome format --write .",
|
32
30
|
"format:check": "biome format .",
|
33
|
-
"clean": "rm -rf build dist coverage test-
|
31
|
+
"clean": "rm -rf build dist coverage test-results .turbo",
|
34
32
|
"prepare": "npm run build",
|
35
33
|
"prepublishOnly": "npm run build && npm run test",
|
36
34
|
"release": "release-it",
|
37
|
-
"release:dry": "release-it --dry-run"
|
35
|
+
"release:dry": "release-it --dry-run",
|
36
|
+
"release:patch": "release-it patch",
|
37
|
+
"release:minor": "release-it minor",
|
38
|
+
"release:major": "release-it major"
|
38
39
|
},
|
39
40
|
"repository": {
|
40
41
|
"type": "git",
|
@@ -1,11 +1,11 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
2
|
|
3
|
-
const fs = require(
|
4
|
-
const path = require(
|
5
|
-
const https = require(
|
6
|
-
const { execSync } = require(
|
3
|
+
const fs = require("node:fs");
|
4
|
+
const path = require("node:path");
|
5
|
+
const https = require("node:https");
|
6
|
+
const { execSync } = require("node:child_process");
|
7
7
|
|
8
|
-
const packageJson = require(
|
8
|
+
const packageJson = require("../package.json");
|
9
9
|
const version = packageJson.version;
|
10
10
|
|
11
11
|
// Platform detection
|
@@ -14,16 +14,16 @@ const arch = process.arch;
|
|
14
14
|
|
15
15
|
// Binary mapping
|
16
16
|
const binaryMap = {
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
darwin: {
|
18
|
+
arm64: { file: "MacKeyServer", archive: "keyspy-darwin-arm64.tar.gz" },
|
19
|
+
x64: { file: "MacKeyServer", archive: "keyspy-darwin-x64.tar.gz" },
|
20
20
|
},
|
21
|
-
|
22
|
-
|
21
|
+
linux: {
|
22
|
+
x64: { file: "X11KeyServer", archive: "keyspy-linux-x64.tar.gz" },
|
23
|
+
},
|
24
|
+
win32: {
|
25
|
+
x64: { file: "WinKeyServer.exe", archive: "keyspy-win32-x64.tar.gz" },
|
23
26
|
},
|
24
|
-
'win32': {
|
25
|
-
'x64': { file: 'WinKeyServer.exe', archive: 'keyspy-win32-x64.tar.gz' }
|
26
|
-
}
|
27
27
|
};
|
28
28
|
|
29
29
|
function log(message) {
|
@@ -39,56 +39,58 @@ function getPlatformInfo() {
|
|
39
39
|
if (!platformInfo) {
|
40
40
|
throw new Error(`Unsupported platform: ${platform}`);
|
41
41
|
}
|
42
|
-
|
42
|
+
|
43
43
|
const archInfo = platformInfo[arch];
|
44
44
|
if (!archInfo) {
|
45
45
|
throw new Error(`Unsupported architecture: ${arch} on ${platform}`);
|
46
46
|
}
|
47
|
-
|
47
|
+
|
48
48
|
return archInfo;
|
49
49
|
}
|
50
50
|
|
51
51
|
function downloadFile(url, dest) {
|
52
52
|
return new Promise((resolve, reject) => {
|
53
53
|
log(`Downloading ${url}`);
|
54
|
-
|
54
|
+
|
55
55
|
const file = fs.createWriteStream(dest);
|
56
|
-
|
57
|
-
https
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
file.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
56
|
+
|
57
|
+
https
|
58
|
+
.get(url, (response) => {
|
59
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
60
|
+
// Handle redirect
|
61
|
+
return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
62
|
+
}
|
63
|
+
|
64
|
+
if (response.statusCode !== 200) {
|
65
|
+
reject(new Error(`Download failed with status ${response.statusCode}`));
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
|
69
|
+
response.pipe(file);
|
70
|
+
|
71
|
+
file.on("finish", () => {
|
72
|
+
file.close();
|
73
|
+
resolve();
|
74
|
+
});
|
75
|
+
|
76
|
+
file.on("error", (err) => {
|
77
|
+
fs.unlink(dest, () => {
|
78
|
+
// Ignore unlink errors - file cleanup is best effort
|
79
|
+
}); // Delete partial file
|
80
|
+
reject(err);
|
81
|
+
});
|
82
|
+
})
|
83
|
+
.on("error", reject);
|
82
84
|
});
|
83
85
|
}
|
84
86
|
|
85
87
|
function extractTarGz(archivePath, extractDir) {
|
86
88
|
try {
|
87
89
|
// Try using tar command
|
88
|
-
execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio:
|
90
|
+
execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio: "inherit" });
|
89
91
|
return true;
|
90
92
|
} catch (_err) {
|
91
|
-
log(
|
93
|
+
log("tar command failed, trying alternative extraction...");
|
92
94
|
return false;
|
93
95
|
}
|
94
96
|
}
|
@@ -96,39 +98,39 @@ function extractTarGz(archivePath, extractDir) {
|
|
96
98
|
async function downloadAndExtract() {
|
97
99
|
try {
|
98
100
|
const { file: binaryFile, archive } = getPlatformInfo();
|
99
|
-
|
101
|
+
|
100
102
|
// Create bin directory
|
101
|
-
const binDir = path.join(__dirname,
|
103
|
+
const binDir = path.join(__dirname, "..", "bin");
|
102
104
|
if (!fs.existsSync(binDir)) {
|
103
105
|
fs.mkdirSync(binDir, { recursive: true });
|
104
106
|
}
|
105
|
-
|
107
|
+
|
106
108
|
const binaryPath = path.join(binDir, binaryFile);
|
107
|
-
|
109
|
+
|
108
110
|
// Check if binary already exists
|
109
111
|
if (fs.existsSync(binaryPath)) {
|
110
112
|
log(`Binary already exists: ${binaryPath}`);
|
111
113
|
return;
|
112
114
|
}
|
113
|
-
|
115
|
+
|
114
116
|
// Download URL
|
115
117
|
const downloadUrl = `https://github.com/teomyth/keyspy/releases/download/v${version}/${archive}`;
|
116
118
|
const archivePath = path.join(binDir, archive);
|
117
|
-
|
119
|
+
|
118
120
|
log(`Platform: ${platform}-${arch}`);
|
119
121
|
log(`Binary: ${binaryFile}`);
|
120
|
-
|
122
|
+
|
121
123
|
try {
|
122
124
|
// Download archive
|
123
125
|
await downloadFile(downloadUrl, archivePath);
|
124
126
|
log(`Downloaded: ${archive}`);
|
125
|
-
|
127
|
+
|
126
128
|
// Extract archive
|
127
129
|
if (extractTarGz(archivePath, binDir)) {
|
128
130
|
log(`Extracted: ${archive}`);
|
129
|
-
|
131
|
+
|
130
132
|
// Make binary executable (Unix systems)
|
131
|
-
if (platform !==
|
133
|
+
if (platform !== "win32") {
|
132
134
|
try {
|
133
135
|
fs.chmodSync(binaryPath, 0o755);
|
134
136
|
log(`Made executable: ${binaryFile}`);
|
@@ -136,43 +138,41 @@ async function downloadAndExtract() {
|
|
136
138
|
log(`Warning: Could not make binary executable: ${err.message}`);
|
137
139
|
}
|
138
140
|
}
|
139
|
-
|
141
|
+
|
140
142
|
// Clean up archive
|
141
143
|
fs.unlinkSync(archivePath);
|
142
144
|
log(`Cleaned up: ${archive}`);
|
143
|
-
|
145
|
+
|
144
146
|
log(`✅ Successfully installed binary: ${binaryFile}`);
|
145
147
|
} else {
|
146
|
-
throw new Error(
|
148
|
+
throw new Error("Failed to extract archive");
|
147
149
|
}
|
148
|
-
|
149
150
|
} catch (downloadErr) {
|
150
151
|
error(`Failed to download binary: ${downloadErr.message}`);
|
151
|
-
log(
|
152
|
-
log(
|
153
|
-
log(
|
154
|
-
log(
|
155
|
-
log(
|
156
|
-
log(
|
157
|
-
log(
|
158
|
-
log(
|
159
|
-
log(
|
160
|
-
|
152
|
+
log("");
|
153
|
+
log("📝 Manual installation options:");
|
154
|
+
log("1. Download from: https://github.com/teomyth/keyspy/releases");
|
155
|
+
log("2. Build from source:");
|
156
|
+
log(" - macOS: npm run build:swift");
|
157
|
+
log(" - Linux: npm run build:x11");
|
158
|
+
log(" - Windows: npm run build:win");
|
159
|
+
log("");
|
160
|
+
log("⚠️ keyspy will not work without the platform-specific binary.");
|
161
|
+
|
161
162
|
// Don't fail the installation, just warn
|
162
163
|
process.exit(0);
|
163
164
|
}
|
164
|
-
|
165
165
|
} catch (err) {
|
166
166
|
error(err.message);
|
167
|
-
log(
|
168
|
-
log(
|
169
|
-
log(
|
170
|
-
log(
|
171
|
-
log(
|
172
|
-
log(
|
173
|
-
log(
|
174
|
-
log(
|
175
|
-
|
167
|
+
log("");
|
168
|
+
log("📋 Supported platforms:");
|
169
|
+
log("- macOS (ARM64, x64)");
|
170
|
+
log("- Linux (x64)");
|
171
|
+
log("- Windows (x64)");
|
172
|
+
log("");
|
173
|
+
log("If your platform should be supported, please report this issue:");
|
174
|
+
log("https://github.com/teomyth/keyspy/issues");
|
175
|
+
|
176
176
|
// Don't fail the installation for unsupported platforms
|
177
177
|
process.exit(0);
|
178
178
|
}
|
@@ -180,7 +180,7 @@ async function downloadAndExtract() {
|
|
180
180
|
|
181
181
|
// Skip download in CI environments or if explicitly disabled
|
182
182
|
if (process.env.CI || process.env.KEYSPY_SKIP_DOWNLOAD) {
|
183
|
-
log(
|
183
|
+
log("Skipping binary download (CI environment or explicitly disabled)");
|
184
184
|
process.exit(0);
|
185
185
|
}
|
186
186
|
|