@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 +115 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
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
|
|
216
|
-
|
|
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
|
-
|
|
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
|
|
256
|
+
const keytar = await resolveKeychain();
|
|
238
257
|
if (keytar) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
268
|
+
const keytar = await resolveKeychain();
|
|
250
269
|
if (keytar) {
|
|
251
270
|
const account = keychainAccount();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2727
|
-
|
|
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
|
-
|
|
2774
|
-
|
|
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
|
-
|
|
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
|