keyfabe 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/README.md +125 -0
- package/dist/commands/clone.d.ts +1 -0
- package/dist/commands/clone.js +66 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +1 -0
- package/dist/commands/delete.js +13 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +73 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.js +14 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.js +39 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +26 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/read.d.ts +1 -0
- package/dist/commands/read.js +41 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/rename.d.ts +1 -0
- package/dist/commands/rename.js +17 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +158 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +20 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/write.d.ts +1 -0
- package/dist/commands/write.js +22 -0
- package/dist/commands/write.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/card-ops.d.ts +2 -0
- package/dist/lib/card-ops.js +89 -0
- package/dist/lib/card-ops.js.map +1 -0
- package/dist/lib/display.d.ts +7 -0
- package/dist/lib/display.js +20 -0
- package/dist/lib/display.js.map +1 -0
- package/dist/lib/firmware.d.ts +13 -0
- package/dist/lib/firmware.js +79 -0
- package/dist/lib/firmware.js.map +1 -0
- package/dist/lib/parsers.d.ts +25 -0
- package/dist/lib/parsers.js +58 -0
- package/dist/lib/parsers.js.map +1 -0
- package/dist/lib/pm3.d.ts +11 -0
- package/dist/lib/pm3.js +48 -0
- package/dist/lib/pm3.js.map +1 -0
- package/dist/lib/prompts.d.ts +4 -0
- package/dist/lib/prompts.js +27 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/store.d.ts +16 -0
- package/dist/lib/store.js +74 -0
- package/dist/lib/store.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# keyfabe
|
|
2
|
+
|
|
3
|
+
A TypeScript CLI tool that wraps the Proxmark3 client to provide an ergonomic keyfob cloning workflow.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Automates the multi-step read/detect/clone/verify process into a single guided flow
|
|
8
|
+
- Detects and surfaces common problems (device not found, firmware mismatch, antenna issues)
|
|
9
|
+
- Stores cloned fob identities for later re-use
|
|
10
|
+
- Walks you through flashing Iceman firmware on a stock Proxmark3 Easy
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- [Proxmark3 Iceman firmware](https://github.com/RfidResearchGroup/proxmark3) installed via Homebrew:
|
|
15
|
+
```sh
|
|
16
|
+
brew tap rfidresearchgroup/proxmark3
|
|
17
|
+
brew install proxmark3
|
|
18
|
+
```
|
|
19
|
+
- A Proxmark3 Easy (or compatible) device
|
|
20
|
+
|
|
21
|
+
If your device has stock firmware, `keyfabe setup` will handle building and flashing the correct firmware for you.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
npm link # makes `keyfabe` available globally
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
# check device connection, firmware, and antenna health
|
|
35
|
+
keyfabe doctor
|
|
36
|
+
|
|
37
|
+
# flash Iceman firmware to a stock Proxmark3 Easy
|
|
38
|
+
keyfabe setup
|
|
39
|
+
|
|
40
|
+
# guided interactive clone flow (read original → write to blank)
|
|
41
|
+
keyfabe clone
|
|
42
|
+
|
|
43
|
+
# read and identify a fob without cloning
|
|
44
|
+
keyfabe read
|
|
45
|
+
|
|
46
|
+
# write a previously-saved identity to a blank fob
|
|
47
|
+
keyfabe write <name>
|
|
48
|
+
|
|
49
|
+
# list saved identities
|
|
50
|
+
keyfabe list
|
|
51
|
+
|
|
52
|
+
# delete a saved identity
|
|
53
|
+
keyfabe delete <name>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
### `keyfabe doctor`
|
|
59
|
+
|
|
60
|
+
Pre-flight check. Verifies device port, firmware communication, and antenna tuning. Gives specific guidance if something is wrong (missing pm3, incompatible firmware, low antenna voltage).
|
|
61
|
+
|
|
62
|
+
### `keyfabe setup`
|
|
63
|
+
|
|
64
|
+
Interactive wizard for flashing Iceman firmware to a stock Proxmark3 Easy. Handles prerequisites check, source extraction from Homebrew cache, building with 256KB size constraints, flashing with bootloader unlock, and post-flash verification.
|
|
65
|
+
|
|
66
|
+
### `keyfabe clone`
|
|
67
|
+
|
|
68
|
+
Guided clone flow: reads the original fob, detects a blank T55x7, writes the ID, and verifies the readback. Optionally saves the identity for later use.
|
|
69
|
+
|
|
70
|
+
### `keyfabe read`
|
|
71
|
+
|
|
72
|
+
Reads and identifies whatever fob is on the antenna. Supports EM410x and HID Prox. Optionally saves the identity.
|
|
73
|
+
|
|
74
|
+
### `keyfabe write <name>`
|
|
75
|
+
|
|
76
|
+
Writes a previously-saved identity to a blank T55x7 fob.
|
|
77
|
+
|
|
78
|
+
### `keyfabe list`
|
|
79
|
+
|
|
80
|
+
Lists all saved fob identities from `~/.keyfabe/fobs.json`.
|
|
81
|
+
|
|
82
|
+
### `keyfabe delete <name>`
|
|
83
|
+
|
|
84
|
+
Deletes a saved fob identity.
|
|
85
|
+
|
|
86
|
+
## Supported Card Types
|
|
87
|
+
|
|
88
|
+
| Type | Read | Clone | Notes |
|
|
89
|
+
|------|------|-------|-------|
|
|
90
|
+
| EM410x | yes | yes | Most common LF keyfob |
|
|
91
|
+
| HID Prox | yes | yes | Uses `lf hid clone` |
|
|
92
|
+
| T55x7 | detect | n/a | Target writable card |
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm run dev # run directly with tsx
|
|
98
|
+
npm run lint # check lint + formatting (Biome)
|
|
99
|
+
npm run lint:fix # auto-fix lint + formatting
|
|
100
|
+
npm test # run tests
|
|
101
|
+
npm run build # compile TypeScript
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A pre-commit hook runs lint, test, and build automatically on every commit. CI does the same on push/PR via GitHub Actions.
|
|
105
|
+
|
|
106
|
+
## Architecture
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
src/
|
|
110
|
+
index.ts # entry point, CLI arg parsing (commander)
|
|
111
|
+
commands/
|
|
112
|
+
doctor.ts # device health check
|
|
113
|
+
setup.ts # firmware flash wizard
|
|
114
|
+
read.ts # read fob
|
|
115
|
+
clone.ts # guided clone flow
|
|
116
|
+
write.ts # write saved identity
|
|
117
|
+
list.ts # list saved identities
|
|
118
|
+
delete.ts # delete saved identity
|
|
119
|
+
lib/
|
|
120
|
+
pm3.ts # spawns pm3 process, sends commands
|
|
121
|
+
firmware.ts # build/flash subprocess helpers
|
|
122
|
+
parsers.ts # parse pm3 output (card type, ID, voltages)
|
|
123
|
+
store.ts # read/write ~/.keyfabe/fobs.json
|
|
124
|
+
prompts.ts # interactive user prompts
|
|
125
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function clone(): Promise<boolean>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { writeAndVerify } from "../lib/card-ops.js";
|
|
4
|
+
import { printCardInfo } from "../lib/display.js";
|
|
5
|
+
import { parseLfSearch } from "../lib/parsers.js";
|
|
6
|
+
import { Pm3Error, pm3Exec } from "../lib/pm3.js";
|
|
7
|
+
import { promptName, waitForEnter } from "../lib/prompts.js";
|
|
8
|
+
import { saveFob } from "../lib/store.js";
|
|
9
|
+
const MAX_READ_RETRIES = 3;
|
|
10
|
+
const READ_RETRY_DELAY = 2000;
|
|
11
|
+
export async function clone() {
|
|
12
|
+
console.log(chalk.bold("\nKeyfob Clone\n"));
|
|
13
|
+
// Step 1: Read original
|
|
14
|
+
await waitForEnter("Place your original keyfob on the antenna.");
|
|
15
|
+
let card = null;
|
|
16
|
+
for (let attempt = 1; attempt <= MAX_READ_RETRIES; attempt++) {
|
|
17
|
+
const spinner = ora(`Reading original (attempt ${attempt}/${MAX_READ_RETRIES})...`).start();
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await pm3Exec("lf search");
|
|
20
|
+
card = parseLfSearch(stdout);
|
|
21
|
+
if (card) {
|
|
22
|
+
spinner.succeed("Original card read");
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
spinner.fail("No card detected.");
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof Pm3Error) {
|
|
29
|
+
spinner.fail(err.message);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
spinner.fail("Read failed.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (attempt < MAX_READ_RETRIES) {
|
|
36
|
+
console.log(chalk.dim(` Retrying in ${READ_RETRY_DELAY / 1000}s...`));
|
|
37
|
+
await new Promise((r) => setTimeout(r, READ_RETRY_DELAY));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!card) {
|
|
41
|
+
console.log(chalk.red("\nFailed to read original card after all attempts."));
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
printCardInfo(card);
|
|
45
|
+
// Step 2: Write to blank
|
|
46
|
+
await waitForEnter("Remove original and place a blank T55x7 fob on the antenna.");
|
|
47
|
+
const success = await writeAndVerify(card);
|
|
48
|
+
if (success) {
|
|
49
|
+
console.log(chalk.green("\nClone successful!\n"));
|
|
50
|
+
const name = await promptName();
|
|
51
|
+
if (name) {
|
|
52
|
+
await saveFob({
|
|
53
|
+
name,
|
|
54
|
+
type: card.type,
|
|
55
|
+
id: card.id,
|
|
56
|
+
encoding: card.encoding,
|
|
57
|
+
savedAt: new Date().toISOString(),
|
|
58
|
+
});
|
|
59
|
+
console.log(chalk.green(`Saved as "${name}".`));
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
console.log(chalk.red("\nClone failed.\n"));
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=clone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone.js","sourceRoot":"","sources":["../../src/commands/clone.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deleteFob(name: string): Promise<boolean>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { printFobNotFound } from "../lib/display.js";
|
|
3
|
+
import { removeFob } from "../lib/store.js";
|
|
4
|
+
export async function deleteFob(name) {
|
|
5
|
+
const removed = await removeFob(name);
|
|
6
|
+
if (!removed) {
|
|
7
|
+
printFobNotFound(name);
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
console.log(chalk.green(`\nDeleted "${name}".\n`));
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,IAAI,MAAM,CAAC,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doctor(): Promise<boolean>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { HF_VOLTAGE_THRESHOLD, LF_VOLTAGE_THRESHOLD, printBrewInstall } from "../lib/display.js";
|
|
4
|
+
import { parseHwStatus, parseHwTune } from "../lib/parsers.js";
|
|
5
|
+
import { detectPort, Pm3Error, pm3Exec } from "../lib/pm3.js";
|
|
6
|
+
export async function doctor() {
|
|
7
|
+
console.log(chalk.bold("\nProxmark3 Health Check\n"));
|
|
8
|
+
// 1. Port detection
|
|
9
|
+
const port = await detectPort();
|
|
10
|
+
if (port) {
|
|
11
|
+
console.log(chalk.green(` Port: ${port}`));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.log(chalk.red(" Port: No Proxmark3 detected. Check USB connection."));
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// 2. Communication check
|
|
18
|
+
const spinner = ora("Checking device communication...").start();
|
|
19
|
+
try {
|
|
20
|
+
const { stdout } = await pm3Exec("hw status");
|
|
21
|
+
const status = parseHwStatus(stdout);
|
|
22
|
+
if (!status.connected) {
|
|
23
|
+
spinner.fail("Cannot communicate with device.");
|
|
24
|
+
console.log(chalk.yellow(" Device found but firmware is incompatible. Run `keyfabe setup` to flash Iceman firmware."));
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
spinner.succeed("Device communication OK");
|
|
28
|
+
console.log(chalk.green(` Firmware: ${status.firmwareVersion}`));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof Pm3Error) {
|
|
32
|
+
if (err.message.includes("not found")) {
|
|
33
|
+
spinner.fail("pm3 command not found.");
|
|
34
|
+
printBrewInstall();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
spinner.fail(err.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
spinner.fail("Failed to communicate with device.");
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// 3. Antenna tuning
|
|
46
|
+
const tuneSpinner = ora("Checking antenna tuning...").start();
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await pm3Exec("hw tune");
|
|
49
|
+
const tune = parseHwTune(stdout);
|
|
50
|
+
tuneSpinner.succeed("Antenna tuning complete");
|
|
51
|
+
const lfColor = tune.lfVoltage >= LF_VOLTAGE_THRESHOLD ? chalk.green : chalk.red;
|
|
52
|
+
const hfColor = tune.hfVoltage >= HF_VOLTAGE_THRESHOLD ? chalk.green : chalk.red;
|
|
53
|
+
console.log(lfColor(` LF antenna: ${tune.lfVoltage.toFixed(2)}V (125 kHz)`));
|
|
54
|
+
console.log(hfColor(` HF antenna: ${tune.hfVoltage.toFixed(2)}V (13.56 MHz)`));
|
|
55
|
+
if (tune.lfVoltage < LF_VOLTAGE_THRESHOLD) {
|
|
56
|
+
console.log(chalk.yellow(" ⚠ LF antenna reading low. Check antenna connection."));
|
|
57
|
+
}
|
|
58
|
+
if (tune.hfVoltage < HF_VOLTAGE_THRESHOLD) {
|
|
59
|
+
console.log(chalk.yellow(" ⚠ HF antenna reading low. Check antenna connection."));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err instanceof Pm3Error) {
|
|
64
|
+
tuneSpinner.fail(err.message);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
tuneSpinner.fail("Failed to check antenna tuning.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
console.log();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function exportFobs(): Promise<boolean>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { loadFobs } from "../lib/store.js";
|
|
3
|
+
export async function exportFobs() {
|
|
4
|
+
const fobs = await loadFobs();
|
|
5
|
+
if (fobs.length === 0) {
|
|
6
|
+
console.error(chalk.dim("No saved fobs to export."));
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
// Output clean JSON to stdout for piping
|
|
10
|
+
process.stdout.write(`${JSON.stringify(fobs, null, 2)}\n`);
|
|
11
|
+
console.error(chalk.dim(`Exported ${fobs.length} fob(s).`));
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,UAAU;IAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;IAE9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC;QACrD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,yCAAyC;IACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function importFile(filePath: string): Promise<boolean>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { importFobs } from "../lib/store.js";
|
|
4
|
+
function validateFobs(data) {
|
|
5
|
+
if (!Array.isArray(data))
|
|
6
|
+
return false;
|
|
7
|
+
return data.every((item) => typeof item === "object" &&
|
|
8
|
+
item !== null &&
|
|
9
|
+
typeof item.name === "string" &&
|
|
10
|
+
typeof item.type === "string" &&
|
|
11
|
+
typeof item.id === "string" &&
|
|
12
|
+
typeof item.savedAt === "string");
|
|
13
|
+
}
|
|
14
|
+
export async function importFile(filePath) {
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = await readFile(filePath, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
console.log(chalk.red(`\nCannot read file: ${filePath}\n`));
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
let data;
|
|
24
|
+
try {
|
|
25
|
+
data = JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
console.log(chalk.red("\nInvalid JSON.\n"));
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!validateFobs(data)) {
|
|
32
|
+
console.log(chalk.red("\nInvalid format. Expected an array of fob objects with name, type, id, and savedAt.\n"));
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const { added, updated } = await importFobs(data);
|
|
36
|
+
console.log(chalk.green(`\nImported ${added} new, updated ${updated} existing.\n`));
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,SAAS,YAAY,CAAC,IAAa;IAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CACb,CAAC,IAAI,EAAE,EAAE,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAC7B,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAC7B,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ;QAC3B,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CACvC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC7C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACD,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uBAAuB,QAAQ,IAAI,CAAC,CAAC,CAAC;QAC5D,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CACP,KAAK,CAAC,GAAG,CAAC,wFAAwF,CAAC,CACtG,CAAC;QACF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,KAAK,iBAAiB,OAAO,cAAc,CAAC,CAAC,CAAC;IACpF,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function list(): Promise<boolean>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { loadFobs } from "../lib/store.js";
|
|
3
|
+
export async function list() {
|
|
4
|
+
const fobs = await loadFobs();
|
|
5
|
+
if (fobs.length === 0) {
|
|
6
|
+
console.log(chalk.dim("\nNo saved fobs. Use `keyfabe read` or `keyfabe clone` to save one.\n"));
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
const cols = {
|
|
10
|
+
name: Math.max(12, ...fobs.map((f) => f.name.length + 2)),
|
|
11
|
+
type: Math.max(10, ...fobs.map((f) => f.type.length + 2)),
|
|
12
|
+
id: Math.max(16, ...fobs.map((f) => f.id.length + 2)),
|
|
13
|
+
};
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(chalk.bold("Name".padEnd(cols.name)) +
|
|
16
|
+
chalk.bold("Type".padEnd(cols.type)) +
|
|
17
|
+
chalk.bold("ID".padEnd(cols.id)) +
|
|
18
|
+
chalk.bold("Saved"));
|
|
19
|
+
for (const fob of fobs) {
|
|
20
|
+
const date = fob.savedAt.slice(0, 10);
|
|
21
|
+
console.log(fob.name.padEnd(cols.name) + fob.type.padEnd(cols.type) + fob.id.padEnd(cols.id) + date);
|
|
22
|
+
}
|
|
23
|
+
console.log();
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,IAAI;IACtB,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;IAE9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC,CAAC;QAChG,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAG;QACT,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzD,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzD,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;KACxD,CAAC;IAEF,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CACP,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAC1B,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACzG,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function read(): Promise<boolean>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { printCardInfo } from "../lib/display.js";
|
|
4
|
+
import { parseLfSearch } from "../lib/parsers.js";
|
|
5
|
+
import { Pm3Error, pm3Exec } from "../lib/pm3.js";
|
|
6
|
+
import { promptName } from "../lib/prompts.js";
|
|
7
|
+
import { saveFob } from "../lib/store.js";
|
|
8
|
+
export async function read() {
|
|
9
|
+
let card = null;
|
|
10
|
+
const spinner = ora("Searching for card (LF)...").start();
|
|
11
|
+
try {
|
|
12
|
+
const { stdout } = await pm3Exec("lf search");
|
|
13
|
+
card = parseLfSearch(stdout);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err instanceof Pm3Error) {
|
|
17
|
+
spinner.fail(err.message);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!card) {
|
|
22
|
+
spinner.fail("No card detected. Make sure the fob is flat against the antenna.");
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
spinner.succeed("Card detected");
|
|
26
|
+
printCardInfo(card);
|
|
27
|
+
// Prompt to save
|
|
28
|
+
const name = await promptName();
|
|
29
|
+
if (name) {
|
|
30
|
+
await saveFob({
|
|
31
|
+
name,
|
|
32
|
+
type: card.type,
|
|
33
|
+
id: card.id,
|
|
34
|
+
encoding: card.encoding,
|
|
35
|
+
savedAt: new Date().toISOString(),
|
|
36
|
+
});
|
|
37
|
+
console.log(chalk.green(`Saved as "${name}".`));
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/commands/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,IAAI;IACtB,IAAI,IAAI,GAAqC,IAAI,CAAC;IAElD,MAAM,OAAO,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,KAAK,EAAE,CAAC;IAC1D,IAAI,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;QACjF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACjC,aAAa,CAAC,IAAI,CAAC,CAAC;IAEpB,iBAAiB;IACjB,MAAM,IAAI,GAAG,MAAM,UAAU,EAAE,CAAC;IAChC,IAAI,IAAI,EAAE,CAAC;QACP,MAAM,OAAO,CAAC;YACV,IAAI;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function rename(oldName: string, newName: string): Promise<boolean>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { printFobNotFound } from "../lib/display.js";
|
|
3
|
+
import { renameFob } from "../lib/store.js";
|
|
4
|
+
export async function rename(oldName, newName) {
|
|
5
|
+
const result = await renameFob(oldName, newName);
|
|
6
|
+
if (result === "not-found") {
|
|
7
|
+
printFobNotFound(oldName);
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (result === "name-taken") {
|
|
11
|
+
console.log(chalk.red(`\nA fob named "${newName}" already exists. Delete it first or choose a different name.\n`));
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
console.log(chalk.green(`\nRenamed "${oldName}" to "${newName}".\n`));
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=rename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rename.js","sourceRoot":"","sources":["../../src/commands/rename.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAe,EAAE,OAAe;IACzD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QACzB,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CACP,KAAK,CAAC,GAAG,CAAC,kBAAkB,OAAO,iEAAiE,CAAC,CACxG,CAAC;QACF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,OAAO,SAAS,OAAO,MAAM,CAAC,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setup(): Promise<boolean>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { printBrewInstall } from "../lib/display.js";
|
|
4
|
+
import { buildFirmware, checkInstalled, execCommand, findBrewCache, flashFirmware, waitForDevice, } from "../lib/firmware.js";
|
|
5
|
+
import { parseHwStatus, parseHwTune } from "../lib/parsers.js";
|
|
6
|
+
import { detectPort, Pm3Error, pm3Exec } from "../lib/pm3.js";
|
|
7
|
+
import { confirm } from "../lib/prompts.js";
|
|
8
|
+
export async function setup() {
|
|
9
|
+
console.log(chalk.bold("\nProxmark3 Firmware Setup Wizard\n"));
|
|
10
|
+
// Step 1: Prerequisites
|
|
11
|
+
console.log(chalk.bold("Step 1: Checking prerequisites...\n"));
|
|
12
|
+
const hasPm3 = await checkInstalled("pm3");
|
|
13
|
+
if (!hasPm3) {
|
|
14
|
+
console.log(chalk.red(" pm3 not found."));
|
|
15
|
+
printBrewInstall();
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
console.log(chalk.green(" pm3: installed"));
|
|
19
|
+
const hasMake = await checkInstalled("make");
|
|
20
|
+
if (!hasMake) {
|
|
21
|
+
console.log(chalk.red(" make not found. Install Xcode Command Line Tools: xcode-select --install"));
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
console.log(chalk.green(" make: installed"));
|
|
25
|
+
const hasProxmark3 = await checkInstalled("proxmark3");
|
|
26
|
+
if (!hasProxmark3) {
|
|
27
|
+
console.log(chalk.red(" proxmark3 flasher not found."));
|
|
28
|
+
printBrewInstall();
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
console.log(chalk.green(" proxmark3: installed"));
|
|
32
|
+
const port = await detectPort();
|
|
33
|
+
if (!port) {
|
|
34
|
+
console.log(chalk.red("\n No Proxmark3 detected. Plug in your device and try again."));
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk.green(` device: ${port}`));
|
|
38
|
+
// Step 2: Diagnose current state
|
|
39
|
+
console.log(chalk.bold("\nStep 2: Checking current firmware...\n"));
|
|
40
|
+
const diagSpinner = ora("Running hw status...").start();
|
|
41
|
+
try {
|
|
42
|
+
const { stdout } = await pm3Exec("hw status");
|
|
43
|
+
const status = parseHwStatus(stdout);
|
|
44
|
+
if (status.connected) {
|
|
45
|
+
diagSpinner.succeed("Firmware is already working.");
|
|
46
|
+
console.log(chalk.green(` Firmware: ${status.firmwareVersion}`));
|
|
47
|
+
console.log(chalk.green("\n No action needed. Device is ready to use."));
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
diagSpinner.warn("Device found but firmware is incompatible. Proceeding with flash.");
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof Pm3Error && err.message.includes("not found")) {
|
|
54
|
+
diagSpinner.fail("pm3 command failed.");
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
diagSpinner.warn("Cannot communicate with device. Proceeding with flash.");
|
|
58
|
+
}
|
|
59
|
+
// Step 3: Locate source
|
|
60
|
+
console.log(chalk.bold("\nStep 3: Locating Proxmark3 source...\n"));
|
|
61
|
+
let tarball;
|
|
62
|
+
const sourceSpinner = ora("Finding Homebrew cache...").start();
|
|
63
|
+
try {
|
|
64
|
+
tarball = await findBrewCache();
|
|
65
|
+
sourceSpinner.succeed(`Found: ${tarball}`);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
sourceSpinner.fail("Proxmark3 not installed via Homebrew.");
|
|
69
|
+
printBrewInstall();
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Step 4: Build firmware
|
|
73
|
+
console.log(chalk.bold("\nStep 4: Building firmware...\n"));
|
|
74
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
75
|
+
const sourceDir = `/tmp/pm3build-${suffix}`;
|
|
76
|
+
const extractSpinner = ora("Extracting source...").start();
|
|
77
|
+
try {
|
|
78
|
+
await execCommand("mkdir", ["-p", sourceDir]);
|
|
79
|
+
await execCommand("tar", ["xf", tarball, "-C", sourceDir, "--strip-components=1"]);
|
|
80
|
+
extractSpinner.succeed("Source extracted.");
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
extractSpinner.fail(`Failed to extract source: ${err.message}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const buildSpinner = ora("Building firmware (this may take ~30s)...").start();
|
|
87
|
+
try {
|
|
88
|
+
await buildFirmware(sourceDir);
|
|
89
|
+
buildSpinner.succeed("Firmware built successfully.");
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
buildSpinner.fail(`Build failed: ${err.message}`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
// Step 5: Flash
|
|
96
|
+
console.log(chalk.bold("\nStep 5: Flashing firmware...\n"));
|
|
97
|
+
const shouldFlash = await confirm("Ready to flash firmware. This will overwrite the device's current firmware. Continue?");
|
|
98
|
+
if (!shouldFlash) {
|
|
99
|
+
console.log(chalk.yellow(` Flash cancelled. Build artifacts remain at: ${sourceDir}`));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const currentPort = await detectPort();
|
|
103
|
+
if (!currentPort) {
|
|
104
|
+
console.log(chalk.red(" Device disconnected. Plug it back in and try again."));
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const flashSpinner = ora("Flashing firmware...").start();
|
|
108
|
+
try {
|
|
109
|
+
await flashFirmware(currentPort, sourceDir);
|
|
110
|
+
flashSpinner.succeed("Firmware flashed successfully.");
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
flashSpinner.fail(`Flash failed: ${err.message}`);
|
|
114
|
+
console.log(chalk.yellow(" If flash was interrupted, hold the button while plugging in to enter recovery mode."));
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// Step 6: Post-flash verification
|
|
118
|
+
console.log(chalk.bold("\nStep 6: Verifying...\n"));
|
|
119
|
+
const waitSpinner = ora("Waiting for device to reappear...").start();
|
|
120
|
+
const newPort = await waitForDevice(15_000);
|
|
121
|
+
if (!newPort) {
|
|
122
|
+
waitSpinner.fail("Device did not reappear after flash.");
|
|
123
|
+
console.log(chalk.yellow(" Try unplugging and re-plugging the device."));
|
|
124
|
+
console.log(chalk.yellow(" If it still doesn't work, hold the button while plugging in for recovery mode."));
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
waitSpinner.succeed(`Device found at ${newPort}`);
|
|
128
|
+
const verifySpinner = ora("Checking communication...").start();
|
|
129
|
+
try {
|
|
130
|
+
const { stdout } = await pm3Exec("hw status");
|
|
131
|
+
const status = parseHwStatus(stdout);
|
|
132
|
+
if (!status.connected) {
|
|
133
|
+
verifySpinner.fail("Communication still failing after flash.");
|
|
134
|
+
console.log(chalk.yellow(" Try running `keyfabe setup` again."));
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
verifySpinner.succeed("Communication OK");
|
|
138
|
+
console.log(chalk.green(` Firmware: ${status.firmwareVersion}`));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
verifySpinner.fail("Failed to verify communication.");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const tuneSpinner = ora("Checking antenna tuning...").start();
|
|
145
|
+
try {
|
|
146
|
+
const { stdout } = await pm3Exec("hw tune");
|
|
147
|
+
const tune = parseHwTune(stdout);
|
|
148
|
+
tuneSpinner.succeed("Antenna check complete");
|
|
149
|
+
console.log(chalk.green(` LF antenna: ${tune.lfVoltage.toFixed(2)}V (125 kHz)`));
|
|
150
|
+
console.log(chalk.green(` HF antenna: ${tune.hfVoltage.toFixed(2)}V (13.56 MHz)`));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
tuneSpinner.fail("Failed to check antenna tuning.");
|
|
154
|
+
}
|
|
155
|
+
console.log(chalk.bold.green("\n Setup complete! Device is ready to use.\n"));
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=setup.js.map
|