@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.
package/dist/index.cjs CHANGED
@@ -363,7 +363,7 @@ async function runStatus(gates, options) {
363
363
  process.exit(ExitCode.CONFIG_ERROR);
364
364
  }
365
365
  const projectRoot = process.cwd();
366
- const sealsFile = core.readSealsSync(projectRoot);
366
+ const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
367
367
  const gatesToCheck = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
368
368
  for (const gateId of gatesToCheck) {
369
369
  if (!attestItConfig.gates[gateId]) {
@@ -1015,12 +1015,24 @@ async function getAllSuiteStatuses(config) {
1015
1015
  const attestations = attestationsFile?.attestations ?? [];
1016
1016
  const results = [];
1017
1017
  for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
1018
- if (!suiteConfig.packages) {
1018
+ let packages;
1019
+ let ignore;
1020
+ if (suiteConfig.gate && config.gates) {
1021
+ const gateConfig = config.gates[suiteConfig.gate];
1022
+ if (gateConfig) {
1023
+ packages = gateConfig.fingerprint.paths;
1024
+ ignore = gateConfig.fingerprint.exclude;
1025
+ }
1026
+ } else if (suiteConfig.packages) {
1027
+ packages = suiteConfig.packages;
1028
+ ignore = suiteConfig.ignore;
1029
+ }
1030
+ if (!packages || packages.length === 0) {
1019
1031
  continue;
1020
1032
  }
1021
1033
  const fingerprintResult = await core.computeFingerprint({
1022
- packages: suiteConfig.packages,
1023
- ...suiteConfig.ignore && { ignore: suiteConfig.ignore }
1034
+ packages,
1035
+ ...ignore && { ignore }
1024
1036
  });
1025
1037
  const attestation = core.findAttestation(
1026
1038
  { schemaVersion: "1", attestations, signature: "" },
@@ -1585,18 +1597,19 @@ async function promptForSeal(suiteName, gateId, config) {
1585
1597
  const fs4 = await import('fs/promises');
1586
1598
  const privateKeyPem = await fs4.readFile(keyResult.keyPath, "utf8");
1587
1599
  await keyResult.cleanup();
1600
+ const identitySlug = localConfig.activeIdentity;
1588
1601
  const seal = core.createSeal({
1589
1602
  gateId,
1590
1603
  fingerprint: gateFingerprint.fingerprint,
1591
- sealedBy: identity.name,
1604
+ sealedBy: identitySlug,
1592
1605
  privateKey: privateKeyPem
1593
1606
  });
1594
1607
  const projectRoot = process.cwd();
1595
- const sealsFile = core.readSealsSync(projectRoot);
1608
+ const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
1596
1609
  sealsFile.seals[gateId] = seal;
1597
- core.writeSealsSync(projectRoot, sealsFile);
1610
+ core.writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
1598
1611
  success(`Seal created for gate '${gateId}'`);
1599
- log(` Sealed by: ${identity.name}`);
1612
+ log(` Sealed by: ${identitySlug} (${identity.name})`);
1600
1613
  log(` Timestamp: ${seal.timestamp}`);
1601
1614
  } catch (err) {
1602
1615
  if (err instanceof Error) {
@@ -1664,18 +1677,31 @@ function getKeyRefFromIdentity(identity) {
1664
1677
  }
1665
1678
  }
1666
1679
  }
1680
+ var MIN_PASSPHRASE_LENGTH = 8;
1667
1681
  function KeygenInteractive(props) {
1668
- const { onComplete, onError } = props;
1682
+ const { onComplete, onCancel, onError } = props;
1669
1683
  const [step, setStep] = React7.useState("checking-providers");
1670
1684
  const [opAvailable, setOpAvailable] = React7.useState(false);
1671
1685
  const [keychainAvailable, setKeychainAvailable] = React7.useState(false);
1686
+ const [yubiKeyAvailable, setYubiKeyAvailable] = React7.useState(false);
1672
1687
  const [accounts, setAccounts] = React7.useState([]);
1673
1688
  const [vaults, setVaults] = React7.useState([]);
1689
+ const [yubiKeyDevices, setYubiKeyDevices] = React7.useState([]);
1674
1690
  const [_selectedProvider, setSelectedProvider] = React7.useState();
1675
1691
  const [selectedAccount, setSelectedAccount] = React7.useState();
1676
1692
  const [selectedVault, setSelectedVault] = React7.useState();
1677
1693
  const [itemName, setItemName] = React7.useState("attest-it-private-key");
1678
1694
  const [keychainItemName, setKeychainItemName] = React7.useState("attest-it-private-key");
1695
+ const [selectedYubiKeySerial, setSelectedYubiKeySerial] = React7.useState();
1696
+ const [selectedYubiKeySlot, setSelectedYubiKeySlot] = React7.useState(2);
1697
+ const [slot1Configured, setSlot1Configured] = React7.useState(false);
1698
+ const [slot2Configured, setSlot2Configured] = React7.useState(false);
1699
+ const [encryptionPassphrase, setEncryptionPassphrase] = React7.useState();
1700
+ ink.useInput((_input, key) => {
1701
+ if (key.escape) {
1702
+ onCancel();
1703
+ }
1704
+ });
1679
1705
  React7.useEffect(() => {
1680
1706
  const checkProviders = async () => {
1681
1707
  try {
@@ -1690,6 +1716,19 @@ function KeygenInteractive(props) {
1690
1716
  }
1691
1717
  const isKeychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
1692
1718
  setKeychainAvailable(isKeychainAvailable);
1719
+ try {
1720
+ const isInstalled = await core.YubiKeyProvider.isInstalled();
1721
+ if (isInstalled) {
1722
+ const isConnected = await core.YubiKeyProvider.isConnected();
1723
+ if (isConnected) {
1724
+ const devices = await core.YubiKeyProvider.listDevices();
1725
+ setYubiKeyDevices(devices);
1726
+ setYubiKeyAvailable(devices.length > 0);
1727
+ }
1728
+ }
1729
+ } catch {
1730
+ setYubiKeyAvailable(false);
1731
+ }
1693
1732
  setStep("select-provider");
1694
1733
  };
1695
1734
  void checkProviders();
@@ -1707,10 +1746,31 @@ function KeygenInteractive(props) {
1707
1746
  void fetchVaults();
1708
1747
  }
1709
1748
  }, [step, selectedAccount, onError]);
1749
+ const checkYubiKeySlots = async (serial) => {
1750
+ try {
1751
+ const slot1 = await core.YubiKeyProvider.isChallengeResponseConfigured(1, serial);
1752
+ const slot2 = await core.YubiKeyProvider.isChallengeResponseConfigured(2, serial);
1753
+ setSlot1Configured(slot1);
1754
+ setSlot2Configured(slot2);
1755
+ if (slot1 && slot2) {
1756
+ setStep("select-yubikey-slot");
1757
+ } else if (slot2) {
1758
+ setSelectedYubiKeySlot(2);
1759
+ void generateKeys("yubikey");
1760
+ } else if (slot1) {
1761
+ setSelectedYubiKeySlot(1);
1762
+ void generateKeys("yubikey");
1763
+ } else {
1764
+ setStep("yubikey-offer-setup");
1765
+ }
1766
+ } catch (err) {
1767
+ onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
1768
+ }
1769
+ };
1710
1770
  const handleProviderSelect = (value) => {
1711
1771
  if (value === "filesystem") {
1712
1772
  setSelectedProvider("filesystem");
1713
- void generateKeys("filesystem");
1773
+ setStep("select-filesystem-encryption");
1714
1774
  } else if (value === "1password") {
1715
1775
  setSelectedProvider("1password");
1716
1776
  if (accounts.length === 1 && accounts[0]) {
@@ -1722,6 +1782,14 @@ function KeygenInteractive(props) {
1722
1782
  } else if (value === "macos-keychain") {
1723
1783
  setSelectedProvider("macos-keychain");
1724
1784
  setStep("enter-keychain-item-name");
1785
+ } else if (value === "yubikey") {
1786
+ setSelectedProvider("yubikey");
1787
+ if (yubiKeyDevices.length > 1) {
1788
+ setStep("select-yubikey-device");
1789
+ } else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
1790
+ setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
1791
+ void checkYubiKeySlots(yubiKeyDevices[0].serial);
1792
+ }
1725
1793
  }
1726
1794
  };
1727
1795
  const handleAccountSelect = (value) => {
@@ -1740,23 +1808,98 @@ function KeygenInteractive(props) {
1740
1808
  setKeychainItemName(value);
1741
1809
  void generateKeys("macos-keychain");
1742
1810
  };
1811
+ const handleYubiKeyDeviceSelect = (value) => {
1812
+ setSelectedYubiKeySerial(value);
1813
+ void checkYubiKeySlots(value);
1814
+ };
1815
+ const handleYubiKeySlotSelect = (value) => {
1816
+ const slot = value === "1" ? 1 : 2;
1817
+ setSelectedYubiKeySlot(slot);
1818
+ void generateKeys("yubikey");
1819
+ };
1820
+ const handleYubiKeySetupConfirm = (value) => {
1821
+ if (value === "yes") {
1822
+ void setupYubiKeySlot();
1823
+ } else {
1824
+ onError(new Error("YubiKey setup cancelled"));
1825
+ }
1826
+ };
1827
+ const handleEncryptionMethodSelect = (value) => {
1828
+ if (value === "passphrase") {
1829
+ setStep("enter-encryption-passphrase");
1830
+ } else {
1831
+ setEncryptionPassphrase(void 0);
1832
+ void generateKeys("filesystem");
1833
+ }
1834
+ };
1835
+ const handleEncryptionPassphrase = (value) => {
1836
+ if (value.length < MIN_PASSPHRASE_LENGTH) {
1837
+ onError(new Error(`Passphrase must be at least ${String(MIN_PASSPHRASE_LENGTH)} characters`));
1838
+ return;
1839
+ }
1840
+ setEncryptionPassphrase(value);
1841
+ setStep("confirm-encryption-passphrase");
1842
+ };
1843
+ const handleConfirmPassphrase = (value) => {
1844
+ if (value !== encryptionPassphrase) {
1845
+ onError(new Error("Passphrases do not match. Please try again."));
1846
+ setEncryptionPassphrase(void 0);
1847
+ setStep("enter-encryption-passphrase");
1848
+ return;
1849
+ }
1850
+ void generateKeys("filesystem");
1851
+ };
1852
+ const setupYubiKeySlot = async () => {
1853
+ setStep("yubikey-configuring");
1854
+ try {
1855
+ const { spawn: spawn3 } = await import('child_process');
1856
+ const args = ["otp", "chalresp", "--touch", "--generate", "2"];
1857
+ if (selectedYubiKeySerial) {
1858
+ args.unshift("--device", selectedYubiKeySerial);
1859
+ }
1860
+ await new Promise((resolve2, reject) => {
1861
+ const proc = spawn3("ykman", args, { stdio: "inherit" });
1862
+ proc.on("close", (code) => {
1863
+ if (code === 0) {
1864
+ resolve2();
1865
+ } else {
1866
+ reject(new Error(`ykman exited with code ${String(code)}`));
1867
+ }
1868
+ });
1869
+ proc.on("error", reject);
1870
+ });
1871
+ setSelectedYubiKeySlot(2);
1872
+ void generateKeys("yubikey");
1873
+ } catch (err) {
1874
+ onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
1875
+ }
1876
+ };
1743
1877
  const generateKeys = async (provider) => {
1744
1878
  setStep("generating");
1745
1879
  try {
1746
1880
  const publicKeyPath = props.publicKeyPath ?? core.getDefaultPublicKeyPath();
1747
1881
  if (provider === "filesystem") {
1748
1882
  const fsProvider = new core.FilesystemKeyProvider();
1749
- const genOptions = { publicKeyPath };
1883
+ const genOptions = {
1884
+ publicKeyPath
1885
+ };
1750
1886
  if (props.force !== void 0) {
1751
1887
  genOptions.force = props.force;
1752
1888
  }
1889
+ if (encryptionPassphrase !== void 0) {
1890
+ genOptions.passphrase = encryptionPassphrase;
1891
+ }
1753
1892
  const result = await fsProvider.generateKeyPair(genOptions);
1754
- onComplete({
1893
+ const completionResult = {
1755
1894
  provider: "filesystem",
1756
1895
  publicKeyPath: result.publicKeyPath,
1757
1896
  privateKeyRef: result.privateKeyRef,
1758
1897
  storageDescription: result.storageDescription
1759
- });
1898
+ };
1899
+ if (result.encrypted) {
1900
+ completionResult.encrypted = result.encrypted;
1901
+ }
1902
+ onComplete(completionResult);
1760
1903
  } else if (provider === "1password") {
1761
1904
  if (!selectedVault || !itemName) {
1762
1905
  throw new Error("Vault and item name are required for 1Password");
@@ -1790,7 +1933,7 @@ function KeygenInteractive(props) {
1790
1933
  completionResult.account = selectedAccount;
1791
1934
  }
1792
1935
  onComplete(completionResult);
1793
- } else {
1936
+ } else if (provider === "macos-keychain") {
1794
1937
  if (!keychainItemName) {
1795
1938
  throw new Error("Item name is required for macOS Keychain");
1796
1939
  }
@@ -1809,10 +1952,39 @@ function KeygenInteractive(props) {
1809
1952
  storageDescription: result.storageDescription,
1810
1953
  itemName: keychainItemName
1811
1954
  });
1955
+ } else {
1956
+ const encryptedKeyPath = core.getDefaultYubiKeyEncryptedKeyPath();
1957
+ const providerOptions = {
1958
+ encryptedKeyPath,
1959
+ slot: selectedYubiKeySlot
1960
+ };
1961
+ if (selectedYubiKeySerial !== void 0) {
1962
+ providerOptions.serial = selectedYubiKeySerial;
1963
+ }
1964
+ const ykProvider = new core.YubiKeyProvider(providerOptions);
1965
+ const genOptions = { publicKeyPath };
1966
+ if (props.force !== void 0) {
1967
+ genOptions.force = props.force;
1968
+ }
1969
+ const result = await ykProvider.generateKeyPair(genOptions);
1970
+ const completionResult = {
1971
+ provider: "yubikey",
1972
+ publicKeyPath: result.publicKeyPath,
1973
+ privateKeyRef: result.privateKeyRef,
1974
+ storageDescription: result.storageDescription,
1975
+ slot: selectedYubiKeySlot,
1976
+ encryptedKeyPath
1977
+ };
1978
+ if (selectedYubiKeySerial !== void 0) {
1979
+ completionResult.serial = selectedYubiKeySerial;
1980
+ }
1981
+ onComplete(completionResult);
1812
1982
  }
1813
1983
  setStep("done");
1814
1984
  } catch (err) {
1815
1985
  onError(err instanceof Error ? err : new Error("Key generation failed"));
1986
+ } finally {
1987
+ setEncryptionPassphrase(void 0);
1816
1988
  }
1817
1989
  };
1818
1990
  if (step === "checking-providers") {
@@ -1834,6 +2006,12 @@ function KeygenInteractive(props) {
1834
2006
  value: "macos-keychain"
1835
2007
  });
1836
2008
  }
2009
+ if (yubiKeyAvailable) {
2010
+ options.push({
2011
+ label: "YubiKey (hardware security key)",
2012
+ value: "yubikey"
2013
+ });
2014
+ }
1837
2015
  if (opAvailable) {
1838
2016
  options.push({
1839
2017
  label: "1Password (requires op CLI)",
@@ -1846,9 +2024,42 @@ function KeygenInteractive(props) {
1846
2024
  /* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleProviderSelect })
1847
2025
  ] });
1848
2026
  }
2027
+ if (step === "select-filesystem-encryption") {
2028
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2029
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Would you like to encrypt your private key with a passphrase?" }),
2030
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "A passphrase adds extra security but must be entered each time you sign." }),
2031
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2032
+ /* @__PURE__ */ jsxRuntime.jsx(
2033
+ ui.Select,
2034
+ {
2035
+ options: [
2036
+ { label: "No encryption (key protected by file permissions only)", value: "none" },
2037
+ { label: "Passphrase protection (AES-256 encryption)", value: "passphrase" }
2038
+ ],
2039
+ onChange: handleEncryptionMethodSelect
2040
+ }
2041
+ )
2042
+ ] });
2043
+ }
2044
+ if (step === "enter-encryption-passphrase") {
2045
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2046
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter a passphrase to encrypt your private key:" }),
2047
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: `(Minimum ${String(MIN_PASSPHRASE_LENGTH)} characters. You will need this passphrase each time you sign.)` }),
2048
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2049
+ /* @__PURE__ */ jsxRuntime.jsx(ui.PasswordInput, { onSubmit: handleEncryptionPassphrase })
2050
+ ] });
2051
+ }
2052
+ if (step === "confirm-encryption-passphrase") {
2053
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2054
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Confirm your passphrase:" }),
2055
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(Enter the same passphrase again to confirm.)" }),
2056
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2057
+ /* @__PURE__ */ jsxRuntime.jsx(ui.PasswordInput, { onSubmit: handleConfirmPassphrase })
2058
+ ] });
2059
+ }
1849
2060
  if (step === "select-account") {
1850
2061
  const options = accounts.map((account) => ({
1851
- label: account.email,
2062
+ label: account.name ? `${account.name} (${account.email})` : account.email,
1852
2063
  value: account.user_uuid
1853
2064
  }));
1854
2065
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
@@ -1890,6 +2101,65 @@ function KeygenInteractive(props) {
1890
2101
  /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
1891
2102
  ] });
1892
2103
  }
2104
+ if (step === "select-yubikey-device") {
2105
+ const options = yubiKeyDevices.map((device) => ({
2106
+ label: `${device.type} (Serial: ${device.serial})`,
2107
+ value: device.serial
2108
+ }));
2109
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2110
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey device:" }),
2111
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2112
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeyDeviceSelect })
2113
+ ] });
2114
+ }
2115
+ if (step === "select-yubikey-slot") {
2116
+ const options = [];
2117
+ if (slot2Configured) {
2118
+ options.push({
2119
+ label: "Slot 2 (recommended)",
2120
+ value: "2"
2121
+ });
2122
+ }
2123
+ if (slot1Configured) {
2124
+ options.push({
2125
+ label: "Slot 1",
2126
+ value: "1"
2127
+ });
2128
+ }
2129
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2130
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
2131
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2132
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeySlotSelect })
2133
+ ] });
2134
+ }
2135
+ if (step === "yubikey-offer-setup") {
2136
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2137
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
2138
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2139
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
2140
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
2141
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
2142
+ /* @__PURE__ */ jsxRuntime.jsx(
2143
+ ui.Select,
2144
+ {
2145
+ options: [
2146
+ { label: "Yes, configure my YubiKey", value: "yes" },
2147
+ { label: "No, cancel", value: "no" }
2148
+ ],
2149
+ onChange: handleYubiKeySetupConfirm
2150
+ }
2151
+ )
2152
+ ] });
2153
+ }
2154
+ if (step === "yubikey-configuring") {
2155
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
2156
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
2157
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
2158
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
2159
+ ] }),
2160
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
2161
+ ] });
2162
+ }
1893
2163
  if (step === "generating") {
1894
2164
  return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
1895
2165
  /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
@@ -2202,7 +2472,7 @@ async function runVerify(gates, options) {
2202
2472
  process.exit(ExitCode.CONFIG_ERROR);
2203
2473
  }
2204
2474
  const projectRoot = process.cwd();
2205
- const sealsFile = core.readSealsSync(projectRoot);
2475
+ const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
2206
2476
  const gatesToVerify = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
2207
2477
  for (const gateId of gatesToVerify) {
2208
2478
  if (!attestItConfig.gates[gateId]) {
@@ -2358,7 +2628,7 @@ async function runSeal(gates, options) {
2358
2628
  process.exit(ExitCode.CONFIG_ERROR);
2359
2629
  }
2360
2630
  const projectRoot = process.cwd();
2361
- const sealsFile = core.readSealsSync(projectRoot);
2631
+ const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
2362
2632
  const gatesToSeal = gates.length > 0 ? gates : getAllGateIds(attestItConfig);
2363
2633
  for (const gateId of gatesToSeal) {
2364
2634
  if (!attestItConfig.gates[gateId]) {
@@ -2371,9 +2641,17 @@ async function runSeal(gates, options) {
2371
2641
  skipped: [],
2372
2642
  failed: []
2373
2643
  };
2644
+ const identitySlug = localConfig.activeIdentity;
2374
2645
  for (const gateId of gatesToSeal) {
2375
2646
  try {
2376
- const result = await processSingleGate(gateId, attestItConfig, identity, sealsFile, options);
2647
+ const result = await processSingleGate(
2648
+ gateId,
2649
+ attestItConfig,
2650
+ identity,
2651
+ identitySlug,
2652
+ sealsFile,
2653
+ options
2654
+ );
2377
2655
  if (result.sealed) {
2378
2656
  summary.sealed.push(gateId);
2379
2657
  } else if (result.skipped) {
@@ -2385,7 +2663,7 @@ async function runSeal(gates, options) {
2385
2663
  }
2386
2664
  }
2387
2665
  if (!options.dryRun && summary.sealed.length > 0) {
2388
- core.writeSealsSync(projectRoot, sealsFile);
2666
+ core.writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
2389
2667
  }
2390
2668
  displaySummary(summary, options.dryRun);
2391
2669
  if (summary.failed.length > 0) {
@@ -2404,7 +2682,7 @@ async function runSeal(gates, options) {
2404
2682
  process.exit(ExitCode.CONFIG_ERROR);
2405
2683
  }
2406
2684
  }
2407
- async function processSingleGate(gateId, config, identity, sealsFile, options) {
2685
+ async function processSingleGate(gateId, config, identity, identitySlug, sealsFile, options) {
2408
2686
  verbose(`Processing gate: ${gateId}`);
2409
2687
  const gate = core.getGate(config, gateId);
2410
2688
  if (!gate) {
@@ -2444,12 +2722,12 @@ async function processSingleGate(gateId, config, identity, sealsFile, options) {
2444
2722
  const seal = core.createSeal({
2445
2723
  gateId,
2446
2724
  fingerprint: fingerprintResult.fingerprint,
2447
- sealedBy: identity.name,
2725
+ sealedBy: identitySlug,
2448
2726
  privateKey: privateKeyPem
2449
2727
  });
2450
2728
  sealsFile.seals[gateId] = seal;
2451
2729
  log(` Sealed gate: ${gateId}`);
2452
- verbose(` Sealed by: ${identity.name}`);
2730
+ verbose(` Sealed by: ${identitySlug} (${identity.name})`);
2453
2731
  verbose(` Timestamp: ${seal.timestamp}`);
2454
2732
  return { sealed: true, skipped: false };
2455
2733
  }
@@ -2974,6 +3252,7 @@ async function runCreate() {
2974
3252
  };
2975
3253
  }
2976
3254
  await core.saveLocalConfig(newConfig);
3255
+ const publicKeyResult = await core.savePublicKey(slug, keyPair.publicKey);
2977
3256
  log("");
2978
3257
  success("Identity created successfully");
2979
3258
  log("");
@@ -2988,6 +3267,12 @@ async function runCreate() {
2988
3267
  log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
2989
3268
  log(` Private Key: ${keyStorageDescription}`);
2990
3269
  log("");
3270
+ log(theme3.blue.bold()("Public key saved to:"));
3271
+ log(` ${publicKeyResult.homePath}`);
3272
+ if (publicKeyResult.projectPath) {
3273
+ log(` ${publicKeyResult.projectPath}`);
3274
+ }
3275
+ log("");
2991
3276
  if (!existingConfig) {
2992
3277
  success(`Set as active identity`);
2993
3278
  log("");
@@ -3879,7 +4164,7 @@ async function runRemove2(slug, options) {
3879
4164
  const projectRoot = process.cwd();
3880
4165
  let sealsFile;
3881
4166
  try {
3882
- sealsFile = core.readSealsSync(projectRoot);
4167
+ sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
3883
4168
  } catch {
3884
4169
  sealsFile = { version: 1, seals: {} };
3885
4170
  }