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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "randomize": {
3
+ "local": false
4
+ },
5
+ "defaults": {
6
+ "verbose": false,
7
+ "json": false
8
+ }
9
+ }
10
+
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
- console.log(require("../package.json").version);
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.forEach((device) => {
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
- console.log(normalized);
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
- devices.forEach((device) => {
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
- const mac = spoof.randomize(argv.local);
190
- console.log(chalk.blue("ℹ"), `Generated random MAC address: ${chalk.bold.cyan(mac)}`);
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.forEach((device) => {
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
- console.log(chalk.blue("ℹ"), `Resetting to hardware MAC address: ${chalk.bold.cyan(it.address)}`);
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
- console.warn(chalk.yellow("Warning: Could not verify administrator privileges. Operation may fail."));
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.1.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"