spoof-d 0.1.0 → 0.2.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/.spoofyrc.example +10 -0
- package/README.md +62 -0
- package/bin/cmd.js +196 -17
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -50,6 +50,12 @@ This repository ([TT5H/spoof-d](https://github.com/TT5H/spoof-d)) is a fork of [
|
|
|
50
50
|
- **Linux**: Modern ip link commands with ifconfig fallback
|
|
51
51
|
- **Unified API** across all platforms
|
|
52
52
|
|
|
53
|
+
### User Experience Features
|
|
54
|
+
- **Progress indicators** for long-running operations
|
|
55
|
+
- **Verbose mode** (`--verbose`) for detailed debugging output
|
|
56
|
+
- **JSON output** (`--json`) for scripting and automation
|
|
57
|
+
- **Configuration file** support (`.spoofyrc` in home directory)
|
|
58
|
+
|
|
53
59
|
## Installation
|
|
54
60
|
|
|
55
61
|
### From npm (recommended)
|
|
@@ -159,6 +165,62 @@ sudo spoofy reset wi-fi
|
|
|
159
165
|
|
|
160
166
|
**Note**: On macOS, restarting your computer will also reset your MAC address to the original hardware address.
|
|
161
167
|
|
|
168
|
+
## Advanced Usage
|
|
169
|
+
|
|
170
|
+
### Verbose Mode
|
|
171
|
+
|
|
172
|
+
Get detailed debugging information:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
spoofy list --verbose
|
|
176
|
+
spoofy randomize en0 --verbose
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### JSON Output
|
|
180
|
+
|
|
181
|
+
Output results in JSON format for scripting:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
spoofy list --json
|
|
185
|
+
spoofy randomize en0 --json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Example JSON output:
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"success": true,
|
|
192
|
+
"device": "en0",
|
|
193
|
+
"mac": "00:11:22:33:44:55",
|
|
194
|
+
"message": "MAC address changed successfully"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Configuration File
|
|
199
|
+
|
|
200
|
+
Create a configuration file at `~/.spoofyrc` (or `%USERPROFILE%\.spoofyrc` on Windows):
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"randomize": {
|
|
205
|
+
"local": true
|
|
206
|
+
},
|
|
207
|
+
"defaults": {
|
|
208
|
+
"verbose": false,
|
|
209
|
+
"json": false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The configuration file allows you to set default options that will be used automatically.
|
|
215
|
+
|
|
216
|
+
### Progress Indicators
|
|
217
|
+
|
|
218
|
+
Long-running operations show progress indicators:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
⏳ Changing MAC address... ✓ Successfully set MAC address
|
|
222
|
+
```
|
|
223
|
+
|
|
162
224
|
## Platform Support
|
|
163
225
|
|
|
164
226
|
### macOS ✅
|
package/bin/cmd.js
CHANGED
|
@@ -5,15 +5,73 @@ const minimist = require("minimist");
|
|
|
5
5
|
const spoof = require("../");
|
|
6
6
|
const { stripIndent } = require("common-tags");
|
|
7
7
|
const cp = require("child_process");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const os = require("os");
|
|
8
11
|
|
|
9
12
|
const argv = minimist(process.argv.slice(2), {
|
|
10
13
|
alias: {
|
|
11
14
|
v: "version",
|
|
15
|
+
V: "verbose",
|
|
16
|
+
j: "json",
|
|
12
17
|
},
|
|
13
|
-
boolean: ["version"],
|
|
18
|
+
boolean: ["version", "verbose", "json"],
|
|
14
19
|
});
|
|
15
20
|
const cmd = argv._[0];
|
|
16
21
|
|
|
22
|
+
// Global flags
|
|
23
|
+
const VERBOSE = argv.verbose || false;
|
|
24
|
+
const JSON_OUTPUT = argv.json || false;
|
|
25
|
+
|
|
26
|
+
// Configuration file support
|
|
27
|
+
let config = null;
|
|
28
|
+
const configPath = path.join(os.homedir(), ".spoofyrc");
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const configContent = fs.readFileSync(configPath, "utf8");
|
|
32
|
+
config = JSON.parse(configContent);
|
|
33
|
+
if (VERBOSE) {
|
|
34
|
+
logVerbose(`Loaded configuration from ${configPath}`);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (VERBOSE) {
|
|
38
|
+
logVerbose(`Failed to load config: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Helper functions
|
|
44
|
+
function logVerbose(message) {
|
|
45
|
+
if (VERBOSE && !JSON_OUTPUT) {
|
|
46
|
+
console.error(chalk.gray(`[VERBOSE] ${message}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function outputJSON(data) {
|
|
51
|
+
if (JSON_OUTPUT) {
|
|
52
|
+
console.log(JSON.stringify(data, null, 2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function showProgress(message) {
|
|
57
|
+
if (JSON_OUTPUT) return;
|
|
58
|
+
process.stdout.write(chalk.blue("⏳ ") + message + "... ");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hideProgress() {
|
|
62
|
+
if (JSON_OUTPUT) return;
|
|
63
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function progressStep(step, total, message) {
|
|
67
|
+
if (JSON_OUTPUT) return;
|
|
68
|
+
const percentage = Math.round((step / total) * 100);
|
|
69
|
+
process.stdout.write(`\r${chalk.blue("⏳")} [${step}/${total}] ${percentage}% - ${message}...`);
|
|
70
|
+
if (step === total) {
|
|
71
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
17
75
|
try {
|
|
18
76
|
init();
|
|
19
77
|
} catch (err) {
|
|
@@ -117,6 +175,8 @@ ${example}${note}
|
|
|
117
175
|
Options:
|
|
118
176
|
--wifi Try to only show wireless interfaces.
|
|
119
177
|
--local Set the locally administered flag on randomized MACs.
|
|
178
|
+
--verbose, -V Show verbose output for debugging.
|
|
179
|
+
--json, -j Output results in JSON format.
|
|
120
180
|
|
|
121
181
|
Platform Support:
|
|
122
182
|
✅ macOS (Sequoia 15.4+, Tahoe 26+)
|
|
@@ -127,7 +187,16 @@ ${example}${note}
|
|
|
127
187
|
}
|
|
128
188
|
|
|
129
189
|
function version() {
|
|
130
|
-
|
|
190
|
+
const pkg = require("../package.json");
|
|
191
|
+
if (JSON_OUTPUT) {
|
|
192
|
+
outputJSON({
|
|
193
|
+
version: pkg.version,
|
|
194
|
+
name: pkg.name,
|
|
195
|
+
description: pkg.description,
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
console.log(pkg.version);
|
|
199
|
+
}
|
|
131
200
|
}
|
|
132
201
|
|
|
133
202
|
function set(mac, devices) {
|
|
@@ -139,8 +208,14 @@ function set(mac, devices) {
|
|
|
139
208
|
throw new Error("Device name is required. Usage: spoofy set <mac> <device>");
|
|
140
209
|
}
|
|
141
210
|
|
|
142
|
-
devices.
|
|
211
|
+
logVerbose(`Setting MAC address ${mac} on ${devices.length} device(s)`);
|
|
212
|
+
|
|
213
|
+
devices.forEach((device, index) => {
|
|
214
|
+
logVerbose(`Processing device ${index + 1}/${devices.length}: ${device}`);
|
|
215
|
+
showProgress(`Finding interface ${device}`);
|
|
216
|
+
|
|
143
217
|
const it = spoof.findInterface(device);
|
|
218
|
+
hideProgress();
|
|
144
219
|
|
|
145
220
|
if (!it) {
|
|
146
221
|
throw new Error(
|
|
@@ -149,6 +224,7 @@ function set(mac, devices) {
|
|
|
149
224
|
);
|
|
150
225
|
}
|
|
151
226
|
|
|
227
|
+
logVerbose(`Found interface: ${it.device} (port: ${it.port})`);
|
|
152
228
|
setMACAddress(it.device, mac, it.port);
|
|
153
229
|
});
|
|
154
230
|
}
|
|
@@ -158,13 +234,34 @@ function normalize(mac) {
|
|
|
158
234
|
throw new Error("MAC address is required. Usage: spoofy normalize <mac>");
|
|
159
235
|
}
|
|
160
236
|
|
|
237
|
+
logVerbose(`Normalizing MAC address: ${mac}`);
|
|
238
|
+
|
|
161
239
|
try {
|
|
162
240
|
const normalized = spoof.normalize(mac);
|
|
163
241
|
if (!normalized) {
|
|
164
242
|
throw new Error(`"${mac}" is not a valid MAC address`);
|
|
165
243
|
}
|
|
166
|
-
|
|
244
|
+
|
|
245
|
+
if (JSON_OUTPUT) {
|
|
246
|
+
outputJSON({
|
|
247
|
+
original: mac,
|
|
248
|
+
normalized: normalized,
|
|
249
|
+
valid: true,
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
console.log(normalized);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
logVerbose(`Normalized: ${mac} -> ${normalized}`);
|
|
167
256
|
} catch (err) {
|
|
257
|
+
if (JSON_OUTPUT) {
|
|
258
|
+
outputJSON({
|
|
259
|
+
original: mac,
|
|
260
|
+
normalized: null,
|
|
261
|
+
valid: false,
|
|
262
|
+
error: err.message,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
168
265
|
throw new Error(
|
|
169
266
|
`Could not normalize MAC address "${mac}": ${err.message}`
|
|
170
267
|
);
|
|
@@ -176,8 +273,15 @@ function randomize(devices) {
|
|
|
176
273
|
throw new Error("Device name is required. Usage: spoofy randomize <device>");
|
|
177
274
|
}
|
|
178
275
|
|
|
179
|
-
|
|
276
|
+
const useLocal = argv.local || (config && config.randomize && config.randomize.local);
|
|
277
|
+
logVerbose(`Randomizing MAC address (local: ${useLocal})`);
|
|
278
|
+
|
|
279
|
+
devices.forEach((device, index) => {
|
|
280
|
+
logVerbose(`Processing device ${index + 1}/${devices.length}: ${device}`);
|
|
281
|
+
showProgress(`Finding interface ${device}`);
|
|
282
|
+
|
|
180
283
|
const it = spoof.findInterface(device);
|
|
284
|
+
hideProgress();
|
|
181
285
|
|
|
182
286
|
if (!it) {
|
|
183
287
|
throw new Error(
|
|
@@ -186,8 +290,15 @@ function randomize(devices) {
|
|
|
186
290
|
);
|
|
187
291
|
}
|
|
188
292
|
|
|
189
|
-
|
|
190
|
-
|
|
293
|
+
logVerbose(`Found interface: ${it.device} (port: ${it.port})`);
|
|
294
|
+
const mac = spoof.randomize(useLocal);
|
|
295
|
+
|
|
296
|
+
if (JSON_OUTPUT) {
|
|
297
|
+
// Will be output in setMACAddress
|
|
298
|
+
} else {
|
|
299
|
+
console.log(chalk.blue("ℹ"), `Generated random MAC address: ${chalk.bold.cyan(mac)}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
191
302
|
setMACAddress(it.device, mac, it.port);
|
|
192
303
|
});
|
|
193
304
|
}
|
|
@@ -197,8 +308,14 @@ function reset(devices) {
|
|
|
197
308
|
throw new Error("Device name is required. Usage: spoofy reset <device>");
|
|
198
309
|
}
|
|
199
310
|
|
|
200
|
-
devices.
|
|
311
|
+
logVerbose(`Resetting MAC address on ${devices.length} device(s)`);
|
|
312
|
+
|
|
313
|
+
devices.forEach((device, index) => {
|
|
314
|
+
logVerbose(`Processing device ${index + 1}/${devices.length}: ${device}`);
|
|
315
|
+
showProgress(`Finding interface ${device}`);
|
|
316
|
+
|
|
201
317
|
const it = spoof.findInterface(device);
|
|
318
|
+
hideProgress();
|
|
202
319
|
|
|
203
320
|
if (!it) {
|
|
204
321
|
throw new Error(
|
|
@@ -214,12 +331,22 @@ function reset(devices) {
|
|
|
214
331
|
);
|
|
215
332
|
}
|
|
216
333
|
|
|
217
|
-
|
|
334
|
+
logVerbose(`Hardware MAC address: ${it.address}`);
|
|
335
|
+
|
|
336
|
+
if (JSON_OUTPUT) {
|
|
337
|
+
// Will be output in setMACAddress
|
|
338
|
+
} else {
|
|
339
|
+
console.log(chalk.blue("ℹ"), `Resetting to hardware MAC address: ${chalk.bold.cyan(it.address)}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
218
342
|
setMACAddress(it.device, it.address, it.port);
|
|
219
343
|
});
|
|
220
344
|
}
|
|
221
345
|
|
|
222
346
|
function list() {
|
|
347
|
+
logVerbose("Starting interface discovery...");
|
|
348
|
+
showProgress("Discovering network interfaces");
|
|
349
|
+
|
|
223
350
|
const targets = [];
|
|
224
351
|
if (argv.wifi) {
|
|
225
352
|
if (process.platform === "win32") {
|
|
@@ -230,6 +357,23 @@ function list() {
|
|
|
230
357
|
}
|
|
231
358
|
|
|
232
359
|
const interfaces = spoof.findInterfaces(targets);
|
|
360
|
+
hideProgress();
|
|
361
|
+
logVerbose(`Found ${interfaces.length} interface(s)`);
|
|
362
|
+
|
|
363
|
+
if (JSON_OUTPUT) {
|
|
364
|
+
outputJSON({
|
|
365
|
+
interfaces: interfaces.map((it) => ({
|
|
366
|
+
port: it.port || it.device,
|
|
367
|
+
device: it.device,
|
|
368
|
+
address: it.address,
|
|
369
|
+
currentAddress: it.currentAddress,
|
|
370
|
+
status: it.status,
|
|
371
|
+
description: it.description,
|
|
372
|
+
})),
|
|
373
|
+
count: interfaces.length,
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
233
377
|
|
|
234
378
|
if (interfaces.length === 0) {
|
|
235
379
|
console.log(chalk.yellow("No network interfaces found."));
|
|
@@ -260,8 +404,11 @@ function list() {
|
|
|
260
404
|
}
|
|
261
405
|
|
|
262
406
|
function setMACAddress(device, mac, port) {
|
|
407
|
+
logVerbose(`Setting MAC address ${mac} on device ${device}`);
|
|
408
|
+
|
|
263
409
|
// Check for admin/root privileges
|
|
264
410
|
if (process.platform === "win32") {
|
|
411
|
+
logVerbose("Checking for Administrator privileges...");
|
|
265
412
|
// On Windows, check if running as administrator
|
|
266
413
|
try {
|
|
267
414
|
const output = cp
|
|
@@ -279,34 +426,66 @@ function setMACAddress(device, mac, port) {
|
|
|
279
426
|
"Right-click Command Prompt or PowerShell and select 'Run as Administrator'"
|
|
280
427
|
);
|
|
281
428
|
}
|
|
429
|
+
logVerbose("Administrator privileges confirmed");
|
|
282
430
|
} catch (err) {
|
|
283
431
|
if (err.message.includes("Must run as Administrator")) {
|
|
284
432
|
throw err;
|
|
285
433
|
}
|
|
286
434
|
// If check fails, warn but continue (might work anyway)
|
|
287
|
-
|
|
435
|
+
if (!JSON_OUTPUT) {
|
|
436
|
+
console.warn(chalk.yellow("Warning: Could not verify administrator privileges. Operation may fail."));
|
|
437
|
+
}
|
|
438
|
+
logVerbose("Could not verify privileges, continuing anyway");
|
|
288
439
|
}
|
|
289
440
|
} else if (process.platform !== "win32") {
|
|
441
|
+
logVerbose("Checking for root privileges...");
|
|
290
442
|
// Unix-like systems (macOS, Linux)
|
|
291
443
|
if (process.getuid && process.getuid() !== 0) {
|
|
292
444
|
throw new Error(
|
|
293
445
|
"Must run as root (or using sudo) to change network settings"
|
|
294
446
|
);
|
|
295
447
|
}
|
|
448
|
+
logVerbose("Root privileges confirmed");
|
|
296
449
|
}
|
|
297
450
|
|
|
298
451
|
try {
|
|
452
|
+
showProgress("Changing MAC address");
|
|
453
|
+
logVerbose(`Calling setInterfaceMAC(${device}, ${mac}, ${port})`);
|
|
454
|
+
|
|
299
455
|
spoof.setInterfaceMAC(device, mac, port);
|
|
300
|
-
console.log(
|
|
301
|
-
chalk.green("✓") +
|
|
302
|
-
" Successfully set MAC address to " +
|
|
303
|
-
chalk.bold.cyan(mac) +
|
|
304
|
-
" on " +
|
|
305
|
-
chalk.bold.green(device)
|
|
306
|
-
);
|
|
307
456
|
|
|
457
|
+
hideProgress();
|
|
458
|
+
|
|
459
|
+
if (JSON_OUTPUT) {
|
|
460
|
+
outputJSON({
|
|
461
|
+
success: true,
|
|
462
|
+
device: device,
|
|
463
|
+
mac: mac,
|
|
464
|
+
message: "MAC address changed successfully",
|
|
465
|
+
});
|
|
466
|
+
} else {
|
|
467
|
+
console.log(
|
|
468
|
+
chalk.green("✓") +
|
|
469
|
+
" Successfully set MAC address to " +
|
|
470
|
+
chalk.bold.cyan(mac) +
|
|
471
|
+
" on " +
|
|
472
|
+
chalk.bold.green(device)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
logVerbose("MAC address change completed successfully");
|
|
308
477
|
// Note: Verification is already done in setInterfaceMAC, so we don't need to do it again here
|
|
309
478
|
} catch (err) {
|
|
479
|
+
hideProgress();
|
|
480
|
+
if (JSON_OUTPUT) {
|
|
481
|
+
outputJSON({
|
|
482
|
+
success: false,
|
|
483
|
+
device: device,
|
|
484
|
+
mac: mac,
|
|
485
|
+
error: err.message,
|
|
486
|
+
code: err.code,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
310
489
|
// Error is already formatted by handleError in the catch block above
|
|
311
490
|
throw err;
|
|
312
491
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spoof-d",
|
|
3
3
|
"description": "Cross-platform MAC address spoofing utility for macOS, Windows, and Linux",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"spoofy": "./bin/cmd.js"
|
|
7
7
|
},
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"index.js",
|
|
10
10
|
"bin/",
|
|
11
11
|
"LICENSE",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
".spoofyrc.example"
|
|
13
14
|
],
|
|
14
15
|
"bugs": {
|
|
15
16
|
"url": "https://github.com/TT5H/spoof-d/issues"
|