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.
Files changed (59) hide show
  1. package/README.md +125 -0
  2. package/dist/commands/clone.d.ts +1 -0
  3. package/dist/commands/clone.js +66 -0
  4. package/dist/commands/clone.js.map +1 -0
  5. package/dist/commands/delete.d.ts +1 -0
  6. package/dist/commands/delete.js +13 -0
  7. package/dist/commands/delete.js.map +1 -0
  8. package/dist/commands/doctor.d.ts +1 -0
  9. package/dist/commands/doctor.js +73 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/export.d.ts +1 -0
  12. package/dist/commands/export.js +14 -0
  13. package/dist/commands/export.js.map +1 -0
  14. package/dist/commands/import.d.ts +1 -0
  15. package/dist/commands/import.js +39 -0
  16. package/dist/commands/import.js.map +1 -0
  17. package/dist/commands/list.d.ts +1 -0
  18. package/dist/commands/list.js +26 -0
  19. package/dist/commands/list.js.map +1 -0
  20. package/dist/commands/read.d.ts +1 -0
  21. package/dist/commands/read.js +41 -0
  22. package/dist/commands/read.js.map +1 -0
  23. package/dist/commands/rename.d.ts +1 -0
  24. package/dist/commands/rename.js +17 -0
  25. package/dist/commands/rename.js.map +1 -0
  26. package/dist/commands/setup.d.ts +1 -0
  27. package/dist/commands/setup.js +158 -0
  28. package/dist/commands/setup.js.map +1 -0
  29. package/dist/commands/show.d.ts +1 -0
  30. package/dist/commands/show.js +20 -0
  31. package/dist/commands/show.js.map +1 -0
  32. package/dist/commands/write.d.ts +1 -0
  33. package/dist/commands/write.js +22 -0
  34. package/dist/commands/write.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +58 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/lib/card-ops.d.ts +2 -0
  39. package/dist/lib/card-ops.js +89 -0
  40. package/dist/lib/card-ops.js.map +1 -0
  41. package/dist/lib/display.d.ts +7 -0
  42. package/dist/lib/display.js +20 -0
  43. package/dist/lib/display.js.map +1 -0
  44. package/dist/lib/firmware.d.ts +13 -0
  45. package/dist/lib/firmware.js +79 -0
  46. package/dist/lib/firmware.js.map +1 -0
  47. package/dist/lib/parsers.d.ts +25 -0
  48. package/dist/lib/parsers.js +58 -0
  49. package/dist/lib/parsers.js.map +1 -0
  50. package/dist/lib/pm3.d.ts +11 -0
  51. package/dist/lib/pm3.js +48 -0
  52. package/dist/lib/pm3.js.map +1 -0
  53. package/dist/lib/prompts.d.ts +4 -0
  54. package/dist/lib/prompts.js +27 -0
  55. package/dist/lib/prompts.js.map +1 -0
  56. package/dist/lib/store.d.ts +16 -0
  57. package/dist/lib/store.js +74 -0
  58. package/dist/lib/store.js.map +1 -0
  59. 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