varlock 1.0.0 → 1.1.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.
Files changed (109) hide show
  1. package/dist/auto-load.js +2 -2
  2. package/dist/{chunk-VODQDF4Q.js → chunk-6RF54KKR.js} +36 -7
  3. package/dist/chunk-6RF54KKR.js.map +1 -0
  4. package/dist/{chunk-UKFHSMKY.js → chunk-7GFD2ATN.js} +4 -4
  5. package/dist/{chunk-UKFHSMKY.js.map → chunk-7GFD2ATN.js.map} +1 -1
  6. package/dist/{chunk-QNTIIXD5.js → chunk-A6THM3IR.js} +5 -5
  7. package/dist/{chunk-QNTIIXD5.js.map → chunk-A6THM3IR.js.map} +1 -1
  8. package/dist/{chunk-FTVXXJMG.js → chunk-CDLU5P62.js} +3 -3
  9. package/dist/{chunk-FTVXXJMG.js.map → chunk-CDLU5P62.js.map} +1 -1
  10. package/dist/{chunk-V2MTE4J6.js → chunk-E3F6QKDZ.js} +5 -5
  11. package/dist/{chunk-V2MTE4J6.js.map → chunk-E3F6QKDZ.js.map} +1 -1
  12. package/dist/{chunk-YHOWSHVH.js → chunk-F5H5MJ6U.js} +32 -5
  13. package/dist/chunk-F5H5MJ6U.js.map +1 -0
  14. package/dist/{chunk-6DXWXIQV.js → chunk-GURKQO4J.js} +259 -149
  15. package/dist/chunk-GURKQO4J.js.map +1 -0
  16. package/dist/{chunk-2ABRAKHE.js → chunk-H6NILU2I.js} +4 -4
  17. package/dist/{chunk-2ABRAKHE.js.map → chunk-H6NILU2I.js.map} +1 -1
  18. package/dist/{chunk-2EEXFFKL.js → chunk-JIUWL2NT.js} +6 -6
  19. package/dist/{chunk-2EEXFFKL.js.map → chunk-JIUWL2NT.js.map} +1 -1
  20. package/dist/chunk-JUPAI2X4.js +30 -0
  21. package/dist/chunk-JUPAI2X4.js.map +1 -0
  22. package/dist/chunk-O3WTD6L4.js +37 -0
  23. package/dist/chunk-O3WTD6L4.js.map +1 -0
  24. package/dist/{chunk-GBCB7Y3B.js → chunk-QP7TS4SU.js} +6 -5
  25. package/dist/chunk-QP7TS4SU.js.map +1 -0
  26. package/dist/{chunk-AMNNQL7K.js → chunk-QSYH5IDD.js} +3 -3
  27. package/dist/{chunk-AMNNQL7K.js.map → chunk-QSYH5IDD.js.map} +1 -1
  28. package/dist/{chunk-JEZQ2DFL.js → chunk-RBFS2QGC.js} +9 -8
  29. package/dist/chunk-RBFS2QGC.js.map +1 -0
  30. package/dist/{chunk-XADO6HQG.js → chunk-S5O4AAVX.js} +6 -6
  31. package/dist/{chunk-XADO6HQG.js.map → chunk-S5O4AAVX.js.map} +1 -1
  32. package/dist/{chunk-CKWXLVMV.js → chunk-SDN53OAC.js} +4 -4
  33. package/dist/{chunk-CKWXLVMV.js.map → chunk-SDN53OAC.js.map} +1 -1
  34. package/dist/{chunk-QJ6NMN5H.js → chunk-TQXYC3G3.js} +5 -5
  35. package/dist/{chunk-QJ6NMN5H.js.map → chunk-TQXYC3G3.js.map} +1 -1
  36. package/dist/{chunk-6JKWTWLB.js → chunk-U2O3AUM2.js} +6 -6
  37. package/dist/{chunk-6JKWTWLB.js.map → chunk-U2O3AUM2.js.map} +1 -1
  38. package/dist/{chunk-LOIJO4MO.js → chunk-VN4LKYXR.js} +4 -4
  39. package/dist/{chunk-LOIJO4MO.js.map → chunk-VN4LKYXR.js.map} +1 -1
  40. package/dist/{chunk-U7RQPX5K.js → chunk-XWYFSG46.js} +27 -6
  41. package/dist/chunk-XWYFSG46.js.map +1 -0
  42. package/dist/{chunk-XTOUG72X.js → chunk-YO6WHPM4.js} +5 -5
  43. package/dist/{chunk-XTOUG72X.js.map → chunk-YO6WHPM4.js.map} +1 -1
  44. package/dist/{chunk-NJONB6CB.js → chunk-ZJNDICC4.js} +5 -5
  45. package/dist/{chunk-NJONB6CB.js.map → chunk-ZJNDICC4.js.map} +1 -1
  46. package/dist/cli/cli-executable.js +33 -33
  47. package/dist/cli/cli-executable.js.map +1 -1
  48. package/dist/config-item-6LTV4PNH.js +7 -0
  49. package/dist/{config-item-2FQ6I6PO.js.map → config-item-6LTV4PNH.js.map} +1 -1
  50. package/dist/dotenv-compat.js +2 -2
  51. package/dist/encrypt.command-F2OTB6HD.js +14 -0
  52. package/dist/{encrypt.command-K2SBJVQ5.js.map → encrypt.command-F2OTB6HD.js.map} +1 -1
  53. package/dist/{env-graph-CXTsI2Eg.d.ts → env-graph-iNQyTcya.d.ts} +4 -0
  54. package/dist/explain.command-TEIPRC7Q.js +15 -0
  55. package/dist/{explain.command-E6MR72IY.js.map → explain.command-TEIPRC7Q.js.map} +1 -1
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.js +7 -7
  58. package/dist/init.command-5LP3UFKD.js +13 -0
  59. package/dist/{init.command-TGDNXHJ7.js.map → init.command-5LP3UFKD.js.map} +1 -1
  60. package/dist/install-plugin.command-X7RSLPUJ.js +13 -0
  61. package/dist/{install-plugin.command-Z2S5DIFS.js.map → install-plugin.command-X7RSLPUJ.js.map} +1 -1
  62. package/dist/lib/exec-sync-varlock.d.ts +34 -9
  63. package/dist/lib/exec-sync-varlock.js +1 -1
  64. package/dist/load.command-7SQRDQ3E.js +15 -0
  65. package/dist/{load.command-K2IH3646.js.map → load.command-7SQRDQ3E.js.map} +1 -1
  66. package/dist/lock.command-4LTGMJA3.js +7 -0
  67. package/dist/{lock.command-GL4CLXED.js.map → lock.command-4LTGMJA3.js.map} +1 -1
  68. package/dist/plugin-lib.d.ts +2 -2
  69. package/dist/printenv.command-ON7RMFEU.js +15 -0
  70. package/dist/{printenv.command-34LGQT6W.js.map → printenv.command-ON7RMFEU.js.map} +1 -1
  71. package/dist/reveal.command-BW6XYVXH.js +15 -0
  72. package/dist/{reveal.command-WOHYRSYO.js.map → reveal.command-BW6XYVXH.js.map} +1 -1
  73. package/dist/run.command-5QADABYL.js +16 -0
  74. package/dist/{run.command-PPC2X2N7.js.map → run.command-5QADABYL.js.map} +1 -1
  75. package/dist/runtime/env.d.ts +1 -1
  76. package/dist/{scan.command-4DJBQEML.js → scan.command-ZVW3XAUG.js} +9 -9
  77. package/dist/{scan.command-4DJBQEML.js.map → scan.command-ZVW3XAUG.js.map} +1 -1
  78. package/dist/telemetry.command-PY6E4QSH.js +13 -0
  79. package/dist/{telemetry.command-LIOCXWQK.js.map → telemetry.command-PY6E4QSH.js.map} +1 -1
  80. package/dist/typegen.command-KZ4O5IKQ.js +15 -0
  81. package/dist/{typegen.command-COL35ALE.js.map → typegen.command-KZ4O5IKQ.js.map} +1 -1
  82. package/native-bins/darwin/VarlockEnclave.app/Contents/CodeResources +0 -0
  83. package/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt +0 -0
  84. package/native-bins/linux-arm64/varlock-local-encrypt +0 -0
  85. package/native-bins/linux-x64/varlock-local-encrypt +0 -0
  86. package/native-bins/win32-x64/varlock-local-encrypt.exe +0 -0
  87. package/package.json +1 -1
  88. package/dist/chunk-6DXWXIQV.js.map +0 -1
  89. package/dist/chunk-7WB7HK5Z.js +0 -21
  90. package/dist/chunk-7WB7HK5Z.js.map +0 -1
  91. package/dist/chunk-GBCB7Y3B.js.map +0 -1
  92. package/dist/chunk-JEZQ2DFL.js.map +0 -1
  93. package/dist/chunk-U7RQPX5K.js.map +0 -1
  94. package/dist/chunk-UUDTMOJS.js +0 -22
  95. package/dist/chunk-UUDTMOJS.js.map +0 -1
  96. package/dist/chunk-VODQDF4Q.js.map +0 -1
  97. package/dist/chunk-YHOWSHVH.js.map +0 -1
  98. package/dist/config-item-2FQ6I6PO.js +0 -7
  99. package/dist/encrypt.command-K2SBJVQ5.js +0 -14
  100. package/dist/explain.command-E6MR72IY.js +0 -15
  101. package/dist/init.command-TGDNXHJ7.js +0 -13
  102. package/dist/install-plugin.command-Z2S5DIFS.js +0 -13
  103. package/dist/load.command-K2IH3646.js +0 -15
  104. package/dist/lock.command-GL4CLXED.js +0 -7
  105. package/dist/printenv.command-34LGQT6W.js +0 -15
  106. package/dist/reveal.command-WOHYRSYO.js +0 -15
  107. package/dist/run.command-PPC2X2N7.js +0 -16
  108. package/dist/telemetry.command-LIOCXWQK.js +0 -13
  109. package/dist/typegen.command-COL35ALE.js +0 -15
@@ -1,30 +1,12 @@
1
- import { getUserVarlockDir } from './chunk-7WB7HK5Z.js';
1
+ import { isWSL, getUserVarlockDir } from './chunk-O3WTD6L4.js';
2
2
  import { __name } from './chunk-6PEHRAEP.js';
3
3
  import { spawn, execFileSync, spawnSync } from 'child_process';
4
- import fs3 from 'fs';
4
+ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import net from 'net';
8
8
  import crypto, { webcrypto } from 'crypto';
9
9
 
10
- var _isWSL;
11
- function isWSL() {
12
- if (_isWSL !== void 0) return _isWSL;
13
- if (process.env.WSL_DISTRO_NAME) {
14
- _isWSL = true;
15
- return true;
16
- }
17
- try {
18
- const version = fs3.readFileSync("/proc/version", "utf-8");
19
- _isWSL = /microsoft|wsl/i.test(version);
20
- } catch {
21
- _isWSL = false;
22
- }
23
- return _isWSL;
24
- }
25
- __name(isWSL, "isWSL");
26
-
27
- // src/lib/local-encrypt/binary-resolver.ts
28
10
  var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
29
11
  function debug(msg) {
30
12
  if (process.env.VARLOCK_DEBUG) {
@@ -39,9 +21,9 @@ function resolvePackageRoot() {
39
21
  let dir = __dirname$1;
40
22
  for (let i = 0; i < 10; i++) {
41
23
  const pkgJsonPath = path.join(dir, "package.json");
42
- if (fs3.existsSync(pkgJsonPath)) {
24
+ if (fs.existsSync(pkgJsonPath)) {
43
25
  try {
44
- const pkgJson = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
26
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
45
27
  if (pkgJson.name === "varlock") return dir;
46
28
  } catch {
47
29
  }
@@ -67,15 +49,15 @@ function getNativeBinSubdir() {
67
49
  __name(getNativeBinSubdir, "getNativeBinSubdir");
68
50
  function resolveMacOSBinary(dir) {
69
51
  const appBundlePath = path.join(dir, MACOS_APP_BUNDLE, "Contents", "MacOS", BINARY_NAME);
70
- if (fs3.existsSync(appBundlePath)) return appBundlePath;
52
+ if (fs.existsSync(appBundlePath)) return appBundlePath;
71
53
  const barePath = path.join(dir, BINARY_NAME);
72
- if (fs3.existsSync(barePath)) return barePath;
54
+ if (fs.existsSync(barePath)) return barePath;
73
55
  return void 0;
74
56
  }
75
57
  __name(resolveMacOSBinary, "resolveMacOSBinary");
76
58
  function resolveStandardBinary(dir) {
77
59
  const binaryPath = path.join(dir, getPlatformBinaryName());
78
- if (fs3.existsSync(binaryPath)) return binaryPath;
60
+ if (fs.existsSync(binaryPath)) return binaryPath;
79
61
  return void 0;
80
62
  }
81
63
  __name(resolveStandardBinary, "resolveStandardBinary");
@@ -85,7 +67,7 @@ function resolveBinaryFromDir(dir) {
85
67
  }
86
68
  __name(resolveBinaryFromDir, "resolveBinaryFromDir");
87
69
  function resolveSeaSibling() {
88
- const execDir = path.dirname(fs3.realpathSync(process.execPath));
70
+ const execDir = path.dirname(fs.realpathSync(process.execPath));
89
71
  const sibling = resolveBinaryFromDir(execDir);
90
72
  if (sibling) return sibling;
91
73
  const libexecDir = path.join(execDir, "..", "libexec");
@@ -95,9 +77,9 @@ __name(resolveSeaSibling, "resolveSeaSibling");
95
77
  function resolveNpmBundled() {
96
78
  const packageRoot = resolvePackageRoot();
97
79
  const nativeBinsDir = path.join(packageRoot, "native-bins", getNativeBinSubdir());
98
- if (fs3.existsSync(nativeBinsDir)) return resolveBinaryFromDir(nativeBinsDir);
80
+ if (fs.existsSync(nativeBinsDir)) return resolveBinaryFromDir(nativeBinsDir);
99
81
  const adjacentNativeBinsDir = path.join(path.dirname(packageRoot), "native-bins", getNativeBinSubdir());
100
- if (fs3.existsSync(adjacentNativeBinsDir)) return resolveBinaryFromDir(adjacentNativeBinsDir);
82
+ if (fs.existsSync(adjacentNativeBinsDir)) return resolveBinaryFromDir(adjacentNativeBinsDir);
101
83
  return void 0;
102
84
  }
103
85
  __name(resolveNpmBundled, "resolveNpmBundled");
@@ -109,20 +91,20 @@ function resolveDevFallback() {
109
91
  dir = parent;
110
92
  if (process.platform === "darwin") {
111
93
  const swiftBuild = path.join(dir, "packages", "encryption-binary-swift", "swift", ".build", "release", "VarlockEnclave");
112
- if (fs3.existsSync(swiftBuild)) return swiftBuild;
94
+ if (fs.existsSync(swiftBuild)) return swiftBuild;
113
95
  }
114
96
  const rustBuild = path.join(dir, "packages", "encryption-binary-rust", "target", "release", getPlatformBinaryName());
115
- if (fs3.existsSync(rustBuild)) return rustBuild;
97
+ if (fs.existsSync(rustBuild)) return rustBuild;
116
98
  }
117
99
  return void 0;
118
100
  }
119
101
  __name(resolveDevFallback, "resolveDevFallback");
120
102
  function ensureExecutable(binaryPath) {
121
103
  try {
122
- fs3.accessSync(binaryPath, fs3.constants.X_OK);
104
+ fs.accessSync(binaryPath, fs.constants.X_OK);
123
105
  } catch {
124
106
  if (process.platform !== "win32") {
125
- fs3.chmodSync(binaryPath, 493);
107
+ fs.chmodSync(binaryPath, 493);
126
108
  }
127
109
  }
128
110
  return binaryPath;
@@ -163,6 +145,10 @@ function resolveNativeBinary() {
163
145
  return void 0;
164
146
  }
165
147
  __name(resolveNativeBinary, "resolveNativeBinary");
148
+ var SEND_TIMEOUT_MS = 3e4;
149
+ var BIOMETRIC_TIMEOUT_MS = 9e4;
150
+ var INTERACTIVE_TIMEOUT_MS = 5 * 6e4;
151
+ var KILL_GRACE_MS = 2e3;
166
152
  function debug2(msg) {
167
153
  if (process.env.VARLOCK_DEBUG) {
168
154
  process.stderr.write(`[varlock:daemon-client] ${msg}
@@ -170,6 +156,40 @@ function debug2(msg) {
170
156
  }
171
157
  }
172
158
  __name(debug2, "debug");
159
+ function killDaemonProcess(pid) {
160
+ try {
161
+ process.kill(pid, "SIGTERM");
162
+ } catch {
163
+ return true;
164
+ }
165
+ const start = Date.now();
166
+ while (Date.now() - start < KILL_GRACE_MS) {
167
+ try {
168
+ process.kill(pid, 0);
169
+ } catch {
170
+ return true;
171
+ }
172
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
173
+ }
174
+ debug2(`daemon pid ${pid} didn't respond to SIGTERM, sending SIGKILL`);
175
+ try {
176
+ process.kill(pid, "SIGKILL");
177
+ } catch {
178
+ return true;
179
+ }
180
+ const killStart = Date.now();
181
+ while (Date.now() - killStart < 500) {
182
+ try {
183
+ process.kill(pid, 0);
184
+ } catch {
185
+ return true;
186
+ }
187
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
188
+ }
189
+ debug2(`daemon pid ${pid} is unkillable (likely in uninterruptible kernel wait) \u2014 proceeding anyway`);
190
+ return false;
191
+ }
192
+ __name(killDaemonProcess, "killDaemonProcess");
173
193
  function getSocketDir() {
174
194
  return path.join(getUserVarlockDir(), "local-encrypt");
175
195
  }
@@ -181,6 +201,10 @@ function getSocketPath() {
181
201
  return path.join(getSocketDir(), "daemon.sock");
182
202
  }
183
203
  __name(getSocketPath, "getSocketPath");
204
+ function getLockPath() {
205
+ return `${getSocketPath()}.lock`;
206
+ }
207
+ __name(getLockPath, "getLockPath");
184
208
  function getPidPath() {
185
209
  return path.join(getSocketDir(), "daemon.pid");
186
210
  }
@@ -189,12 +213,29 @@ function getDaemonInfoPath() {
189
213
  return path.join(getSocketDir(), "daemon.info");
190
214
  }
191
215
  __name(getDaemonInfoPath, "getDaemonInfoPath");
216
+ function getDaemonStateFiles() {
217
+ const files = [getPidPath(), getDaemonInfoPath()];
218
+ if (process.platform !== "win32") {
219
+ files.push(getSocketPath(), getLockPath());
220
+ }
221
+ return files;
222
+ }
223
+ __name(getDaemonStateFiles, "getDaemonStateFiles");
224
+ function cleanupDaemonFiles() {
225
+ for (const file of getDaemonStateFiles()) {
226
+ try {
227
+ fs.unlinkSync(file);
228
+ } catch {
229
+ }
230
+ }
231
+ }
232
+ __name(cleanupDaemonFiles, "cleanupDaemonFiles");
192
233
  function checkDaemonBinaryStale() {
193
234
  const infoPath = getDaemonInfoPath();
194
235
  const pidPath = getPidPath();
195
236
  let info;
196
237
  try {
197
- info = JSON.parse(fs3.readFileSync(infoPath, "utf-8"));
238
+ info = JSON.parse(fs.readFileSync(infoPath, "utf-8"));
198
239
  } catch {
199
240
  }
200
241
  const currentBinaryPath = resolveNativeBinary();
@@ -204,7 +245,7 @@ function checkDaemonBinaryStale() {
204
245
  debug2(`daemon binary path changed: ${info.binaryPath} \u2192 ${currentBinaryPath}`);
205
246
  } else {
206
247
  try {
207
- const stat = fs3.statSync(currentBinaryPath);
248
+ const stat = fs.statSync(currentBinaryPath);
208
249
  if (stat.mtimeMs === info.binaryMtimeMs) {
209
250
  debug2("daemon binary is current \u2014 no restart needed");
210
251
  return void 0;
@@ -218,18 +259,20 @@ function checkDaemonBinaryStale() {
218
259
  debug2("no daemon.info file \u2014 treating running daemon as stale");
219
260
  }
220
261
  try {
221
- const pid = parseInt(fs3.readFileSync(pidPath, "utf-8").trim(), 10);
262
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
222
263
  process.kill(pid, 0);
223
264
  return pid;
224
265
  } catch {
266
+ debug2("stale PID file points to dead process \u2014 cleaning up");
267
+ cleanupDaemonFiles();
225
268
  return void 0;
226
269
  }
227
270
  }
228
271
  __name(checkDaemonBinaryStale, "checkDaemonBinaryStale");
229
272
  function writeDaemonInfo(binaryPath) {
230
273
  try {
231
- const stat = fs3.statSync(binaryPath);
232
- fs3.writeFileSync(getDaemonInfoPath(), JSON.stringify({
274
+ const stat = fs.statSync(binaryPath);
275
+ fs.writeFileSync(getDaemonInfoPath(), JSON.stringify({
233
276
  binaryPath,
234
277
  binaryMtimeMs: stat.mtimeMs
235
278
  }));
@@ -277,22 +320,8 @@ var DaemonClient = class {
277
320
  const stalePid = this.spawnedInThisProcess ? void 0 : checkDaemonBinaryStale();
278
321
  if (stalePid) {
279
322
  debug2(`killing stale daemon (pid ${stalePid}) \u2014 binary has been updated`);
280
- try {
281
- process.kill(stalePid, "SIGTERM");
282
- } catch {
283
- }
284
- for (const file of [getPidPath(), getDaemonInfoPath()]) {
285
- try {
286
- fs3.unlinkSync(file);
287
- } catch {
288
- }
289
- }
290
- if (process.platform !== "win32") {
291
- try {
292
- fs3.unlinkSync(socketPath);
293
- } catch {
294
- }
295
- }
323
+ killDaemonProcess(stalePid);
324
+ cleanupDaemonFiles();
296
325
  } else {
297
326
  try {
298
327
  await this.connectToSocket(socketPath);
@@ -302,7 +331,8 @@ var DaemonClient = class {
302
331
  }
303
332
  try {
304
333
  await this.spawnDaemon();
305
- } catch {
334
+ } catch (err) {
335
+ debug2(`spawnDaemon failed: ${err instanceof Error ? err.message : err}`);
306
336
  await new Promise((r) => {
307
337
  setTimeout(r, 1e3);
308
338
  });
@@ -310,76 +340,88 @@ var DaemonClient = class {
310
340
  await this.connectToSocket(socketPath);
311
341
  }
312
342
  async decrypt(ciphertext, keyId = "varlock-default") {
313
- await this.ensureConnected();
314
- const result = await this.sendMessage({
315
- action: "decrypt",
316
- payload: { ciphertext, keyId }
343
+ return this.withRetry(async () => {
344
+ await this.ensureConnected();
345
+ const result = await this.sendMessage({
346
+ action: "decrypt",
347
+ payload: { ciphertext, keyId }
348
+ }, BIOMETRIC_TIMEOUT_MS);
349
+ if (typeof result === "string") return result;
350
+ if (result && typeof result === "object" && "error" in result) {
351
+ throw new Error(String(result.error));
352
+ }
353
+ return String(result);
317
354
  });
318
- if (typeof result === "string") return result;
319
- if (result && typeof result === "object" && "error" in result) {
320
- throw new Error(String(result.error));
321
- }
322
- return String(result);
323
355
  }
324
356
  async promptSecret(opts) {
325
- await this.ensureConnected();
326
- try {
327
- const result = await this.sendMessage({
328
- action: "prompt-secret",
329
- payload: {
330
- itemKey: opts?.itemKey,
331
- message: opts?.message,
332
- keyId: opts?.keyId
357
+ return this.withRetry(async () => {
358
+ await this.ensureConnected();
359
+ try {
360
+ const result = await this.sendMessage({
361
+ action: "prompt-secret",
362
+ payload: {
363
+ itemKey: opts?.itemKey,
364
+ message: opts?.message,
365
+ keyId: opts?.keyId
366
+ }
367
+ }, INTERACTIVE_TIMEOUT_MS);
368
+ if (result && typeof result === "object" && "ciphertext" in result) {
369
+ return result.ciphertext;
333
370
  }
334
- });
335
- if (result && typeof result === "object" && "ciphertext" in result) {
336
- return result.ciphertext;
371
+ return void 0;
372
+ } catch (err) {
373
+ if (err instanceof Error && err.message === "cancelled") return void 0;
374
+ throw err;
337
375
  }
338
- return void 0;
339
- } catch (err) {
340
- if (err instanceof Error && err.message === "cancelled") return void 0;
341
- throw err;
342
- }
376
+ });
343
377
  }
344
378
  async invalidateSession() {
345
- await this.ensureConnected();
346
- await this.sendMessage({ action: "invalidate-session" });
379
+ return this.withRetry(async () => {
380
+ await this.ensureConnected();
381
+ await this.sendMessage({ action: "invalidate-session" });
382
+ });
347
383
  }
348
384
  async keychainGet(opts) {
349
- await this.ensureConnected();
350
- const result = await this.sendMessage({
351
- action: "keychain-get",
352
- payload: opts
385
+ return this.withRetry(async () => {
386
+ await this.ensureConnected();
387
+ const result = await this.sendMessage({
388
+ action: "keychain-get",
389
+ payload: opts
390
+ }, BIOMETRIC_TIMEOUT_MS);
391
+ if (typeof result === "string") return result;
392
+ if (result && typeof result === "object" && "error" in result) {
393
+ throw new Error(String(result.error));
394
+ }
395
+ return String(result);
353
396
  });
354
- if (typeof result === "string") return result;
355
- if (result && typeof result === "object" && "error" in result) {
356
- throw new Error(String(result.error));
357
- }
358
- return String(result);
359
397
  }
360
398
  async keychainSearch(opts) {
361
- await this.ensureConnected();
362
- const result = await this.sendMessage({
363
- action: "keychain-search",
364
- payload: opts ?? {}
399
+ return this.withRetry(async () => {
400
+ await this.ensureConnected();
401
+ const result = await this.sendMessage({
402
+ action: "keychain-search",
403
+ payload: opts ?? {}
404
+ });
405
+ return result ?? [];
365
406
  });
366
- return result ?? [];
367
407
  }
368
408
  async keychainPick(opts) {
369
- await this.ensureConnected();
370
- try {
371
- const result = await this.sendMessage({
372
- action: "keychain-pick",
373
- payload: { itemKey: opts?.itemKey }
374
- });
375
- if (result && typeof result === "object" && "service" in result) {
376
- return result;
409
+ return this.withRetry(async () => {
410
+ await this.ensureConnected();
411
+ try {
412
+ const result = await this.sendMessage({
413
+ action: "keychain-pick",
414
+ payload: { itemKey: opts?.itemKey }
415
+ }, INTERACTIVE_TIMEOUT_MS);
416
+ if (result && typeof result === "object" && "service" in result) {
417
+ return result;
418
+ }
419
+ return void 0;
420
+ } catch (err) {
421
+ if (err instanceof Error && err.message === "cancelled") return void 0;
422
+ throw err;
377
423
  }
378
- return void 0;
379
- } catch (err) {
380
- if (err instanceof Error && err.message === "cancelled") return void 0;
381
- throw err;
382
- }
424
+ });
383
425
  }
384
426
  cleanup() {
385
427
  for (const { reject } of this.messageQueue.values()) {
@@ -392,6 +434,37 @@ var DaemonClient = class {
392
434
  this.buffer = Buffer.alloc(0);
393
435
  }
394
436
  // -- Private --
437
+ /**
438
+ * Run an async operation, and on recoverable failure (timeout, connection
439
+ * closed) clean up, reconnect to the daemon, and retry once.
440
+ */
441
+ async withRetry(fn) {
442
+ try {
443
+ return await fn();
444
+ } catch (err) {
445
+ const msg = err instanceof Error ? err.message : "";
446
+ const recoverable = msg.includes("timed out") || msg.includes("connection closed") || msg.includes("Not connected");
447
+ if (!recoverable) throw err;
448
+ debug2(`recoverable error, reconnecting: ${msg}`);
449
+ this.forceCleanup();
450
+ await this.ensureConnected();
451
+ return await fn();
452
+ }
453
+ }
454
+ /**
455
+ * Aggressive cleanup: kill the daemon process if we know its PID,
456
+ * then reset client state so the next ensureConnected spawns fresh.
457
+ */
458
+ forceCleanup() {
459
+ this.cleanup();
460
+ this.spawnedInThisProcess = false;
461
+ try {
462
+ const pid = parseInt(fs.readFileSync(getPidPath(), "utf-8").trim(), 10);
463
+ killDaemonProcess(pid);
464
+ } catch {
465
+ }
466
+ cleanupDaemonFiles();
467
+ }
395
468
  connectToSocket(socketPath) {
396
469
  return new Promise((resolve, reject) => {
397
470
  const socket = new net.Socket();
@@ -417,6 +490,11 @@ var DaemonClient = class {
417
490
  socket.on("close", () => {
418
491
  this.isConnected = false;
419
492
  this.socket = null;
493
+ for (const { reject: rej } of this.messageQueue.values()) {
494
+ rej(new Error("Daemon connection closed"));
495
+ }
496
+ this.messageQueue.clear();
497
+ this.buffer = Buffer.alloc(0);
420
498
  });
421
499
  socket.connect(socketPath);
422
500
  });
@@ -443,7 +521,7 @@ var DaemonClient = class {
443
521
  }
444
522
  }
445
523
  }
446
- sendMessage(message) {
524
+ sendMessage(message, timeoutMs = SEND_TIMEOUT_MS) {
447
525
  return new Promise((resolve, reject) => {
448
526
  if (!this.isConnected || !this.socket) {
449
527
  reject(new Error("Not connected to daemon"));
@@ -455,7 +533,20 @@ var DaemonClient = class {
455
533
  const messageBytes = Buffer.from(jsonData, "utf-8");
456
534
  const lengthBuf = Buffer.alloc(4);
457
535
  lengthBuf.writeUInt32LE(messageBytes.length, 0);
458
- this.messageQueue.set(messageId, { resolve, reject });
536
+ const timeout = setTimeout(() => {
537
+ this.messageQueue.delete(messageId);
538
+ reject(new Error(`Daemon message timed out after ${timeoutMs}ms (action: ${message.action})`));
539
+ }, timeoutMs);
540
+ this.messageQueue.set(messageId, {
541
+ resolve: /* @__PURE__ */ __name((value) => {
542
+ clearTimeout(timeout);
543
+ resolve(value);
544
+ }, "resolve"),
545
+ reject: /* @__PURE__ */ __name((err) => {
546
+ clearTimeout(timeout);
547
+ reject(err);
548
+ }, "reject")
549
+ });
459
550
  this.socket.write(Buffer.concat([lengthBuf, messageBytes]));
460
551
  });
461
552
  }
@@ -468,35 +559,26 @@ var DaemonClient = class {
468
559
  const pidPath = getPidPath();
469
560
  const isWindows = process.platform === "win32";
470
561
  if (!isWindows) {
471
- fs3.mkdirSync(path.dirname(socketPath), { recursive: true });
562
+ fs.mkdirSync(path.dirname(socketPath), { recursive: true });
472
563
  }
473
- fs3.mkdirSync(path.dirname(pidPath), { recursive: true });
474
- if (fs3.existsSync(pidPath)) {
564
+ fs.mkdirSync(path.dirname(pidPath), { recursive: true });
565
+ if (fs.existsSync(pidPath)) {
475
566
  try {
476
- const pid = parseInt(fs3.readFileSync(pidPath, "utf-8").trim(), 10);
567
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
477
568
  process.kill(pid, 0);
478
- await new Promise((r) => {
479
- setTimeout(r, 500);
480
- });
481
- return;
569
+ try {
570
+ await this.connectToSocket(socketPath);
571
+ return;
572
+ } catch {
573
+ debug2(`daemon pid ${pid} alive but socket unresponsive \u2014 killing`);
574
+ killDaemonProcess(pid);
575
+ }
482
576
  } catch {
483
577
  }
484
578
  }
485
- if (!isWindows) {
486
- for (const file of [socketPath, pidPath, getDaemonInfoPath()]) {
487
- if (fs3.existsSync(file)) {
488
- fs3.unlinkSync(file);
489
- }
490
- }
491
- if (fs3.existsSync(socketPath)) {
492
- throw new Error(`Failed to clean up stale socket file: ${socketPath}`);
493
- }
494
- } else {
495
- for (const file of [pidPath, getDaemonInfoPath()]) {
496
- if (fs3.existsSync(file)) {
497
- fs3.unlinkSync(file);
498
- }
499
- }
579
+ cleanupDaemonFiles();
580
+ if (!isWindows && fs.existsSync(socketPath)) {
581
+ throw new Error(`Failed to clean up stale socket file: ${socketPath}`);
500
582
  }
501
583
  return new Promise((resolve, reject) => {
502
584
  const child = spawn(binaryPath, [
@@ -708,7 +790,7 @@ function getKeyFilePath(keyId) {
708
790
  }
709
791
  __name(getKeyFilePath, "getKeyFilePath");
710
792
  function keyExists(keyId = DEFAULT_KEY_ID) {
711
- return fs3.existsSync(getKeyFilePath(keyId));
793
+ return fs.existsSync(getKeyFilePath(keyId));
712
794
  }
713
795
  __name(keyExists, "keyExists");
714
796
  async function generateKey(keyId = DEFAULT_KEY_ID) {
@@ -720,18 +802,18 @@ async function generateKey(keyId = DEFAULT_KEY_ID) {
720
802
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
721
803
  };
722
804
  const keyStorePath = getKeyStorePath();
723
- fs3.mkdirSync(keyStorePath, { recursive: true });
805
+ fs.mkdirSync(keyStorePath, { recursive: true });
724
806
  const filePath = getKeyFilePath(keyId);
725
- fs3.writeFileSync(filePath, JSON.stringify(stored, null, 2), { mode: 384 });
807
+ fs.writeFileSync(filePath, JSON.stringify(stored, null, 2), { mode: 384 });
726
808
  return { keyId, publicKey: keyPair.publicKey };
727
809
  }
728
810
  __name(generateKey, "generateKey");
729
811
  function loadKeyPair(keyId) {
730
812
  const filePath = getKeyFilePath(keyId);
731
- if (!fs3.existsSync(filePath)) {
813
+ if (!fs.existsSync(filePath)) {
732
814
  throw new Error(`Key not found: ${keyId}`);
733
815
  }
734
- const data = fs3.readFileSync(filePath, "utf-8");
816
+ const data = fs.readFileSync(filePath, "utf-8");
735
817
  const parsed = JSON.parse(data);
736
818
  const privateKey = parsed.privateKey ?? (parsed.protection === "none" ? parsed.protectedPrivateKey : void 0) ?? parsed.protectedPrivateKey;
737
819
  if (!parsed.publicKey || !privateKey) {
@@ -771,21 +853,49 @@ function debug3(msg) {
771
853
  }
772
854
  }
773
855
  __name(debug3, "debug");
774
- var _cachedTtyId;
775
- function getSelfTtyId() {
776
- if (_cachedTtyId) return _cachedTtyId;
856
+ var _cachedSessionId;
857
+ function getSelfSessionId() {
858
+ if (_cachedSessionId) return _cachedSessionId;
777
859
  try {
778
- const ttyPath = fs3.readlinkSync("/proc/self/fd/0");
860
+ const ttyPath = fs.readlinkSync("/proc/self/fd/0");
779
861
  if (ttyPath && ttyPath.startsWith("/dev/")) {
780
- _cachedTtyId = ttyPath;
862
+ _cachedSessionId = ttyPath;
781
863
  return ttyPath;
782
864
  }
783
865
  } catch {
784
866
  }
785
- _cachedTtyId = `pid:${process.pid}`;
786
- return _cachedTtyId;
867
+ try {
868
+ const chain = [process.pid];
869
+ let current = process.pid;
870
+ for (let i = 0; i < 64; i++) {
871
+ const stat = fs.readFileSync(`/proc/${current}/stat`, "utf-8");
872
+ const fields = stat.split(") ");
873
+ if (fields.length < 2) break;
874
+ const ppid = parseInt(fields[1].split(" ")[1], 10);
875
+ if (!ppid || ppid <= 1) break;
876
+ chain.push(ppid);
877
+ current = ppid;
878
+ }
879
+ if (chain.length >= 4) {
880
+ const scopePid = chain[chain.length - 3];
881
+ let startTime = 0;
882
+ try {
883
+ const scopeStat = fs.readFileSync(`/proc/${scopePid}/stat`, "utf-8");
884
+ const scopeFields = scopeStat.split(") ");
885
+ if (scopeFields.length >= 2) {
886
+ startTime = parseInt(scopeFields[1].split(" ")[19], 10) || 0;
887
+ }
888
+ } catch {
889
+ }
890
+ _cachedSessionId = `ptree:${scopePid}:${startTime}`;
891
+ return _cachedSessionId;
892
+ }
893
+ } catch {
894
+ }
895
+ _cachedSessionId = `pid:${process.pid}`;
896
+ return _cachedSessionId;
787
897
  }
788
- __name(getSelfTtyId, "getSelfTtyId");
898
+ __name(getSelfSessionId, "getSelfSessionId");
789
899
  var _wslDaemonPrestartAttempted = false;
790
900
  function toWindowsPathFromWsl(pathInWsl) {
791
901
  if (!isWSL()) return void 0;
@@ -1029,7 +1139,7 @@ async function decryptValue2(ciphertext, keyId = DEFAULT_KEY_ID2) {
1029
1139
  }
1030
1140
  const stdinPayload = JSON.stringify({
1031
1141
  data: ciphertext,
1032
- ttyId: getSelfTtyId()
1142
+ ttyId: getSelfSessionId()
1033
1143
  });
1034
1144
  const runViaDaemon = /* @__PURE__ */ __name((timeout) => spawnSync(binaryPath, ["decrypt", "--key-id", keyId, "--data-stdin", "--via-daemon"], {
1035
1145
  input: stdinPayload,
@@ -1088,5 +1198,5 @@ async function lockSession() {
1088
1198
  __name(lockSession, "lockSession");
1089
1199
 
1090
1200
  export { decryptValue2 as decryptValue, encryptValue2 as encryptValue, ensureKey, getBackendInfo, getDaemonClient, lockSession };
1091
- //# sourceMappingURL=chunk-6DXWXIQV.js.map
1092
- //# sourceMappingURL=chunk-6DXWXIQV.js.map
1201
+ //# sourceMappingURL=chunk-GURKQO4J.js.map
1202
+ //# sourceMappingURL=chunk-GURKQO4J.js.map