@@ -30,7 +30,7 @@ function debug(msg: string) {
3030
3131// ── Native binary one-shot commands ────────────────────────────────────
3232
33- function runNativeBinary ( args : Array < string > ) : string {
33+ function runNativeBinary ( args : Array < string > , opts ?: { timeout ?: number } ) : string {
3434 const binaryPath = resolveNativeBinary ( ) ;
3535 if ( ! binaryPath ) {
3636 debug ( 'runNativeBinary: no binary found' ) ;
@@ -39,14 +39,14 @@ function runNativeBinary(args: Array<string>): string {
3939 debug ( `runNativeBinary: ${ binaryPath } ${ args . join ( ' ' ) } ` ) ;
4040 const output = execFileSync ( binaryPath , args , {
4141 encoding : 'utf-8' ,
42- timeout : 30_000 ,
42+ timeout : opts ?. timeout ?? 30_000 ,
4343 } ) . trim ( ) ;
4444 debug ( `runNativeBinary result: ${ output . slice ( 0 , 200 ) } ` ) ;
4545 return output ;
4646}
4747
48- function runNativeBinaryJson < T = Record < string , unknown > > ( args : Array < string > ) : T {
49- const output = runNativeBinary ( args ) ;
48+ function runNativeBinaryJson < T = Record < string , unknown > > ( args : Array < string > , opts ?: { timeout ?: number } ) : T {
49+ const output = runNativeBinary ( args , opts ) ;
5050 const parsed = JSON . parse ( output ) ;
5151 if ( parsed . error ) {
5252 throw new Error ( parsed . error ) ;
@@ -57,6 +57,8 @@ function runNativeBinaryJson<T = Record<string, unknown>>(args: Array<string>):
5757// ── Backend detection ──────────────────────────────────────────────────
5858
5959let cachedBackendInfo : BackendInfo | undefined ;
60+ /** Keys reported by the status command — avoids a separate key-exists .exe spawn on WSL2 */
61+ let cachedStatusKeys : Array < string > | undefined ;
6062
6163function detectBackendType ( ) : { type : BackendType ; isFileFallback : boolean } {
6264 const binaryPath = resolveNativeBinary ( ) ;
@@ -89,7 +91,8 @@ export function getBackendInfo(): BackendInfo {
8991 // Query the native binary for its actual capabilities
9092 try {
9193 const status = runNativeBinaryJson < NativeStatusResult > ( [ 'status' ] ) ;
92- debug ( `getBackendInfo: status result: hardwareBacked=${ status . hardwareBacked } , biometricAvailable=${ status . biometricAvailable } , backend=${ status . backend } ` ) ;
94+ debug ( `getBackendInfo: status result: hardwareBacked=${ status . hardwareBacked } , biometricAvailable=${ status . biometricAvailable } , backend=${ status . backend } , keys=${ status . keys ?. join ( ',' ) } ` ) ;
95+ cachedStatusKeys = status . keys ;
9396 cachedBackendInfo = {
9497 type,
9598 platform : process . platform ,
@@ -146,6 +149,11 @@ export function keyExists(keyId: string = DEFAULT_KEY_ID): boolean {
146149 if ( backend . type === 'file' ) {
147150 return fileBackend . keyExists ( keyId ) ;
148151 }
152+ // Use cached keys from status command to avoid an extra .exe spawn (significant on WSL2)
153+ if ( cachedStatusKeys ) {
154+ debug ( `keyExists: using cached status keys for ${ keyId } ` ) ;
155+ return cachedStatusKeys . includes ( keyId ) ;
156+ }
149157 const result = runNativeBinaryJson < { exists : boolean } > ( [ 'key-exists' , '--key-id' , keyId ] ) ;
150158 return result . exists ;
151159}
@@ -204,7 +212,11 @@ export async function decryptValue(ciphertext: string, keyId: string = DEFAULT_K
204212 if ( backend . biometricAvailable ) {
205213 if ( isWSL ( ) ) {
206214 debug ( 'decryptValue: WSL2 biometric decrypt via --via-daemon' ) ;
207- const result = runNativeBinaryJson < { plaintext : string } > ( [ 'decrypt' , '--key-id' , keyId , '--data' , ciphertext , '--via-daemon' ] ) ;
215+ // Longer timeout: includes daemon spawn + Windows Hello biometric prompt
216+ const result = runNativeBinaryJson < { plaintext : string } > (
217+ [ 'decrypt' , '--key-id' , keyId , '--data' , ciphertext , '--via-daemon' ] ,
218+ { timeout : 60_000 } ,
219+ ) ;
208220 return result . plaintext ;
209221 }
210222 debug ( 'decryptValue: biometric decrypt via daemon client' ) ;
0 commit comments