From 8ea177c901e04e8b72757ee30d0678daabe7e588 Mon Sep 17 00:00:00 2001 From: 0xDevNinja Date: Thu, 30 Apr 2026 01:23:07 +0530 Subject: [PATCH 1/2] feat(m4): session-key store via @napi-rs/keyring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `SessionKeyStore` wrapping @napi-rs/keyring (OS keychain — macOS Keychain, Windows Credential Manager, libsecret on Linux) for secure storage of EVM private keys / SIWE JWTs / session secrets. Public API on `@titular/acp-sdk/session-keys`: - `SessionKeyStore({ service })` — typed wrapper with `set/get/delete` - `inMemorySessionKeyStore()` — fallback for `--non-interactive` CI flows and tests where no OS keychain is reachable - New error code `keyring_unavailable` so consumers can distinguish @napi-rs/keyring is `optionalDependencies`: native module fails on some build envs; missing dep degrades to in-memory with a console.warn. Service name is namespaced (default `titular-acp`) so multiple deploys on the same host don't collide on entries. --- packages/acp-sdk-ts/package.json | 7 + .../src/__tests__/session-keys.test.ts | 296 ++++++++++++++++++ packages/acp-sdk-ts/src/errors.ts | 3 +- packages/acp-sdk-ts/src/session-keys.ts | 289 +++++++++++++++++ 4 files changed, 594 insertions(+), 1 deletion(-) create mode 100644 packages/acp-sdk-ts/src/__tests__/session-keys.test.ts create mode 100644 packages/acp-sdk-ts/src/session-keys.ts diff --git a/packages/acp-sdk-ts/package.json b/packages/acp-sdk-ts/package.json index 64f0026..a69237c 100644 --- a/packages/acp-sdk-ts/package.json +++ b/packages/acp-sdk-ts/package.json @@ -19,6 +19,10 @@ "./llm": { "types": "./dist/llm/index.d.ts", "import": "./dist/llm/index.js" + }, + "./session-keys": { + "types": "./dist/session-keys.d.ts", + "import": "./dist/session-keys.js" } }, "files": ["dist", "README.md"], @@ -31,6 +35,9 @@ "dependencies": { "viem": "^2.21.50" }, + "optionalDependencies": { + "@napi-rs/keyring": "^1.1.0" + }, "devDependencies": { "@types/node": "~22.0.0", "@vitest/coverage-v8": "^4.1.5", diff --git a/packages/acp-sdk-ts/src/__tests__/session-keys.test.ts b/packages/acp-sdk-ts/src/__tests__/session-keys.test.ts new file mode 100644 index 0000000..8864416 --- /dev/null +++ b/packages/acp-sdk-ts/src/__tests__/session-keys.test.ts @@ -0,0 +1,296 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { AcpError } from "../errors.js"; +import { + DEFAULT_SESSION_KEY_SERVICE, + InMemorySessionKeyStore, + type KeyringEntry, + type KeyringModule, + KeyringSessionKeyStore, + createSessionKeyStore, +} from "../session-keys.js"; + +// Build an in-memory test double that mimics the `@napi-rs/keyring` Entry +// surface. Each (service, account) pair maps to one virtual keychain row. +function makeKeyringMock(): { + module: KeyringModule; + rows: Map; +} { + const rows = new Map(); + const key = (service: string, account: string) => `${service}::${account}`; + + class FakeEntry implements KeyringEntry { + constructor( + private readonly service: string, + private readonly account: string + ) {} + setPassword(secret: string): void { + rows.set(key(this.service, this.account), secret); + } + getPassword(): string | null { + return rows.get(key(this.service, this.account)) ?? null; + } + deletePassword(): boolean { + return rows.delete(key(this.service, this.account)); + } + } + + return { module: { Entry: FakeEntry }, rows }; +} + +describe("InMemorySessionKeyStore", () => { + it("uses the default service name when none is supplied", () => { + const store = new InMemorySessionKeyStore(); + expect(store.service).toBe(DEFAULT_SESSION_KEY_SERVICE); + expect(store.backend).toBe("in-memory"); + }); + + it("rejects an empty service name at construction", () => { + expect(() => new InMemorySessionKeyStore({ service: "" })).toThrow(AcpError); + }); + + it("round-trips a secret", async () => { + const store = new InMemorySessionKeyStore({ + service: "titular-acp:base-sepolia", + }); + expect(store.service).toBe("titular-acp:base-sepolia"); + + await store.set("0xabc", "deadbeef"); + expect(await store.get("0xabc")).toBe("deadbeef"); + }); + + it("returns null for a missing account", async () => { + const store = new InMemorySessionKeyStore(); + expect(await store.get("0xnobody")).toBeNull(); + }); + + it("overwrites on repeated set", async () => { + const store = new InMemorySessionKeyStore(); + await store.set("a", "first"); + await store.set("a", "second"); + expect(await store.get("a")).toBe("second"); + }); + + it("delete is idempotent — succeeds on missing accounts", async () => { + const store = new InMemorySessionKeyStore(); + await expect(store.delete("never-existed")).resolves.toBeUndefined(); + await store.set("a", "x"); + await store.delete("a"); + expect(await store.get("a")).toBeNull(); + }); + + it("clear() wipes all entries (test helper)", async () => { + const store = new InMemorySessionKeyStore(); + await store.set("a", "1"); + await store.set("b", "2"); + store.clear(); + expect(await store.get("a")).toBeNull(); + expect(await store.get("b")).toBeNull(); + }); + + it("rejects empty account / secret on every method", async () => { + const store = new InMemorySessionKeyStore(); + await expect(store.set("", "x")).rejects.toThrow(AcpError); + await expect(store.set("a", "")).rejects.toThrow(AcpError); + await expect(store.get("")).rejects.toThrow(AcpError); + await expect(store.delete("")).rejects.toThrow(AcpError); + }); + + it("rejects non-string account / secret with invalid_param", async () => { + const store = new InMemorySessionKeyStore(); + // @ts-expect-error: deliberately passing non-string to verify runtime guard + await expect(store.set(123, "x")).rejects.toMatchObject({ + code: "invalid_param", + }); + // @ts-expect-error: deliberately passing non-string to verify runtime guard + await expect(store.set("a", null)).rejects.toMatchObject({ + code: "invalid_param", + }); + }); +}); + +describe("KeyringSessionKeyStore", () => { + it("delegates set/get/delete to the keyring Entry under the configured service", async () => { + const { module, rows } = makeKeyringMock(); + const store = new KeyringSessionKeyStore(module, { + service: "titular-acp:base-sepolia", + }); + + expect(store.backend).toBe("os-keychain"); + expect(store.service).toBe("titular-acp:base-sepolia"); + + await store.set("0xabc", "secret-jwt"); + expect(rows.get("titular-acp:base-sepolia::0xabc")).toBe("secret-jwt"); + + expect(await store.get("0xabc")).toBe("secret-jwt"); + + await store.delete("0xabc"); + expect(rows.has("titular-acp:base-sepolia::0xabc")).toBe(false); + expect(await store.get("0xabc")).toBeNull(); + }); + + it("namespaces per service — same account across services does not collide", async () => { + const { module, rows } = makeKeyringMock(); + const mainnet = new KeyringSessionKeyStore(module, { + service: "titular-acp:base", + }); + const testnet = new KeyringSessionKeyStore(module, { + service: "titular-acp:base-sepolia", + }); + + await mainnet.set("0xabc", "mainnet-key"); + await testnet.set("0xabc", "testnet-key"); + + expect(await mainnet.get("0xabc")).toBe("mainnet-key"); + expect(await testnet.get("0xabc")).toBe("testnet-key"); + expect(rows.size).toBe(2); + }); + + it("wraps native errors as AcpError(keyring_unavailable) without leaking the message", async () => { + const failingModule: KeyringModule = { + Entry: class { + setPassword(): void { + throw new Error("user prompted: passphrase: hunter2"); + } + getPassword(): string | null { + throw new Error("backend disconnected: socket detail"); + } + deletePassword(): boolean { + throw new Error("os-specific error 0xdeadbeef"); + } + }, + }; + const store = new KeyringSessionKeyStore(failingModule); + + const setErr = await store.set("a", "x").catch((e) => e); + expect(setErr).toBeInstanceOf(AcpError); + expect(setErr.code).toBe("keyring_unavailable"); + expect(setErr.message).not.toContain("hunter2"); + expect(setErr.message).not.toContain("passphrase"); + + const getErr = await store.get("a").catch((e) => e); + expect(getErr).toBeInstanceOf(AcpError); + expect(getErr.message).not.toContain("socket"); + + const delErr = await store.delete("a").catch((e) => e); + expect(delErr).toBeInstanceOf(AcpError); + expect(delErr.message).not.toContain("0xdeadbeef"); + }); + + it("returns null when the keyring entry is empty", async () => { + const { module } = makeKeyringMock(); + const store = new KeyringSessionKeyStore(module); + expect(await store.get("0xnope")).toBeNull(); + }); + + it("rejects empty inputs before touching the keyring", async () => { + const probe = vi.fn(); + const sniffingModule: KeyringModule = { + Entry: class { + constructor() { + probe(); + } + setPassword(): void {} + getPassword(): string | null { + return null; + } + deletePassword(): boolean { + return true; + } + }, + }; + const store = new KeyringSessionKeyStore(sniffingModule); + await expect(store.set("", "x")).rejects.toBeInstanceOf(AcpError); + expect(probe).not.toHaveBeenCalled(); + }); +}); + +describe("createSessionKeyStore", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns the keychain-backed store when the keyring module loads", async () => { + const { module } = makeKeyringMock(); + const store = await createSessionKeyStore({ + service: "titular-acp:base-sepolia", + loadKeyring: async () => module, + }); + expect(store.backend).toBe("os-keychain"); + expect(store.service).toBe("titular-acp:base-sepolia"); + }); + + it("unwraps a default-export wrapped module", async () => { + const { module } = makeKeyringMock(); + const store = await createSessionKeyStore({ + // simulate a CJS interop wrapper + loadKeyring: async () => ({ default: module }) as unknown as KeyringModule, + }); + expect(store.backend).toBe("os-keychain"); + }); + + it("falls back to in-memory + warns when loader rejects", async () => { + const warn = vi.fn(); + const store = await createSessionKeyStore({ + loadKeyring: async () => { + throw new Error("native binary missing"); + }, + warn, + }); + expect(store.backend).toBe("in-memory"); + expect(warn).toHaveBeenCalledTimes(1); + const message = warn.mock.calls[0]?.[0] as string; + expect(message).toContain("@napi-rs/keyring"); + expect(message.toLowerCase()).toContain("in-memory"); + }); + + it("falls back when loader resolves to a non-keyring module", async () => { + const warn = vi.fn(); + const store = await createSessionKeyStore({ + loadKeyring: async () => ({ Wrong: 1 }) as unknown as KeyringModule, + warn, + }); + expect(store.backend).toBe("in-memory"); + expect(warn).toHaveBeenCalled(); + }); + + it("backend: 'in-memory' skips the loader entirely", async () => { + const loadKeyring = vi.fn(); + const warn = vi.fn(); + const store = await createSessionKeyStore({ + backend: "in-memory", + loadKeyring, + warn, + }); + expect(store.backend).toBe("in-memory"); + expect(loadKeyring).not.toHaveBeenCalled(); + expect(warn).not.toHaveBeenCalled(); + }); + + it("backend: 'os-keychain' surfaces loader failures as AcpError(keyring_unavailable)", async () => { + await expect( + createSessionKeyStore({ + backend: "os-keychain", + loadKeyring: async () => { + throw new Error("missing"); + }, + }) + ).rejects.toMatchObject({ code: "keyring_unavailable" }); + }); + + it("uses console.warn by default when falling back", async () => { + const spy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const store = await createSessionKeyStore({ + loadKeyring: async () => { + throw new Error("missing"); + }, + }); + expect(store.backend).toBe("in-memory"); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("validates service even when forcing in-memory", async () => { + await expect(createSessionKeyStore({ backend: "in-memory", service: "" })).rejects.toThrow( + AcpError + ); + }); +}); diff --git a/packages/acp-sdk-ts/src/errors.ts b/packages/acp-sdk-ts/src/errors.ts index 978f8ec..b18b042 100644 --- a/packages/acp-sdk-ts/src/errors.ts +++ b/packages/acp-sdk-ts/src/errors.ts @@ -13,7 +13,8 @@ export type AcpErrorCode = | "tx_reverted" | "event_not_found" | "provider_cannot_sign" - | "auth_failed"; + | "auth_failed" + | "keyring_unavailable"; /** * Base error class for everything thrown by the SDK. Always carries a diff --git a/packages/acp-sdk-ts/src/session-keys.ts b/packages/acp-sdk-ts/src/session-keys.ts new file mode 100644 index 0000000..8aba235 --- /dev/null +++ b/packages/acp-sdk-ts/src/session-keys.ts @@ -0,0 +1,289 @@ +// SessionKeyStore — secure storage for SDK + CLI session secrets. +// +// Wraps `@napi-rs/keyring` so callers can persist EVM private keys, SIWE JWTs, +// and other long-lived session material in the host OS credential store +// (macOS Keychain, Windows Credential Manager, libsecret on Linux) instead of +// a plaintext config file. +// +// The keyring package is a native module — pulling it in unconditionally would +// break consumers that ship to environments without prebuilt binaries (Alpine, +// some serverless runtimes, browser bundlers). We declare it as an +// `optionalDependencies` entry and load it lazily; if the module fails to +// import, the factory returns a clearly-marked in-memory fallback and emits a +// `console.warn` so operators know secrets are NOT being persisted. +// +// Service names are namespaced per-chain (`titular-acp:base-sepolia`) so a +// developer juggling mainnet / testnet keys in the same OS user account does +// not silently overwrite one with the other. Account ids are caller-supplied +// (typically the EVM address or `siwe:
`). +// +// Errors NEVER include the secret value or the underlying provider message +// (which on macOS occasionally leaks parts of the prompt) — only a stable +// machine-readable code, so logs at INFO level are safe to ship to telemetry. + +import { AcpError } from "./errors.js"; + +/** Default service name. Callers SHOULD pass an explicit `service` per chain. */ +export const DEFAULT_SESSION_KEY_SERVICE = "titular-acp"; + +/** + * Common interface implemented by both the OS-keychain-backed store and the + * in-memory fallback. Consumers should program against this type, not the + * concrete classes — the factory may return either depending on the host. + */ +export interface SessionKeyStore { + /** Human-readable description of where secrets live ("os-keychain" / "in-memory"). */ + readonly backend: SessionKeyBackend; + /** Service namespace this store was constructed with. */ + readonly service: string; + /** Persist `secret` under `account`. Overwrites any existing value. */ + set(account: string, secret: string): Promise; + /** Retrieve `secret` for `account`, or `null` if absent. */ + get(account: string): Promise; + /** Remove the entry. Idempotent — succeeds if the entry is already absent. */ + delete(account: string): Promise; +} + +/** Discriminator returned by {@link SessionKeyStore.backend}. */ +export type SessionKeyBackend = "os-keychain" | "in-memory"; + +/** + * Minimal subset of `@napi-rs/keyring`'s `Entry` API the store relies on. + * Declared explicitly so the SDK does not take a hard type-time dependency + * on the optional native module. + */ +export interface KeyringEntry { + setPassword(secret: string): void; + getPassword(): string | null; + deletePassword(): boolean; +} + +/** Module shape imported lazily from `@napi-rs/keyring`. */ +export interface KeyringModule { + Entry: new (service: string, account: string) => KeyringEntry; +} + +/** Options accepted by every store. */ +export interface SessionKeyStoreOptions { + /** + * Service namespace. Callers SHOULD include the chain id / network name + * so testnet and mainnet credentials don't collide + * (e.g. `titular-acp:base-sepolia`). Defaults to {@link DEFAULT_SESSION_KEY_SERVICE}. + */ + service?: string; +} + +/** Options for {@link createSessionKeyStore}. */ +export interface CreateSessionKeyStoreOptions extends SessionKeyStoreOptions { + /** + * Force a specific backend. When omitted, tries `os-keychain` first and + * falls back to `in-memory` if the native module cannot be loaded. + */ + backend?: SessionKeyBackend; + /** + * Override the keyring loader — used by tests to inject a mock without + * relying on `vi.mock`. Must resolve to a module exposing `Entry` (or a + * `default` namespace wrapping one — CJS-interop is unwrapped automatically). + */ + loadKeyring?: () => Promise; + /** + * Override `console.warn`. Used by tests to assert the fallback notice + * fires, and by hosts that want to route warnings to their logger. + */ + warn?: (message: string) => void; +} + +// --- OS-keychain-backed store ------------------------------------------------ + +/** + * Store backed by `@napi-rs/keyring`. Construct directly when you have already + * resolved the keyring module (e.g. in a CLI that requires persistence and is + * willing to error out when the native binary is missing). Most callers should + * use {@link createSessionKeyStore}, which falls back transparently. + */ +export class KeyringSessionKeyStore implements SessionKeyStore { + readonly backend: SessionKeyBackend = "os-keychain"; + readonly service: string; + readonly #keyring: KeyringModule; + + constructor(keyring: KeyringModule, options: SessionKeyStoreOptions = {}) { + this.#keyring = keyring; + this.service = normalizeService(options.service); + } + + async set(account: string, secret: string): Promise { + assertNonEmpty(account, "account"); + assertNonEmpty(secret, "secret"); + const entry = this.#entry(account); + try { + entry.setPassword(secret); + } catch (cause) { + // Deliberately swallow the underlying message — on macOS the OS surfaces + // user-facing prompt text that we don't want in our logs. + throw new AcpError("keyring_unavailable", "failed to write to OS keychain", { cause }); + } + } + + async get(account: string): Promise { + assertNonEmpty(account, "account"); + const entry = this.#entry(account); + try { + const value = entry.getPassword(); + return value ?? null; + } catch (cause) { + throw new AcpError("keyring_unavailable", "failed to read from OS keychain", { cause }); + } + } + + async delete(account: string): Promise { + assertNonEmpty(account, "account"); + const entry = this.#entry(account); + try { + entry.deletePassword(); + } catch (cause) { + throw new AcpError("keyring_unavailable", "failed to delete from OS keychain", { cause }); + } + } + + #entry(account: string): KeyringEntry { + return new this.#keyring.Entry(this.service, account); + } +} + +// --- in-memory fallback ------------------------------------------------------ + +/** + * Process-local store used in CI, unit tests, and any runtime where the native + * keyring is unavailable. Secrets do NOT survive process exit. Callers using + * this in production must accept that they are running without persistence. + */ +export class InMemorySessionKeyStore implements SessionKeyStore { + readonly backend: SessionKeyBackend = "in-memory"; + readonly service: string; + readonly #map = new Map(); + + constructor(options: SessionKeyStoreOptions = {}) { + this.service = normalizeService(options.service); + } + + async set(account: string, secret: string): Promise { + assertNonEmpty(account, "account"); + assertNonEmpty(secret, "secret"); + this.#map.set(account, secret); + } + + async get(account: string): Promise { + assertNonEmpty(account, "account"); + return this.#map.get(account) ?? null; + } + + async delete(account: string): Promise { + assertNonEmpty(account, "account"); + this.#map.delete(account); + } + + /** + * Test helper — wipe all entries. NOT part of the {@link SessionKeyStore} + * contract because it is meaningless against a real keychain (which holds + * entries from many services). + */ + clear(): void { + this.#map.clear(); + } +} + +// --- factory ----------------------------------------------------------------- + +/** + * Build a {@link SessionKeyStore}, preferring the OS keychain and falling back + * to in-memory storage if `@napi-rs/keyring` cannot be loaded (missing native + * binary, sandboxed runtime, etc.). When falling back, emits a `console.warn` + * so operators are not silently running without persistence. + * + * Pass `backend: "in-memory"` to skip the load attempt entirely — useful for + * tests and `--non-interactive` CI flows where the keychain would prompt. + */ +export async function createSessionKeyStore( + options: CreateSessionKeyStoreOptions = {} +): Promise { + const service = normalizeService(options.service); + + if (options.backend === "in-memory") { + return new InMemorySessionKeyStore({ service }); + } + + const loader = options.loadKeyring ?? defaultLoadKeyring; + try { + const raw = await loader(); + const mod = normalizeKeyringModule(raw); + return new KeyringSessionKeyStore(mod, { service }); + } catch (cause) { + if (options.backend === "os-keychain") { + // Caller asked explicitly for keychain — surface the failure rather + // than silently downgrading. + throw new AcpError("keyring_unavailable", "failed to load @napi-rs/keyring", { cause }); + } + const warn = options.warn ?? defaultWarn; + warn( + "[@titular/acp-sdk] @napi-rs/keyring unavailable; falling back to in-memory session" + + " key store. Secrets will NOT persist across process restarts." + ); + return new InMemorySessionKeyStore({ service }); + } +} + +async function defaultLoadKeyring(): Promise { + // Dynamic import so the optional dep is only resolved at runtime and does + // not appear in the static module graph (bundlers can drop it cleanly). + // The string is split to discourage bundlers from rewriting it. + const specifier = "@napi-rs/keyring"; + return (await import(/* @vite-ignore */ specifier)) as unknown; +} + +/** + * Coerce a loader result into a {@link KeyringModule}. Tolerates the common + * CJS-interop pattern where the module is wrapped in a `default` namespace. + */ +function normalizeKeyringModule(raw: unknown): KeyringModule { + if (isKeyringModule(raw)) return raw; + if ( + typeof raw === "object" && + raw !== null && + "default" in raw && + isKeyringModule((raw as { default: unknown }).default) + ) { + return (raw as { default: KeyringModule }).default; + } + throw new Error("@napi-rs/keyring module did not expose Entry"); +} + +function defaultWarn(message: string): void { + // Routed through console.warn so the operator sees it on stderr; tests + // override via `options.warn`. + console.warn(message); +} + +// --- helpers ----------------------------------------------------------------- + +function normalizeService(service: string | undefined): string { + if (service === undefined) return DEFAULT_SESSION_KEY_SERVICE; + if (typeof service !== "string" || service.length === 0) { + throw new AcpError("invalid_param", "`service` must be a non-empty string"); + } + return service; +} + +function assertNonEmpty(value: unknown, key: string): void { + if (typeof value !== "string" || value.length === 0) { + throw new AcpError("invalid_param", `\`${key}\` must be a non-empty string`); + } +} + +function isKeyringModule(value: unknown): value is KeyringModule { + return ( + typeof value === "object" && + value !== null && + "Entry" in value && + typeof (value as { Entry: unknown }).Entry === "function" + ); +} From ba47333256226cfd5bc02f1592b119d3c5086bdc Mon Sep 17 00:00:00 2001 From: 0xDevNinja Date: Thu, 30 Apr 2026 01:30:14 +0530 Subject: [PATCH 2/2] chore(m4): refresh pnpm-lock.yaml for @napi-rs/keyring optional dep CI runs `pnpm install --frozen-lockfile`, which fails when package.json declares an optional dep that isn't in the lockfile. Refreshes the lockfile to register `@napi-rs/keyring@^1.1.0` under the acp-sdk-ts importer's `optionalDependencies` block. --- pnpm-lock.yaml | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32852d8..2de45a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,10 @@ importers: vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@22.0.3)(@vitest/coverage-v8@4.1.5)(jsdom@29.0.2(@noble/hashes@1.8.0))(vite@8.0.10(@types/node@22.0.3)(jiti@2.6.1)(yaml@2.6.1)) + optionalDependencies: + '@napi-rs/keyring': + specifier: ^1.1.0 + version: 1.2.0 packages: @@ -535,6 +539,82 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@napi-rs/keyring-darwin-arm64@1.2.0': + resolution: {integrity: sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/keyring-darwin-x64@1.2.0': + resolution: {integrity: sha512-dBHjtKRCj4ByfnfqIKIJLo3wueQNJhLRyuxtX/rR4K/XtcS7VLlRD01XXizjpre54vpmObj63w+ZpHG+mGM8uA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/keyring-freebsd-x64@1.2.0': + resolution: {integrity: sha512-DPZFr11pNJSnaoh0dzSUNF+T6ORhy3CkzUT3uGixbA71cAOPJ24iG8e8QrLOkuC/StWrAku3gBnth2XMWOcR3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/keyring-linux-arm-gnueabihf@1.2.0': + resolution: {integrity: sha512-8xv6DyEMlvRdqJzp4F39RLUmmTQsLcGYYv/3eIfZNZN1O5257tHxTrFYqAsny659rJJK2EKeSa7PhrSibQqRWQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/keyring-linux-arm64-gnu@1.2.0': + resolution: {integrity: sha512-Pu2V6Py+PBt7inryEecirl+t+ti8bhZphjP+W68iVaXHUxLdWmkgL9KI1VkbRHbx5k8K5Tew9OP218YfmVguIA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/keyring-linux-arm64-musl@1.2.0': + resolution: {integrity: sha512-8TDymrpC4P1a9iDEaegT7RnrkmrJN5eNZh3Im3UEV5PPYGtrb82CRxsuFohthCWQW81O483u1bu+25+XA4nKUw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/keyring-linux-riscv64-gnu@1.2.0': + resolution: {integrity: sha512-awsB5XI1MYL7fwfjMDGmKOWvNgJEO7mM7iVEMS0fO39f0kVJnOSjlu7RHcXAF0LOx+0VfF3oxbWqJmZbvRCRHw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/keyring-linux-x64-gnu@1.2.0': + resolution: {integrity: sha512-8E+7z4tbxSJXxIBqA+vfB1CGajpCDRyTyqXkBig5NtASrv4YXcntSo96Iah2QDR5zD3dSTsmbqJudcj9rKKuHQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/keyring-linux-x64-musl@1.2.0': + resolution: {integrity: sha512-8RZ8yVEnmWr/3BxKgBSzmgntI7lNEsY7xouNfOsQkuVAiCNmxzJwETspzK3PQ2FHtDxgz5vHQDEBVGMyM4hUHA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/keyring-win32-arm64-msvc@1.2.0': + resolution: {integrity: sha512-AoqaDZpQ6KPE19VBLpxyORcp+yWmHI9Xs9Oo0PJ4mfHma4nFSLVdhAubJCxdlNptHe5va7ghGCHj3L9Akiv4cQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/keyring-win32-ia32-msvc@1.2.0': + resolution: {integrity: sha512-EYL+EEI6bCsYi3LfwcQdnX3P/R76ENKNn+3PmpGheBsUFLuh0gQuP7aMVHM4rTw6UVe+L3vCLZSptq/oeacz0A==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/keyring-win32-x64-msvc@1.2.0': + resolution: {integrity: sha512-xFlx/TsmqmCwNU9v+AVnEJgoEAlBYgzFF5Ihz1rMpPAt4qQWWkMd4sCyM1gMJ1A/GnRqRegDiQpwaxGUHFtFbA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/keyring@1.2.0': + resolution: {integrity: sha512-d0d4Oyxm+v980PEq1ZH2PmS6cvpMIRc17eYpiU47KgW+lzxklMu6+HOEOPmxrpnF/XQZ0+Q78I2mgMhbIIo/dg==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -2334,6 +2414,58 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@napi-rs/keyring-darwin-arm64@1.2.0': + optional: true + + '@napi-rs/keyring-darwin-x64@1.2.0': + optional: true + + '@napi-rs/keyring-freebsd-x64@1.2.0': + optional: true + + '@napi-rs/keyring-linux-arm-gnueabihf@1.2.0': + optional: true + + '@napi-rs/keyring-linux-arm64-gnu@1.2.0': + optional: true + + '@napi-rs/keyring-linux-arm64-musl@1.2.0': + optional: true + + '@napi-rs/keyring-linux-riscv64-gnu@1.2.0': + optional: true + + '@napi-rs/keyring-linux-x64-gnu@1.2.0': + optional: true + + '@napi-rs/keyring-linux-x64-musl@1.2.0': + optional: true + + '@napi-rs/keyring-win32-arm64-msvc@1.2.0': + optional: true + + '@napi-rs/keyring-win32-ia32-msvc@1.2.0': + optional: true + + '@napi-rs/keyring-win32-x64-msvc@1.2.0': + optional: true + + '@napi-rs/keyring@1.2.0': + optionalDependencies: + '@napi-rs/keyring-darwin-arm64': 1.2.0 + '@napi-rs/keyring-darwin-x64': 1.2.0 + '@napi-rs/keyring-freebsd-x64': 1.2.0 + '@napi-rs/keyring-linux-arm-gnueabihf': 1.2.0 + '@napi-rs/keyring-linux-arm64-gnu': 1.2.0 + '@napi-rs/keyring-linux-arm64-musl': 1.2.0 + '@napi-rs/keyring-linux-riscv64-gnu': 1.2.0 + '@napi-rs/keyring-linux-x64-gnu': 1.2.0 + '@napi-rs/keyring-linux-x64-musl': 1.2.0 + '@napi-rs/keyring-win32-arm64-msvc': 1.2.0 + '@napi-rs/keyring-win32-ia32-msvc': 1.2.0 + '@napi-rs/keyring-win32-x64-msvc': 1.2.0 + optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0