@huitl/sdk 0.1.1 → 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/dist/cli.cjs CHANGED
@@ -1156,8 +1156,8 @@ var require_command = __commonJS({
1156
1156
  "use strict";
1157
1157
  var EventEmitter = require("events").EventEmitter;
1158
1158
  var childProcess = require("child_process");
1159
- var path3 = require("path");
1160
- var fs2 = require("fs");
1159
+ var path4 = require("path");
1160
+ var fs3 = require("fs");
1161
1161
  var process2 = require("process");
1162
1162
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1163
1163
  var { CommanderError: CommanderError2 } = require_error();
@@ -2138,7 +2138,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2138
2138
  * @param {string} subcommandName
2139
2139
  */
2140
2140
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2141
- if (fs2.existsSync(executableFile)) return;
2141
+ if (fs3.existsSync(executableFile)) return;
2142
2142
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2143
2143
  const executableMissing = `'${executableFile}' does not exist
2144
2144
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2156,11 +2156,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2156
2156
  let launchWithNode = false;
2157
2157
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2158
2158
  function findFile(baseDir, baseName) {
2159
- const localBin = path3.resolve(baseDir, baseName);
2160
- if (fs2.existsSync(localBin)) return localBin;
2161
- if (sourceExt.includes(path3.extname(baseName))) return void 0;
2159
+ const localBin = path4.resolve(baseDir, baseName);
2160
+ if (fs3.existsSync(localBin)) return localBin;
2161
+ if (sourceExt.includes(path4.extname(baseName))) return void 0;
2162
2162
  const foundExt = sourceExt.find(
2163
- (ext) => fs2.existsSync(`${localBin}${ext}`)
2163
+ (ext) => fs3.existsSync(`${localBin}${ext}`)
2164
2164
  );
2165
2165
  if (foundExt) return `${localBin}${foundExt}`;
2166
2166
  return void 0;
@@ -2172,21 +2172,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2172
2172
  if (this._scriptPath) {
2173
2173
  let resolvedScriptPath;
2174
2174
  try {
2175
- resolvedScriptPath = fs2.realpathSync(this._scriptPath);
2175
+ resolvedScriptPath = fs3.realpathSync(this._scriptPath);
2176
2176
  } catch {
2177
2177
  resolvedScriptPath = this._scriptPath;
2178
2178
  }
2179
- executableDir = path3.resolve(
2180
- path3.dirname(resolvedScriptPath),
2179
+ executableDir = path4.resolve(
2180
+ path4.dirname(resolvedScriptPath),
2181
2181
  executableDir
2182
2182
  );
2183
2183
  }
2184
2184
  if (executableDir) {
2185
2185
  let localFile = findFile(executableDir, executableFile);
2186
2186
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2187
- const legacyName = path3.basename(
2187
+ const legacyName = path4.basename(
2188
2188
  this._scriptPath,
2189
- path3.extname(this._scriptPath)
2189
+ path4.extname(this._scriptPath)
2190
2190
  );
2191
2191
  if (legacyName !== this._name) {
2192
2192
  localFile = findFile(
@@ -2197,7 +2197,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2197
2197
  }
2198
2198
  executableFile = localFile || executableFile;
2199
2199
  }
2200
- launchWithNode = sourceExt.includes(path3.extname(executableFile));
2200
+ launchWithNode = sourceExt.includes(path4.extname(executableFile));
2201
2201
  let proc;
2202
2202
  if (process2.platform !== "win32") {
2203
2203
  if (launchWithNode) {
@@ -3044,7 +3044,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3044
3044
  * @return {Command}
3045
3045
  */
3046
3046
  nameFromFilename(filename) {
3047
- this._name = path3.basename(filename, path3.extname(filename));
3047
+ this._name = path4.basename(filename, path4.extname(filename));
3048
3048
  return this;
3049
3049
  }
3050
3050
  /**
@@ -3058,9 +3058,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3058
3058
  * @param {string} [path]
3059
3059
  * @return {(string|null|Command)}
3060
3060
  */
3061
- executableDir(path4) {
3062
- if (path4 === void 0) return this._executableDir;
3063
- this._executableDir = path4;
3061
+ executableDir(path5) {
3062
+ if (path5 === void 0) return this._executableDir;
3063
+ this._executableDir = path5;
3064
3064
  return this;
3065
3065
  }
3066
3066
  /**
@@ -3713,6 +3713,380 @@ var init_app = __esm({
3713
3713
  }
3714
3714
  });
3715
3715
 
3716
+ // src/cli/commands/login.ts
3717
+ var login_exports = {};
3718
+ __export(login_exports, {
3719
+ getApiKey: () => getApiKey,
3720
+ registerLogin: () => registerLogin,
3721
+ saveApiKey: () => saveApiKey
3722
+ });
3723
+ function getApiKey() {
3724
+ try {
3725
+ const config = JSON.parse(import_fs2.default.readFileSync(CONFIG_FILE, "utf-8"));
3726
+ return config.apiKey ?? null;
3727
+ } catch {
3728
+ return null;
3729
+ }
3730
+ }
3731
+ function saveApiKey(apiKey) {
3732
+ if (!import_fs2.default.existsSync(CONFIG_DIR)) import_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
3733
+ import_fs2.default.writeFileSync(CONFIG_FILE, JSON.stringify({ apiKey }, null, 2));
3734
+ }
3735
+ function registerLogin(program3) {
3736
+ program3.command("login").description("Authenticate with HUITL Cloud").option("--api-key <key>", "Set API key directly").action((opts) => {
3737
+ if (opts.apiKey) {
3738
+ saveApiKey(opts.apiKey);
3739
+ console.log("API key saved. You can now use huitl provision.");
3740
+ return;
3741
+ }
3742
+ console.log("To get an API key:");
3743
+ console.log(" 1. Visit https://huitl-cloud.vercel.app/api/auth/google");
3744
+ console.log(" 2. Sign in with Google");
3745
+ console.log(" 3. Copy your API key");
3746
+ console.log(" 4. Run: huitl login --api-key <your-key>");
3747
+ });
3748
+ }
3749
+ var import_fs2, import_path3, import_os, CONFIG_DIR, CONFIG_FILE;
3750
+ var init_login = __esm({
3751
+ "src/cli/commands/login.ts"() {
3752
+ "use strict";
3753
+ import_fs2 = __toESM(require("fs"), 1);
3754
+ import_path3 = __toESM(require("path"), 1);
3755
+ import_os = __toESM(require("os"), 1);
3756
+ CONFIG_DIR = import_path3.default.join(import_os.default.homedir(), ".huitl");
3757
+ CONFIG_FILE = import_path3.default.join(CONFIG_DIR, "config.json");
3758
+ }
3759
+ });
3760
+
3761
+ // src/cloud/client.ts
3762
+ var client_exports = {};
3763
+ __export(client_exports, {
3764
+ HuitlCloudClient: () => HuitlCloudClient
3765
+ });
3766
+ var DEFAULT_BASE_URL, HuitlCloudClient;
3767
+ var init_client = __esm({
3768
+ "src/cloud/client.ts"() {
3769
+ "use strict";
3770
+ DEFAULT_BASE_URL = "https://huitl-cloud.vercel.app";
3771
+ HuitlCloudClient = class {
3772
+ apiKey;
3773
+ baseUrl;
3774
+ constructor(config) {
3775
+ this.apiKey = config.apiKey;
3776
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
3777
+ }
3778
+ async request(path4, options = {}) {
3779
+ const resp = await fetch(`${this.baseUrl}/api${path4}`, {
3780
+ ...options,
3781
+ headers: {
3782
+ Authorization: `Bearer ${this.apiKey}`,
3783
+ "Content-Type": "application/json",
3784
+ ...options.headers
3785
+ }
3786
+ });
3787
+ const data = await resp.json();
3788
+ if (!resp.ok) throw new Error(data.error || data.message || `API error: ${resp.status}`);
3789
+ return data;
3790
+ }
3791
+ async provision(name) {
3792
+ return this.request("/v1/provision", {
3793
+ method: "POST",
3794
+ body: JSON.stringify({ name })
3795
+ });
3796
+ }
3797
+ async registerUid(chipId, uid) {
3798
+ await this.request("/v1/chips", {
3799
+ method: "PATCH",
3800
+ body: JSON.stringify({ chipId, uid })
3801
+ });
3802
+ }
3803
+ async listChips() {
3804
+ return this.request("/v1/chips");
3805
+ }
3806
+ async me() {
3807
+ return this.request("/v1/me");
3808
+ }
3809
+ };
3810
+ }
3811
+ });
3812
+
3813
+ // src/nfc/reader.ts
3814
+ var reader_exports = {};
3815
+ __export(reader_exports, {
3816
+ waitForCard: () => waitForCard
3817
+ });
3818
+ function loadNfcPcsc() {
3819
+ try {
3820
+ return require("nfc-pcsc");
3821
+ } catch {
3822
+ console.error(
3823
+ "Error: nfc-pcsc is required for chip provisioning.\nInstall it with: npm install nfc-pcsc\n\nYou also need a PC/SC compatible NFC reader (e.g. ACR122U).\nOn macOS, PC/SC is built-in. On Linux, install pcscd:\n sudo apt install pcscd"
3824
+ );
3825
+ process.exit(1);
3826
+ }
3827
+ }
3828
+ function waitForCard() {
3829
+ return new Promise((resolve, reject) => {
3830
+ const { NFC } = loadNfcPcsc();
3831
+ const nfc = new NFC();
3832
+ nfc.on("reader", (reader) => {
3833
+ console.log(`Reader found: ${reader.name}`);
3834
+ reader.aid = "D2760000850101";
3835
+ reader.on("card", async (card) => {
3836
+ let uid = card.uid?.toUpperCase() || "";
3837
+ if (!uid || uid.length < 14) {
3838
+ try {
3839
+ const getUid = Buffer.from([255, 202, 0, 0, 0]);
3840
+ const resp = await reader.transmit(getUid, 64);
3841
+ if (resp.length >= 9 && resp[resp.length - 2] === 144) {
3842
+ uid = resp.subarray(0, 7).toString("hex").toUpperCase();
3843
+ }
3844
+ } catch {
3845
+ }
3846
+ }
3847
+ resolve({ uid, reader });
3848
+ });
3849
+ reader.on("error", (err) => reject(err));
3850
+ });
3851
+ nfc.on("error", (err) => {
3852
+ reject(new Error(`NFC error: ${err.message}. Make sure your reader is connected.`));
3853
+ });
3854
+ });
3855
+ }
3856
+ var init_reader = __esm({
3857
+ "src/nfc/reader.ts"() {
3858
+ "use strict";
3859
+ }
3860
+ });
3861
+
3862
+ // src/nfc/ntag424.ts
3863
+ var ntag424_exports = {};
3864
+ __export(ntag424_exports, {
3865
+ authenticateEV2First: () => authenticateEV2First,
3866
+ buildApdu: () => buildApdu,
3867
+ buildNdefUrl: () => buildNdefUrl,
3868
+ calcSessionMac: () => calcSessionMac,
3869
+ calculateOffsets: () => calculateOffsets,
3870
+ changeFileSettings: () => changeFileSettings,
3871
+ changeKey2: () => changeKey2,
3872
+ checkSW: () => checkSW,
3873
+ crc32: () => crc32,
3874
+ deriveAuthSessionKeys: () => deriveAuthSessionKeys,
3875
+ encryptSessionData: () => encryptSessionData,
3876
+ rotateLeft: () => rotateLeft,
3877
+ sendCommand: () => sendCommand,
3878
+ writeNdef: () => writeNdef
3879
+ });
3880
+ function buildApdu(ins, p1, p2, data) {
3881
+ if (data && data.length > 0) {
3882
+ const apdu2 = Buffer.alloc(5 + data.length + 1);
3883
+ apdu2[0] = 144;
3884
+ apdu2[1] = ins;
3885
+ apdu2[2] = p1;
3886
+ apdu2[3] = p2;
3887
+ apdu2[4] = data.length;
3888
+ data.copy(apdu2, 5);
3889
+ apdu2[5 + data.length] = 0;
3890
+ return apdu2;
3891
+ }
3892
+ const apdu = Buffer.alloc(5);
3893
+ apdu[0] = 144;
3894
+ apdu[1] = ins;
3895
+ apdu[2] = p1;
3896
+ apdu[3] = p2;
3897
+ apdu[4] = 0;
3898
+ return apdu;
3899
+ }
3900
+ function checkSW(resp, label) {
3901
+ const sw1 = resp[resp.length - 2];
3902
+ const sw2 = resp[resp.length - 1];
3903
+ if (sw1 === 145 && (sw2 === 0 || sw2 === 175)) {
3904
+ return resp.subarray(0, resp.length - 2);
3905
+ }
3906
+ throw new Error(`${label} failed: SW=${resp.subarray(resp.length - 2).toString("hex").toUpperCase()}`);
3907
+ }
3908
+ function buildNdefUrl(url) {
3909
+ const uriPayload = Buffer.concat([Buffer.from([0]), Buffer.from(url, "ascii")]);
3910
+ const ndefRecord = Buffer.concat([
3911
+ Buffer.from([209]),
3912
+ Buffer.from([1]),
3913
+ Buffer.from([uriPayload.length]),
3914
+ Buffer.from("U", "ascii"),
3915
+ uriPayload
3916
+ ]);
3917
+ const lenBuf = Buffer.alloc(2);
3918
+ lenBuf.writeUInt16BE(ndefRecord.length);
3919
+ return Buffer.concat([lenBuf, ndefRecord]);
3920
+ }
3921
+ function calculateOffsets(url) {
3922
+ const HEADER = 7;
3923
+ const piccPos = url.indexOf("picc_data=") + "picc_data=".length;
3924
+ const cmacPos = url.indexOf("cmac=") + "cmac=".length;
3925
+ return {
3926
+ piccOffset: HEADER + piccPos,
3927
+ macOffset: HEADER + cmacPos,
3928
+ macInputOffset: HEADER + piccPos
3929
+ };
3930
+ }
3931
+ function xor2(a, b) {
3932
+ const r = Buffer.alloc(a.length);
3933
+ for (let i = 0; i < a.length; i++) r[i] = a[i] ^ b[i];
3934
+ return r;
3935
+ }
3936
+ function rotateLeft(buf) {
3937
+ const r = Buffer.alloc(buf.length);
3938
+ buf.copy(r, 0, 1);
3939
+ r[r.length - 1] = buf[0];
3940
+ return r;
3941
+ }
3942
+ function aesEncrypt(key, iv, data) {
3943
+ const c = import_crypto7.default.createCipheriv("aes-128-cbc", key, iv);
3944
+ c.setAutoPadding(false);
3945
+ return Buffer.concat([c.update(data), c.final()]);
3946
+ }
3947
+ function aesDecrypt(key, iv, data) {
3948
+ const d = import_crypto7.default.createDecipheriv("aes-128-cbc", key, iv);
3949
+ d.setAutoPadding(false);
3950
+ return Buffer.concat([d.update(data), d.final()]);
3951
+ }
3952
+ function crc32(data) {
3953
+ let crc = 4294967295;
3954
+ for (let i = 0; i < data.length; i++) {
3955
+ crc ^= data[i];
3956
+ for (let j = 0; j < 8; j++) {
3957
+ if (crc & 1) crc = crc >>> 1 ^ 3988292384;
3958
+ else crc = crc >>> 1;
3959
+ }
3960
+ }
3961
+ crc = crc >>> 0;
3962
+ const buf = Buffer.alloc(4);
3963
+ buf.writeUInt32LE(crc);
3964
+ return buf;
3965
+ }
3966
+ function deriveAuthSessionKeys(rndA, rndB, key) {
3967
+ const xorPart = Buffer.alloc(6);
3968
+ for (let i = 0; i < 6; i++) xorPart[i] = rndA[2 + i] ^ rndB[i];
3969
+ const svSuffix = Buffer.concat([
3970
+ rndA.subarray(0, 2),
3971
+ xorPart,
3972
+ rndB.subarray(6, 16),
3973
+ rndA.subarray(8, 16)
3974
+ ]);
3975
+ const sv1 = Buffer.concat([Buffer.from("A55A00010080", "hex"), svSuffix]);
3976
+ const sv2 = Buffer.concat([Buffer.from("5AA500010080", "hex"), svSuffix]);
3977
+ return { kEnc: aesCmac(key, sv1), kMac: aesCmac(key, sv2) };
3978
+ }
3979
+ function calcSessionMac(session, cmd, data) {
3980
+ const ctrLE = Buffer.alloc(2);
3981
+ ctrLE.writeUInt16LE(session.cmdCtr);
3982
+ const macInput = Buffer.concat([Buffer.from([cmd]), ctrLE, session.ti, data]);
3983
+ const full = aesCmac(session.kSesAuthMac, macInput);
3984
+ const t = Buffer.alloc(8);
3985
+ for (let i = 0; i < 8; i++) t[i] = full[i * 2 + 1];
3986
+ return t;
3987
+ }
3988
+ function encryptSessionData(session, cmd, data) {
3989
+ const ctrLE = Buffer.alloc(2);
3990
+ ctrLE.writeUInt16LE(session.cmdCtr);
3991
+ const ivInput = Buffer.alloc(16, 0);
3992
+ ivInput[0] = 165;
3993
+ ivInput[1] = 90;
3994
+ session.ti.copy(ivInput, 2);
3995
+ ctrLE.copy(ivInput, 6);
3996
+ const ivC = import_crypto7.default.createCipheriv("aes-128-ecb", session.kSesAuthEnc, null);
3997
+ ivC.setAutoPadding(false);
3998
+ const iv = Buffer.concat([ivC.update(ivInput), ivC.final()]);
3999
+ const padLen = (16 - data.length % 16) % 16;
4000
+ const padded = Buffer.alloc(data.length + padLen, 0);
4001
+ data.copy(padded);
4002
+ if (padLen > 0) padded[data.length] = 128;
4003
+ return aesEncrypt(session.kSesAuthEnc, iv, padded);
4004
+ }
4005
+ async function sendCommand(reader, ins, p1, p2, data) {
4006
+ const apdu = buildApdu(ins, p1, p2, data);
4007
+ return reader.transmit(apdu, 512);
4008
+ }
4009
+ async function authenticateEV2First(reader, keyNo, key) {
4010
+ const resp1 = await sendCommand(reader, 113, 0, 0, Buffer.from([keyNo, 0]));
4011
+ const encRndB = checkSW(resp1, "AuthEV2First-part1");
4012
+ const rndB = aesDecrypt(key, ZERO16, encRndB);
4013
+ const rndA = import_crypto7.default.randomBytes(16);
4014
+ const rndBrot = rotateLeft(rndB);
4015
+ const encPart2 = aesEncrypt(key, ZERO16, Buffer.concat([rndA, rndBrot]));
4016
+ const resp2 = await sendCommand(reader, 175, 0, 0, encPart2);
4017
+ const data2 = checkSW(resp2, "AuthEV2First-part2");
4018
+ const decResp = aesDecrypt(key, ZERO16, data2);
4019
+ const ti = decResp.subarray(0, 4);
4020
+ const rndACheck = decResp.subarray(4, 20);
4021
+ if (!import_crypto7.default.timingSafeEqual(rndACheck, rotateLeft(rndA))) {
4022
+ throw new Error("Auth failed: RndA mismatch. Key may not be factory default.");
4023
+ }
4024
+ const { kEnc, kMac } = deriveAuthSessionKeys(rndA, rndB, key);
4025
+ return { ti, cmdCtr: 0, kSesAuthEnc: kEnc, kSesAuthMac: kMac };
4026
+ }
4027
+ async function writeNdef(reader, session, url) {
4028
+ const fileData = buildNdefUrl(url);
4029
+ const offset = Buffer.from([0, 0, 0]);
4030
+ const lenLE = Buffer.alloc(3);
4031
+ lenLE.writeUIntLE(fileData.length, 0, 3);
4032
+ const cmdData = Buffer.concat([Buffer.from([2]), offset, lenLE, fileData]);
4033
+ const resp = await sendCommand(reader, 141, 0, 0, cmdData);
4034
+ checkSW(resp, "WriteData");
4035
+ session.cmdCtr++;
4036
+ }
4037
+ async function changeKey2(reader, session, newKey) {
4038
+ const oldKey = ZERO16;
4039
+ const xoredKey = xor2(newKey, oldKey);
4040
+ const crc = crc32(newKey);
4041
+ const keyVer = Buffer.from([0]);
4042
+ const plain = Buffer.concat([xoredKey, keyVer, crc]);
4043
+ const padded = Buffer.alloc(32, 0);
4044
+ plain.copy(padded);
4045
+ padded[plain.length] = 128;
4046
+ const encData = encryptSessionData(session, 196, padded);
4047
+ const mac = calcSessionMac(session, 196, Buffer.concat([Buffer.from([2]), encData]));
4048
+ const cmdData = Buffer.concat([Buffer.from([2]), encData, mac]);
4049
+ const resp = await sendCommand(reader, 196, 0, 0, cmdData);
4050
+ checkSW(resp, "ChangeKey");
4051
+ session.cmdCtr++;
4052
+ }
4053
+ async function changeFileSettings(reader, session, piccOffset, macOffset, macInputOffset) {
4054
+ const fileOption = 64;
4055
+ const accessRights = Buffer.from([0, 224]);
4056
+ const sdmOptions = 193;
4057
+ const sdmAccessRights = Buffer.from([255, 34]);
4058
+ const piccOff = Buffer.alloc(3);
4059
+ piccOff.writeUIntLE(piccOffset, 0, 3);
4060
+ const macInputOff = Buffer.alloc(3);
4061
+ macInputOff.writeUIntLE(macInputOffset, 0, 3);
4062
+ const macOff = Buffer.alloc(3);
4063
+ macOff.writeUIntLE(macOffset, 0, 3);
4064
+ const settingsData = Buffer.concat([
4065
+ Buffer.from([fileOption]),
4066
+ accessRights,
4067
+ Buffer.from([sdmOptions]),
4068
+ sdmAccessRights,
4069
+ piccOff,
4070
+ macInputOff,
4071
+ macOff
4072
+ ]);
4073
+ const encSettings = encryptSessionData(session, 95, settingsData);
4074
+ const mac = calcSessionMac(session, 95, Buffer.concat([Buffer.from([2]), encSettings]));
4075
+ const cmdData = Buffer.concat([Buffer.from([2]), encSettings, mac]);
4076
+ const resp = await sendCommand(reader, 95, 0, 0, cmdData);
4077
+ checkSW(resp, "ChangeFileSettings");
4078
+ session.cmdCtr++;
4079
+ }
4080
+ var import_crypto7, ZERO16;
4081
+ var init_ntag424 = __esm({
4082
+ "src/nfc/ntag424.ts"() {
4083
+ "use strict";
4084
+ import_crypto7 = __toESM(require("crypto"), 1);
4085
+ init_aes_cmac();
4086
+ ZERO16 = Buffer.alloc(16, 0);
4087
+ }
4088
+ });
4089
+
3716
4090
  // node_modules/commander/esm.mjs
3717
4091
  var import_index = __toESM(require_commander(), 1);
3718
4092
  var {
@@ -3918,12 +4292,109 @@ function registerInit(program3) {
3918
4292
  });
3919
4293
  }
3920
4294
 
4295
+ // src/cli/commands/provision.ts
4296
+ function registerProvision(program3) {
4297
+ program3.command("provision").description("Provision an NTAG 424 DNA chip (write AES key + SUN config)").option("--cloud", "Generate key via HUITL Cloud (default)").option("--local", "Use a locally provided key").option("--key <hex>", "AES-128 key for --local mode (32 hex chars)").option("--name <name>", "Chip name/label").option("--base-url <url>", "Base URL for the tap endpoint", "https://huitlprotocol.com").action(async (opts) => {
4298
+ const isLocal = opts.local === true;
4299
+ let aesKeyHex;
4300
+ let chipId;
4301
+ if (isLocal) {
4302
+ if (!opts.key || opts.key.length !== 32 || !/^[0-9a-fA-F]+$/.test(opts.key)) {
4303
+ console.error("Error: --local requires --key with 32 hex characters");
4304
+ process.exit(1);
4305
+ }
4306
+ aesKeyHex = opts.key.toUpperCase();
4307
+ } else {
4308
+ const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_login(), login_exports));
4309
+ const apiKey = getApiKey2();
4310
+ if (!apiKey) {
4311
+ console.error("Not logged in. Run: huitl login --api-key <key>");
4312
+ console.error("Or use --local mode with --key.");
4313
+ process.exit(1);
4314
+ }
4315
+ console.log("Requesting key from HUITL Cloud...");
4316
+ const { HuitlCloudClient: HuitlCloudClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
4317
+ const client = new HuitlCloudClient2({ apiKey });
4318
+ try {
4319
+ const result = await client.provision(opts.name);
4320
+ aesKeyHex = result.aesKey;
4321
+ chipId = result.chipId;
4322
+ console.log(`Key generated (chip ID: ${chipId})`);
4323
+ } catch (err) {
4324
+ console.error("Error:", err instanceof Error ? err.message : String(err));
4325
+ process.exit(1);
4326
+ }
4327
+ }
4328
+ const aesKey = Buffer.from(aesKeyHex, "hex");
4329
+ const ndefUrl = `${opts.baseUrl}/tap?picc_data=00000000000000000000000000000000&cmac=0000000000000000`;
4330
+ console.log();
4331
+ console.log(`AES Key: ${aesKeyHex}`);
4332
+ console.log(`URL: ${ndefUrl}`);
4333
+ console.log();
4334
+ console.log("Place your NTAG 424 DNA chip on the reader...");
4335
+ console.log();
4336
+ const { waitForCard: waitForCard2 } = await Promise.resolve().then(() => (init_reader(), reader_exports));
4337
+ const {
4338
+ authenticateEV2First: authenticateEV2First2,
4339
+ writeNdef: writeNdef2,
4340
+ changeKey2: changeKey22,
4341
+ changeFileSettings: changeFileSettings2,
4342
+ calculateOffsets: calculateOffsets2
4343
+ } = await Promise.resolve().then(() => (init_ntag424(), ntag424_exports));
4344
+ const { uid, reader } = await waitForCard2();
4345
+ console.log(`Chip detected \u2014 UID: ${uid}`);
4346
+ console.log();
4347
+ try {
4348
+ console.log("Step 1/5: Authenticating with factory key...");
4349
+ const session = await authenticateEV2First2(reader, 0, Buffer.alloc(16, 0));
4350
+ console.log("Step 2/5: Writing NDEF URL...");
4351
+ await writeNdef2(reader, session, ndefUrl);
4352
+ console.log("Step 3/5: Writing AES key...");
4353
+ try {
4354
+ await changeKey22(reader, session, aesKey);
4355
+ } catch (err) {
4356
+ if (err.message.includes("911E")) {
4357
+ console.log(" Key 2 may already be set. Skipping.");
4358
+ } else throw err;
4359
+ }
4360
+ console.log("Step 4/5: Re-authenticating...");
4361
+ const session2 = await authenticateEV2First2(reader, 0, Buffer.alloc(16, 0));
4362
+ console.log("Step 5/5: Configuring SUN (Secure Unique NFC)...");
4363
+ const { piccOffset, macOffset, macInputOffset } = calculateOffsets2(ndefUrl);
4364
+ await changeFileSettings2(reader, session2, piccOffset, macOffset, macInputOffset);
4365
+ if (chipId) {
4366
+ console.log("Registering UID with HUITL Cloud...");
4367
+ const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_login(), login_exports));
4368
+ const { HuitlCloudClient: HuitlCloudClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
4369
+ const client = new HuitlCloudClient2({ apiKey: getApiKey2() });
4370
+ await client.registerUid(chipId, uid);
4371
+ }
4372
+ console.log();
4373
+ console.log("Chip provisioned successfully!");
4374
+ console.log(` UID: ${uid}`);
4375
+ console.log(` AES Key: ${aesKeyHex}`);
4376
+ console.log(` Tap URL: ${opts.baseUrl}/tap`);
4377
+ console.log();
4378
+ console.log("Tap the chip with your phone to test it.");
4379
+ process.exit(0);
4380
+ } catch (err) {
4381
+ console.error();
4382
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
4383
+ console.error("Make sure you're using an NTAG 424 DNA chip with factory default keys.");
4384
+ process.exit(1);
4385
+ }
4386
+ });
4387
+ }
4388
+
3921
4389
  // src/cli/index.ts
4390
+ init_login();
3922
4391
  var program2 = new Command();
3923
- program2.name("huitl").description("HUITL Protocol CLI \u2014 NTAG 424 DNA verification tools").version("0.1.0");
4392
+ program2.name("huitl").description("HUITL Protocol CLI \u2014 NTAG 424 DNA verification tools").version("0.2.0");
3924
4393
  registerVerify(program2);
3925
4394
  registerKeygen(program2);
3926
4395
  registerDev(program2);
3927
4396
  registerInit(program2);
4397
+ registerProvision(program2);
4398
+ registerLogin(program2);
3928
4399
  program2.parse();
3929
4400
  //# sourceMappingURL=cli.cjs.map