@attest-it/cli 0.6.1 → 0.8.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.
@@ -6,7 +6,7 @@ import * as path from 'path';
6
6
  import { join, dirname } from 'path';
7
7
  import { detectTheme } from 'chromaterm';
8
8
  import { input, select, confirm, checkbox } from '@inquirer/prompts';
9
- import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, YubiKeyProvider, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, findConfigPath, loadPreferences, savePreferences, findAttestation, setAttestItHomeDir } from '@attest-it/core';
9
+ import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, YubiKeyProvider, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, savePublicKey, findConfigPath, loadPreferences, savePreferences, findAttestation, setAttestItHomeDir, getDefaultYubiKeyEncryptedKeyPath } from '@attest-it/core';
10
10
  import tabtab2 from '@pnpm/tabtab';
11
11
  import { spawn } from 'child_process';
12
12
  import * as os from 'os';
@@ -16,7 +16,7 @@ import { useState, useEffect } from 'react';
16
16
  import { render, useApp, Box, Text, useInput } from 'ink';
17
17
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
18
18
  import { mkdir, writeFile, unlink, readFile } from 'fs/promises';
19
- import { Spinner, Select, TextInput } from '@inkjs/ui';
19
+ import { Spinner, Select, PasswordInput, TextInput } from '@inkjs/ui';
20
20
  import { stringify } from 'yaml';
21
21
  import { fileURLToPath } from 'url';
22
22
 
@@ -337,7 +337,7 @@ async function runStatus(gates, options) {
337
337
  process.exit(ExitCode.CONFIG_ERROR);
338
338
  }
339
339
  const projectRoot = process.cwd();
340
- const sealsFile = readSealsSync(projectRoot);
340
+ const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
341
341
  const gatesToCheck = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
342
342
  for (const gateId of gatesToCheck) {
343
343
  if (!attestItConfig.gates[gateId]) {
@@ -989,12 +989,24 @@ async function getAllSuiteStatuses(config) {
989
989
  const attestations = attestationsFile?.attestations ?? [];
990
990
  const results = [];
991
991
  for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
992
- if (!suiteConfig.packages) {
992
+ let packages;
993
+ let ignore;
994
+ if (suiteConfig.gate && config.gates) {
995
+ const gateConfig = config.gates[suiteConfig.gate];
996
+ if (gateConfig) {
997
+ packages = gateConfig.fingerprint.paths;
998
+ ignore = gateConfig.fingerprint.exclude;
999
+ }
1000
+ } else if (suiteConfig.packages) {
1001
+ packages = suiteConfig.packages;
1002
+ ignore = suiteConfig.ignore;
1003
+ }
1004
+ if (!packages || packages.length === 0) {
993
1005
  continue;
994
1006
  }
995
1007
  const fingerprintResult = await computeFingerprint({
996
- packages: suiteConfig.packages,
997
- ...suiteConfig.ignore && { ignore: suiteConfig.ignore }
1008
+ packages,
1009
+ ...ignore && { ignore }
998
1010
  });
999
1011
  const attestation = findAttestation(
1000
1012
  { schemaVersion: "1", attestations, signature: "" },
@@ -1559,18 +1571,19 @@ async function promptForSeal(suiteName, gateId, config) {
1559
1571
  const fs4 = await import('fs/promises');
1560
1572
  const privateKeyPem = await fs4.readFile(keyResult.keyPath, "utf8");
1561
1573
  await keyResult.cleanup();
1574
+ const identitySlug = localConfig.activeIdentity;
1562
1575
  const seal = createSeal({
1563
1576
  gateId,
1564
1577
  fingerprint: gateFingerprint.fingerprint,
1565
- sealedBy: identity.name,
1578
+ sealedBy: identitySlug,
1566
1579
  privateKey: privateKeyPem
1567
1580
  });
1568
1581
  const projectRoot = process.cwd();
1569
- const sealsFile = readSealsSync(projectRoot);
1582
+ const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
1570
1583
  sealsFile.seals[gateId] = seal;
1571
- writeSealsSync(projectRoot, sealsFile);
1584
+ writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
1572
1585
  success(`Seal created for gate '${gateId}'`);
1573
- log(` Sealed by: ${identity.name}`);
1586
+ log(` Sealed by: ${identitySlug} (${identity.name})`);
1574
1587
  log(` Timestamp: ${seal.timestamp}`);
1575
1588
  } catch (err) {
1576
1589
  if (err instanceof Error) {
@@ -1638,18 +1651,31 @@ function getKeyRefFromIdentity(identity) {
1638
1651
  }
1639
1652
  }
1640
1653
  }
1654
+ var MIN_PASSPHRASE_LENGTH = 8;
1641
1655
  function KeygenInteractive(props) {
1642
- const { onComplete, onError } = props;
1656
+ const { onComplete, onCancel, onError } = props;
1643
1657
  const [step, setStep] = useState("checking-providers");
1644
1658
  const [opAvailable, setOpAvailable] = useState(false);
1645
1659
  const [keychainAvailable, setKeychainAvailable] = useState(false);
1660
+ const [yubiKeyAvailable, setYubiKeyAvailable] = useState(false);
1646
1661
  const [accounts, setAccounts] = useState([]);
1647
1662
  const [vaults, setVaults] = useState([]);
1663
+ const [yubiKeyDevices, setYubiKeyDevices] = useState([]);
1648
1664
  const [_selectedProvider, setSelectedProvider] = useState();
1649
1665
  const [selectedAccount, setSelectedAccount] = useState();
1650
1666
  const [selectedVault, setSelectedVault] = useState();
1651
1667
  const [itemName, setItemName] = useState("attest-it-private-key");
1652
1668
  const [keychainItemName, setKeychainItemName] = useState("attest-it-private-key");
1669
+ const [selectedYubiKeySerial, setSelectedYubiKeySerial] = useState();
1670
+ const [selectedYubiKeySlot, setSelectedYubiKeySlot] = useState(2);
1671
+ const [slot1Configured, setSlot1Configured] = useState(false);
1672
+ const [slot2Configured, setSlot2Configured] = useState(false);
1673
+ const [encryptionPassphrase, setEncryptionPassphrase] = useState();
1674
+ useInput((_input, key) => {
1675
+ if (key.escape) {
1676
+ onCancel();
1677
+ }
1678
+ });
1653
1679
  useEffect(() => {
1654
1680
  const checkProviders = async () => {
1655
1681
  try {
@@ -1664,6 +1690,19 @@ function KeygenInteractive(props) {
1664
1690
  }
1665
1691
  const isKeychainAvailable = MacOSKeychainKeyProvider.isAvailable();
1666
1692
  setKeychainAvailable(isKeychainAvailable);
1693
+ try {
1694
+ const isInstalled = await YubiKeyProvider.isInstalled();
1695
+ if (isInstalled) {
1696
+ const isConnected = await YubiKeyProvider.isConnected();
1697
+ if (isConnected) {
1698
+ const devices = await YubiKeyProvider.listDevices();
1699
+ setYubiKeyDevices(devices);
1700
+ setYubiKeyAvailable(devices.length > 0);
1701
+ }
1702
+ }
1703
+ } catch {
1704
+ setYubiKeyAvailable(false);
1705
+ }
1667
1706
  setStep("select-provider");
1668
1707
  };
1669
1708
  void checkProviders();
@@ -1681,10 +1720,31 @@ function KeygenInteractive(props) {
1681
1720
  void fetchVaults();
1682
1721
  }
1683
1722
  }, [step, selectedAccount, onError]);
1723
+ const checkYubiKeySlots = async (serial) => {
1724
+ try {
1725
+ const slot1 = await YubiKeyProvider.isChallengeResponseConfigured(1, serial);
1726
+ const slot2 = await YubiKeyProvider.isChallengeResponseConfigured(2, serial);
1727
+ setSlot1Configured(slot1);
1728
+ setSlot2Configured(slot2);
1729
+ if (slot1 && slot2) {
1730
+ setStep("select-yubikey-slot");
1731
+ } else if (slot2) {
1732
+ setSelectedYubiKeySlot(2);
1733
+ void generateKeys("yubikey");
1734
+ } else if (slot1) {
1735
+ setSelectedYubiKeySlot(1);
1736
+ void generateKeys("yubikey");
1737
+ } else {
1738
+ setStep("yubikey-offer-setup");
1739
+ }
1740
+ } catch (err) {
1741
+ onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
1742
+ }
1743
+ };
1684
1744
  const handleProviderSelect = (value) => {
1685
1745
  if (value === "filesystem") {
1686
1746
  setSelectedProvider("filesystem");
1687
- void generateKeys("filesystem");
1747
+ setStep("select-filesystem-encryption");
1688
1748
  } else if (value === "1password") {
1689
1749
  setSelectedProvider("1password");
1690
1750
  if (accounts.length === 1 && accounts[0]) {
@@ -1696,6 +1756,14 @@ function KeygenInteractive(props) {
1696
1756
  } else if (value === "macos-keychain") {
1697
1757
  setSelectedProvider("macos-keychain");
1698
1758
  setStep("enter-keychain-item-name");
1759
+ } else if (value === "yubikey") {
1760
+ setSelectedProvider("yubikey");
1761
+ if (yubiKeyDevices.length > 1) {
1762
+ setStep("select-yubikey-device");
1763
+ } else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
1764
+ setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
1765
+ void checkYubiKeySlots(yubiKeyDevices[0].serial);
1766
+ }
1699
1767
  }
1700
1768
  };
1701
1769
  const handleAccountSelect = (value) => {
@@ -1714,23 +1782,98 @@ function KeygenInteractive(props) {
1714
1782
  setKeychainItemName(value);
1715
1783
  void generateKeys("macos-keychain");
1716
1784
  };
1785
+ const handleYubiKeyDeviceSelect = (value) => {
1786
+ setSelectedYubiKeySerial(value);
1787
+ void checkYubiKeySlots(value);
1788
+ };
1789
+ const handleYubiKeySlotSelect = (value) => {
1790
+ const slot = value === "1" ? 1 : 2;
1791
+ setSelectedYubiKeySlot(slot);
1792
+ void generateKeys("yubikey");
1793
+ };
1794
+ const handleYubiKeySetupConfirm = (value) => {
1795
+ if (value === "yes") {
1796
+ void setupYubiKeySlot();
1797
+ } else {
1798
+ onError(new Error("YubiKey setup cancelled"));
1799
+ }
1800
+ };
1801
+ const handleEncryptionMethodSelect = (value) => {
1802
+ if (value === "passphrase") {
1803
+ setStep("enter-encryption-passphrase");
1804
+ } else {
1805
+ setEncryptionPassphrase(void 0);
1806
+ void generateKeys("filesystem");
1807
+ }
1808
+ };
1809
+ const handleEncryptionPassphrase = (value) => {
1810
+ if (value.length < MIN_PASSPHRASE_LENGTH) {
1811
+ onError(new Error(`Passphrase must be at least ${String(MIN_PASSPHRASE_LENGTH)} characters`));
1812
+ return;
1813
+ }
1814
+ setEncryptionPassphrase(value);
1815
+ setStep("confirm-encryption-passphrase");
1816
+ };
1817
+ const handleConfirmPassphrase = (value) => {
1818
+ if (value !== encryptionPassphrase) {
1819
+ onError(new Error("Passphrases do not match. Please try again."));
1820
+ setEncryptionPassphrase(void 0);
1821
+ setStep("enter-encryption-passphrase");
1822
+ return;
1823
+ }
1824
+ void generateKeys("filesystem");
1825
+ };
1826
+ const setupYubiKeySlot = async () => {
1827
+ setStep("yubikey-configuring");
1828
+ try {
1829
+ const { spawn: spawn3 } = await import('child_process');
1830
+ const args = ["otp", "chalresp", "--touch", "--generate", "2"];
1831
+ if (selectedYubiKeySerial) {
1832
+ args.unshift("--device", selectedYubiKeySerial);
1833
+ }
1834
+ await new Promise((resolve2, reject) => {
1835
+ const proc = spawn3("ykman", args, { stdio: "inherit" });
1836
+ proc.on("close", (code) => {
1837
+ if (code === 0) {
1838
+ resolve2();
1839
+ } else {
1840
+ reject(new Error(`ykman exited with code ${String(code)}`));
1841
+ }
1842
+ });
1843
+ proc.on("error", reject);
1844
+ });
1845
+ setSelectedYubiKeySlot(2);
1846
+ void generateKeys("yubikey");
1847
+ } catch (err) {
1848
+ onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
1849
+ }
1850
+ };
1717
1851
  const generateKeys = async (provider) => {
1718
1852
  setStep("generating");
1719
1853
  try {
1720
1854
  const publicKeyPath = props.publicKeyPath ?? getDefaultPublicKeyPath();
1721
1855
  if (provider === "filesystem") {
1722
1856
  const fsProvider = new FilesystemKeyProvider();
1723
- const genOptions = { publicKeyPath };
1857
+ const genOptions = {
1858
+ publicKeyPath
1859
+ };
1724
1860
  if (props.force !== void 0) {
1725
1861
  genOptions.force = props.force;
1726
1862
  }
1863
+ if (encryptionPassphrase !== void 0) {
1864
+ genOptions.passphrase = encryptionPassphrase;
1865
+ }
1727
1866
  const result = await fsProvider.generateKeyPair(genOptions);
1728
- onComplete({
1867
+ const completionResult = {
1729
1868
  provider: "filesystem",
1730
1869
  publicKeyPath: result.publicKeyPath,
1731
1870
  privateKeyRef: result.privateKeyRef,
1732
1871
  storageDescription: result.storageDescription
1733
- });
1872
+ };
1873
+ if (result.encrypted) {
1874
+ completionResult.encrypted = result.encrypted;
1875
+ }
1876
+ onComplete(completionResult);
1734
1877
  } else if (provider === "1password") {
1735
1878
  if (!selectedVault || !itemName) {
1736
1879
  throw new Error("Vault and item name are required for 1Password");
@@ -1764,7 +1907,7 @@ function KeygenInteractive(props) {
1764
1907
  completionResult.account = selectedAccount;
1765
1908
  }
1766
1909
  onComplete(completionResult);
1767
- } else {
1910
+ } else if (provider === "macos-keychain") {
1768
1911
  if (!keychainItemName) {
1769
1912
  throw new Error("Item name is required for macOS Keychain");
1770
1913
  }
@@ -1783,10 +1926,39 @@ function KeygenInteractive(props) {
1783
1926
  storageDescription: result.storageDescription,
1784
1927
  itemName: keychainItemName
1785
1928
  });
1929
+ } else {
1930
+ const encryptedKeyPath = getDefaultYubiKeyEncryptedKeyPath();
1931
+ const providerOptions = {
1932
+ encryptedKeyPath,
1933
+ slot: selectedYubiKeySlot
1934
+ };
1935
+ if (selectedYubiKeySerial !== void 0) {
1936
+ providerOptions.serial = selectedYubiKeySerial;
1937
+ }
1938
+ const ykProvider = new YubiKeyProvider(providerOptions);
1939
+ const genOptions = { publicKeyPath };
1940
+ if (props.force !== void 0) {
1941
+ genOptions.force = props.force;
1942
+ }
1943
+ const result = await ykProvider.generateKeyPair(genOptions);
1944
+ const completionResult = {
1945
+ provider: "yubikey",
1946
+ publicKeyPath: result.publicKeyPath,
1947
+ privateKeyRef: result.privateKeyRef,
1948
+ storageDescription: result.storageDescription,
1949
+ slot: selectedYubiKeySlot,
1950
+ encryptedKeyPath
1951
+ };
1952
+ if (selectedYubiKeySerial !== void 0) {
1953
+ completionResult.serial = selectedYubiKeySerial;
1954
+ }
1955
+ onComplete(completionResult);
1786
1956
  }
1787
1957
  setStep("done");
1788
1958
  } catch (err) {
1789
1959
  onError(err instanceof Error ? err : new Error("Key generation failed"));
1960
+ } finally {
1961
+ setEncryptionPassphrase(void 0);
1790
1962
  }
1791
1963
  };
1792
1964
  if (step === "checking-providers") {
@@ -1808,6 +1980,12 @@ function KeygenInteractive(props) {
1808
1980
  value: "macos-keychain"
1809
1981
  });
1810
1982
  }
1983
+ if (yubiKeyAvailable) {
1984
+ options.push({
1985
+ label: "YubiKey (hardware security key)",
1986
+ value: "yubikey"
1987
+ });
1988
+ }
1811
1989
  if (opAvailable) {
1812
1990
  options.push({
1813
1991
  label: "1Password (requires op CLI)",
@@ -1820,9 +1998,42 @@ function KeygenInteractive(props) {
1820
1998
  /* @__PURE__ */ jsx(Select, { options, onChange: handleProviderSelect })
1821
1999
  ] });
1822
2000
  }
2001
+ if (step === "select-filesystem-encryption") {
2002
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2003
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Would you like to encrypt your private key with a passphrase?" }),
2004
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "A passphrase adds extra security but must be entered each time you sign." }),
2005
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2006
+ /* @__PURE__ */ jsx(
2007
+ Select,
2008
+ {
2009
+ options: [
2010
+ { label: "No encryption (key protected by file permissions only)", value: "none" },
2011
+ { label: "Passphrase protection (AES-256 encryption)", value: "passphrase" }
2012
+ ],
2013
+ onChange: handleEncryptionMethodSelect
2014
+ }
2015
+ )
2016
+ ] });
2017
+ }
2018
+ if (step === "enter-encryption-passphrase") {
2019
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2020
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Enter a passphrase to encrypt your private key:" }),
2021
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `(Minimum ${String(MIN_PASSPHRASE_LENGTH)} characters. You will need this passphrase each time you sign.)` }),
2022
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2023
+ /* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleEncryptionPassphrase })
2024
+ ] });
2025
+ }
2026
+ if (step === "confirm-encryption-passphrase") {
2027
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2028
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Confirm your passphrase:" }),
2029
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(Enter the same passphrase again to confirm.)" }),
2030
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2031
+ /* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleConfirmPassphrase })
2032
+ ] });
2033
+ }
1823
2034
  if (step === "select-account") {
1824
2035
  const options = accounts.map((account) => ({
1825
- label: account.email,
2036
+ label: account.name ? `${account.name} (${account.email})` : account.email,
1826
2037
  value: account.user_uuid
1827
2038
  }));
1828
2039
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -1864,6 +2075,65 @@ function KeygenInteractive(props) {
1864
2075
  /* @__PURE__ */ jsx(TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
1865
2076
  ] });
1866
2077
  }
2078
+ if (step === "select-yubikey-device") {
2079
+ const options = yubiKeyDevices.map((device) => ({
2080
+ label: `${device.type} (Serial: ${device.serial})`,
2081
+ value: device.serial
2082
+ }));
2083
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2084
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey device:" }),
2085
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2086
+ /* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeyDeviceSelect })
2087
+ ] });
2088
+ }
2089
+ if (step === "select-yubikey-slot") {
2090
+ const options = [];
2091
+ if (slot2Configured) {
2092
+ options.push({
2093
+ label: "Slot 2 (recommended)",
2094
+ value: "2"
2095
+ });
2096
+ }
2097
+ if (slot1Configured) {
2098
+ options.push({
2099
+ label: "Slot 1",
2100
+ value: "1"
2101
+ });
2102
+ }
2103
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2104
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
2105
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2106
+ /* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeySlotSelect })
2107
+ ] });
2108
+ }
2109
+ if (step === "yubikey-offer-setup") {
2110
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2111
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
2112
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2113
+ /* @__PURE__ */ jsx(Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
2114
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
2115
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
2116
+ /* @__PURE__ */ jsx(
2117
+ Select,
2118
+ {
2119
+ options: [
2120
+ { label: "Yes, configure my YubiKey", value: "yes" },
2121
+ { label: "No, cancel", value: "no" }
2122
+ ],
2123
+ onChange: handleYubiKeySetupConfirm
2124
+ }
2125
+ )
2126
+ ] });
2127
+ }
2128
+ if (step === "yubikey-configuring") {
2129
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2130
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2131
+ /* @__PURE__ */ jsx(Spinner, {}),
2132
+ /* @__PURE__ */ jsx(Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
2133
+ ] }),
2134
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
2135
+ ] });
2136
+ }
1867
2137
  if (step === "generating") {
1868
2138
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1869
2139
  /* @__PURE__ */ jsx(Spinner, {}),
@@ -2176,7 +2446,7 @@ async function runVerify(gates, options) {
2176
2446
  process.exit(ExitCode.CONFIG_ERROR);
2177
2447
  }
2178
2448
  const projectRoot = process.cwd();
2179
- const sealsFile = readSealsSync(projectRoot);
2449
+ const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
2180
2450
  const gatesToVerify = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
2181
2451
  for (const gateId of gatesToVerify) {
2182
2452
  if (!attestItConfig.gates[gateId]) {
@@ -2332,7 +2602,7 @@ async function runSeal(gates, options) {
2332
2602
  process.exit(ExitCode.CONFIG_ERROR);
2333
2603
  }
2334
2604
  const projectRoot = process.cwd();
2335
- const sealsFile = readSealsSync(projectRoot);
2605
+ const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
2336
2606
  const gatesToSeal = gates.length > 0 ? gates : getAllGateIds(attestItConfig);
2337
2607
  for (const gateId of gatesToSeal) {
2338
2608
  if (!attestItConfig.gates[gateId]) {
@@ -2345,9 +2615,17 @@ async function runSeal(gates, options) {
2345
2615
  skipped: [],
2346
2616
  failed: []
2347
2617
  };
2618
+ const identitySlug = localConfig.activeIdentity;
2348
2619
  for (const gateId of gatesToSeal) {
2349
2620
  try {
2350
- const result = await processSingleGate(gateId, attestItConfig, identity, sealsFile, options);
2621
+ const result = await processSingleGate(
2622
+ gateId,
2623
+ attestItConfig,
2624
+ identity,
2625
+ identitySlug,
2626
+ sealsFile,
2627
+ options
2628
+ );
2351
2629
  if (result.sealed) {
2352
2630
  summary.sealed.push(gateId);
2353
2631
  } else if (result.skipped) {
@@ -2359,7 +2637,7 @@ async function runSeal(gates, options) {
2359
2637
  }
2360
2638
  }
2361
2639
  if (!options.dryRun && summary.sealed.length > 0) {
2362
- writeSealsSync(projectRoot, sealsFile);
2640
+ writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
2363
2641
  }
2364
2642
  displaySummary(summary, options.dryRun);
2365
2643
  if (summary.failed.length > 0) {
@@ -2378,7 +2656,7 @@ async function runSeal(gates, options) {
2378
2656
  process.exit(ExitCode.CONFIG_ERROR);
2379
2657
  }
2380
2658
  }
2381
- async function processSingleGate(gateId, config, identity, sealsFile, options) {
2659
+ async function processSingleGate(gateId, config, identity, identitySlug, sealsFile, options) {
2382
2660
  verbose(`Processing gate: ${gateId}`);
2383
2661
  const gate = getGate(config, gateId);
2384
2662
  if (!gate) {
@@ -2418,12 +2696,12 @@ async function processSingleGate(gateId, config, identity, sealsFile, options) {
2418
2696
  const seal = createSeal({
2419
2697
  gateId,
2420
2698
  fingerprint: fingerprintResult.fingerprint,
2421
- sealedBy: identity.name,
2699
+ sealedBy: identitySlug,
2422
2700
  privateKey: privateKeyPem
2423
2701
  });
2424
2702
  sealsFile.seals[gateId] = seal;
2425
2703
  log(` Sealed gate: ${gateId}`);
2426
- verbose(` Sealed by: ${identity.name}`);
2704
+ verbose(` Sealed by: ${identitySlug} (${identity.name})`);
2427
2705
  verbose(` Timestamp: ${seal.timestamp}`);
2428
2706
  return { sealed: true, skipped: false };
2429
2707
  }
@@ -2948,6 +3226,7 @@ async function runCreate() {
2948
3226
  };
2949
3227
  }
2950
3228
  await saveLocalConfig(newConfig);
3229
+ const publicKeyResult = await savePublicKey(slug, keyPair.publicKey);
2951
3230
  log("");
2952
3231
  success("Identity created successfully");
2953
3232
  log("");
@@ -2962,6 +3241,12 @@ async function runCreate() {
2962
3241
  log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
2963
3242
  log(` Private Key: ${keyStorageDescription}`);
2964
3243
  log("");
3244
+ log(theme3.blue.bold()("Public key saved to:"));
3245
+ log(` ${publicKeyResult.homePath}`);
3246
+ if (publicKeyResult.projectPath) {
3247
+ log(` ${publicKeyResult.projectPath}`);
3248
+ }
3249
+ log("");
2965
3250
  if (!existingConfig) {
2966
3251
  success(`Set as active identity`);
2967
3252
  log("");
@@ -3853,7 +4138,7 @@ async function runRemove2(slug, options) {
3853
4138
  const projectRoot = process.cwd();
3854
4139
  let sealsFile;
3855
4140
  try {
3856
- sealsFile = readSealsSync(projectRoot);
4141
+ sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
3857
4142
  } catch {
3858
4143
  sealsFile = { version: 1, seals: {} };
3859
4144
  }