@evident-ai/cli 3.2.1-dev.fa69368 → 3.3.1-dev.4a86610

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.js CHANGED
@@ -212,51 +212,80 @@ var api = {
212
212
 
213
213
  // src/lib/keychain.ts
214
214
  var SERVICE_NAME = "evident-cli";
215
- var keytarWarned = false;
216
- async function getKeytar() {
215
+ var PROBE_SERVICE_NAME = "evident-cli-probe";
216
+ var keychainWarned = false;
217
+ function warnUnavailable(err) {
218
+ if (!keychainWarned) {
219
+ keychainWarned = true;
220
+ console.warn(
221
+ `System keychain unavailable, falling back to file-based credential storage: ${err instanceof Error ? err.message : String(err)}`
222
+ );
223
+ }
224
+ }
225
+ var keychain;
226
+ async function probeKeychain() {
217
227
  try {
218
- const keytar = await import("keytar");
228
+ const keytar = await import("@napi-rs/keyring/keytar.js");
219
229
  if (typeof keytar.setPassword !== "function") {
220
230
  return null;
221
231
  }
232
+ await keytar.findCredentials(PROBE_SERVICE_NAME);
222
233
  return keytar;
223
234
  } catch (err) {
224
- if (!keytarWarned) {
225
- keytarWarned = true;
226
- console.warn(
227
- `System keychain unavailable, falling back to file-based credential storage: ${err instanceof Error ? err.message : String(err)}`
228
- );
229
- }
235
+ warnUnavailable(err);
230
236
  return null;
231
237
  }
232
238
  }
239
+ function resolveKeychain() {
240
+ if (!keychain) {
241
+ keychain = probeKeychain();
242
+ }
243
+ return keychain;
244
+ }
233
245
  function keychainAccount() {
234
246
  return getApiUrlConfig();
235
247
  }
248
+ function storeInFileFallback(credentials2) {
249
+ setCredentials({
250
+ token: credentials2.token,
251
+ user: credentials2.user,
252
+ expiresAt: credentials2.expiresAt
253
+ });
254
+ }
236
255
  async function storeToken(credentials2) {
237
- const keytar = await getKeytar();
256
+ const keytar = await resolveKeychain();
238
257
  if (keytar) {
239
- await keytar.setPassword(SERVICE_NAME, keychainAccount(), JSON.stringify(credentials2));
240
- } else {
241
- setCredentials({
242
- token: credentials2.token,
243
- user: credentials2.user,
244
- expiresAt: credentials2.expiresAt
245
- });
258
+ try {
259
+ await keytar.setPassword(SERVICE_NAME, keychainAccount(), JSON.stringify(credentials2));
260
+ return;
261
+ } catch (err) {
262
+ warnUnavailable(err);
263
+ }
246
264
  }
265
+ storeInFileFallback(credentials2);
247
266
  }
248
267
  async function getToken() {
249
- const keytar = await getKeytar();
268
+ const keytar = await resolveKeychain();
250
269
  if (keytar) {
251
270
  const account = keychainAccount();
252
- const stored = await keytar.getPassword(SERVICE_NAME, account);
253
- if (stored) {
254
- try {
255
- return JSON.parse(stored);
256
- } catch {
257
- await keytar.deletePassword(SERVICE_NAME, account);
258
- return null;
271
+ try {
272
+ const stored = await keytar.getPassword(SERVICE_NAME, account);
273
+ if (stored) {
274
+ try {
275
+ return JSON.parse(stored);
276
+ } catch {
277
+ try {
278
+ await keytar.deletePassword(SERVICE_NAME, account);
279
+ } catch (err) {
280
+ console.warn(
281
+ `Failed to clear invalid keychain entry for ${account}: ${err instanceof Error ? err.message : String(err)}`
282
+ );
283
+ }
284
+ return null;
285
+ }
259
286
  }
287
+ } catch (err) {
288
+ warnUnavailable(err);
260
289
  }
261
290
  }
262
291
  const creds = getCredentials();
@@ -273,7 +302,7 @@ function toError(err) {
273
302
  return err instanceof Error ? err : new Error(String(err));
274
303
  }
275
304
  async function deleteToken(options = {}) {
276
- const keytar = await getKeytar();
305
+ const keytar = await resolveKeychain();
277
306
  const failures = [];
278
307
  if (keytar) {
279
308
  if (options.all) {
@@ -286,14 +315,25 @@ async function deleteToken(options = {}) {
286
315
  await Promise.all(
287
316
  accounts.map(async (entry) => {
288
317
  try {
289
- await keytar.deletePassword(SERVICE_NAME, entry.account);
318
+ const deleted = await keytar.deletePassword(SERVICE_NAME, entry.account);
319
+ if (!deleted) {
320
+ failures.push({
321
+ type: "delete",
322
+ account: entry.account,
323
+ error: new Error("deletePassword resolved false")
324
+ });
325
+ }
290
326
  } catch (err) {
291
327
  failures.push({ type: "delete", account: entry.account, error: toError(err) });
292
328
  }
293
329
  })
294
330
  );
295
331
  } else {
296
- await keytar.deletePassword(SERVICE_NAME, keychainAccount());
332
+ try {
333
+ await keytar.deletePassword(SERVICE_NAME, keychainAccount());
334
+ } catch (err) {
335
+ warnUnavailable(err);
336
+ }
297
337
  }
298
338
  }
299
339
  if (options.all) {
@@ -1052,6 +1092,7 @@ var MAX_FILE_SYNC_DIRECTORIES = 16;
1052
1092
 
1053
1093
  // ../../packages/types/src/logging/index.ts
1054
1094
  var CORRELATION_ID_HEADER = "x-evident-correlation-id";
1095
+ var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
1055
1096
  function log(level, event, fields) {
1056
1097
  const method = level === "debug" ? "log" : level;
1057
1098
  try {
@@ -2645,6 +2686,17 @@ var StreamForwarder = class {
2645
2686
  };
2646
2687
 
2647
2688
  // src/lib/tunnel/connection.ts
2689
+ var FAILURE_REASON_HEADER_LC = FORWARD_FAILURE_REASON_HEADER.toLowerCase();
2690
+ var TunnelUpgradeRejectedError = class extends Error {
2691
+ constructor(message, reason) {
2692
+ super(message);
2693
+ this.reason = reason;
2694
+ }
2695
+ };
2696
+ function classifyUpgradeRejection(headers) {
2697
+ const value = headers[FAILURE_REASON_HEADER_LC];
2698
+ return value === "do_code_updated" ? "do_code_updated" : "unknown";
2699
+ }
2648
2700
  var MAX_RECONNECT_DELAY = 3e4;
2649
2701
  var BASE_RECONNECT_DELAY = 500;
2650
2702
  function getReconnectDelay(attempt) {
@@ -2689,6 +2741,7 @@ function connectTunnel(options) {
2689
2741
  onError,
2690
2742
  onResponse,
2691
2743
  onInfo,
2744
+ onWarning,
2692
2745
  onDrainPing
2693
2746
  } = options;
2694
2747
  const tunnelUrl = getTunnelUrlConfig();
@@ -2708,8 +2761,11 @@ function connectTunnel(options) {
2708
2761
  reject(new Error("Connection timeout"));
2709
2762
  }, 3e4);
2710
2763
  let upgradeRejection = null;
2764
+ let upgradeRejectionReason = null;
2711
2765
  ws.on("unexpected-response", (_req, res) => {
2712
2766
  clearTimeout(connectionTimeout);
2767
+ const reason = classifyUpgradeRejection(res.headers);
2768
+ upgradeRejectionReason = reason;
2713
2769
  const chunks = [];
2714
2770
  res.on("data", (chunk) => chunks.push(chunk));
2715
2771
  res.on("end", () => {
@@ -2723,8 +2779,14 @@ function connectTunnel(options) {
2723
2779
  }
2724
2780
  const statusLine = `HTTP ${res.statusCode}${res.statusMessage ? ` ${res.statusMessage}` : ""}`;
2725
2781
  upgradeRejection = detail ? `${statusLine}: ${detail}` : statusLine;
2726
- onError?.(`Tunnel refused by relay (${upgradeRejection})`);
2727
- reject(new Error(`Tunnel handshake rejected: ${upgradeRejection}`));
2782
+ if (reason === "do_code_updated") {
2783
+ onWarning?.("Relay redeployed \u2014 reconnecting");
2784
+ } else {
2785
+ onError?.(`Tunnel refused by relay (${upgradeRejection})`);
2786
+ }
2787
+ reject(
2788
+ new TunnelUpgradeRejectedError(`Tunnel handshake rejected: ${upgradeRejection}`, reason)
2789
+ );
2728
2790
  });
2729
2791
  });
2730
2792
  ws.on("open", () => {
@@ -2770,8 +2832,14 @@ function connectTunnel(options) {
2770
2832
  ws.on("error", (error2) => {
2771
2833
  clearTimeout(connectionTimeout);
2772
2834
  const detail = upgradeRejection ?? describeSocketError(error2, url);
2773
- onError?.(`Connection error: ${detail}`);
2774
- reject(upgradeRejection ? new Error(upgradeRejection) : new Error(detail));
2835
+ if (upgradeRejectionReason === "do_code_updated") {
2836
+ onWarning?.("Relay redeployed \u2014 reconnecting");
2837
+ } else {
2838
+ onError?.(`Connection error: ${detail}`);
2839
+ }
2840
+ reject(
2841
+ upgradeRejectionReason !== null ? new TunnelUpgradeRejectedError(detail, upgradeRejectionReason) : new Error(detail)
2842
+ );
2775
2843
  });
2776
2844
  ws.on("close", (code, reason) => {
2777
2845
  const reasonStr = reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
@@ -2847,7 +2915,8 @@ var RunnerConnection = class {
2847
2915
  onError: (error2) => events.onError?.(error2),
2848
2916
  onResponse: () => events.onResponse?.(),
2849
2917
  onDrainPing: () => events.onDrainPing?.(),
2850
- onInfo: (message) => events.onInfo?.(message)
2918
+ onInfo: (message) => events.onInfo?.(message),
2919
+ onWarning: (message) => events.onWarning?.(message)
2851
2920
  });
2852
2921
  return;
2853
2922
  } catch (error2) {
@@ -2858,7 +2927,12 @@ var RunnerConnection = class {
2858
2927
  }
2859
2928
  const delay = getReconnectDelay(this.reconnectAttempt);
2860
2929
  events.onReconnecting?.(this.reconnectAttempt);
2861
- events.onError?.(`Connection failed, retrying in ${Math.round(delay / 1e3)}s...`);
2930
+ const retryMessage = `Connection failed, retrying in ${Math.round(delay / 1e3)}s...`;
2931
+ if (error2 instanceof TunnelUpgradeRejectedError && error2.reason === "do_code_updated") {
2932
+ events.onWarning?.(retryMessage);
2933
+ } else {
2934
+ events.onError?.(retryMessage);
2935
+ }
2862
2936
  await this.sleep(delay);
2863
2937
  }
2864
2938
  }
@@ -7889,6 +7963,13 @@ async function run(options) {
7889
7963
  logActivity(state, { type: "error", error: error2 });
7890
7964
  if (state.interactive) displayStatus(state);
7891
7965
  },
7966
+ // `warn`, not `info`: `forwardRunnerActivity`'s FORWARDED_LEVELS floor is
7967
+ // {'warn','error'}, so an `info` entry would never leave the machine and
7968
+ // an operator couldn't correlate a reconnect storm with a relay deploy.
7969
+ onWarning: (message) => {
7970
+ logActivity(state, { type: "info", level: "warn", message });
7971
+ if (state.interactive) displayStatus(state);
7972
+ },
7892
7973
  // Web traffic is proxied transparently; note opencode is live and stamp
7893
7974
  // proxied activity so the idle loop treats interactive proxy use as work.
7894
7975
  // Fires per forwarded response head (incl. every SSE open) and excludes