@evident-ai/cli 3.2.1-dev.a241839 → 3.2.1-dev.cf740cc

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/README.md CHANGED
@@ -111,7 +111,10 @@ Options:
111
111
  `7d`, `24h`). Setting this (or `--session-cleanup-max-count`) is what enables
112
112
  cleanup — there is no separate on/off flag. An invalid value is warned about
113
113
  and ignored, which can leave cleanup off if it was the only rule set. Env:
114
- `EVIDENT_SESSION_CLEANUP_MAX_AGE`.
114
+ `EVIDENT_SESSION_CLEANUP_MAX_AGE`. With cleanup off, a runner whose local
115
+ session store has grown large warns once at startup, on the runner's page,
116
+ naming this flag — pruning stops the store growing, but it does not shrink
117
+ what has already grown.
115
118
  - `--session-cleanup-max-count <n>` — Keep only the newest N OpenCode sessions
116
119
  by last activity, deleting the rest. Also enables cleanup; combines with
117
120
  `--session-cleanup-max-age` as OR. A session with a turn actively in progress
package/dist/index.js CHANGED
@@ -212,6 +212,7 @@ var api = {
212
212
 
213
213
  // src/lib/keychain.ts
214
214
  var SERVICE_NAME = "evident-cli";
215
+ var keytarWarned = false;
215
216
  async function getKeytar() {
216
217
  try {
217
218
  const keytar = await import("keytar");
@@ -219,7 +220,13 @@ async function getKeytar() {
219
220
  return null;
220
221
  }
221
222
  return keytar;
222
- } catch {
223
+ } 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
+ }
223
230
  return null;
224
231
  }
225
232
  }
@@ -368,8 +375,9 @@ async function deviceFlowLogin(options) {
368
375
  await waitForEnter("Press Enter to open the browser...");
369
376
  try {
370
377
  await open(verification_uri);
371
- } catch {
372
- console.log(chalk2.dim("Could not open browser. Please visit the URL manually."));
378
+ } catch (error2) {
379
+ const message = error2 instanceof Error ? error2.message : String(error2);
380
+ console.log(chalk2.dim(`Could not open browser (${message}). Please visit the URL manually.`));
373
381
  }
374
382
  }
375
383
  const spinner = ora("Waiting for authentication...").start();
@@ -909,14 +917,25 @@ function readClaudeCliCredentials() {
909
917
  { encoding: "utf-8", timeout: 2e3, stdio: ["pipe", "pipe", "ignore"] }
910
918
  );
911
919
  return parseClaudeCliCredentials(raw);
912
- } catch {
920
+ } catch (err) {
921
+ if (err.status !== 44) {
922
+ console.warn(
923
+ `readClaudeCliCredentials: security find-generic-password failed: ${err instanceof Error ? err.message : String(err)}`
924
+ );
925
+ }
913
926
  return null;
914
927
  }
915
928
  }
916
929
  try {
917
930
  const raw = readFileSync(join(homedir(), ".claude", ".credentials.json"), "utf-8");
918
931
  return parseClaudeCliCredentials(raw);
919
- } catch {
932
+ } catch (err) {
933
+ const code = err.code;
934
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
935
+ console.warn(
936
+ `readClaudeCliCredentials: reading .claude/.credentials.json failed: ${err instanceof Error ? err.message : String(err)}`
937
+ );
938
+ }
920
939
  return null;
921
940
  }
922
941
  }
@@ -1004,7 +1023,7 @@ async function claudeUsage() {
1004
1023
 
1005
1024
  // src/commands/run.ts
1006
1025
  import { homedir as homedir3 } from "os";
1007
- import { isAbsolute as isAbsolute2, join as join3, parse, resolve as resolvePath } from "path";
1026
+ import { isAbsolute as isAbsolute2, join as join4, parse, resolve as resolvePath } from "path";
1008
1027
  import chalk6 from "chalk";
1009
1028
 
1010
1029
  // ../../packages/types/src/agents/index.ts
@@ -1414,7 +1433,10 @@ function findOpenCodeProcesses() {
1414
1433
  }
1415
1434
  }
1416
1435
  }
1417
- } catch {
1436
+ } catch (err) {
1437
+ console.warn(
1438
+ `findOpenCodeProcesses: ps fallback failed: ${err instanceof Error ? err.message : String(err)}`
1439
+ );
1418
1440
  }
1419
1441
  }
1420
1442
  for (const pid of pids) {
@@ -1437,7 +1459,10 @@ function findOpenCodeProcesses() {
1437
1459
  }
1438
1460
  }
1439
1461
  }
1440
- } catch {
1462
+ } catch (err) {
1463
+ console.warn(
1464
+ `findOpenCodeProcesses: process detection failed: ${err instanceof Error ? err.message : String(err)}`
1465
+ );
1441
1466
  }
1442
1467
  return instances;
1443
1468
  }
@@ -1511,7 +1536,12 @@ function stopOpenCode(opencodeProcess) {
1511
1536
  } else {
1512
1537
  process.kill(-opencodeProcess.pid, "SIGTERM");
1513
1538
  }
1514
- } catch {
1539
+ } catch (err) {
1540
+ if (err.code !== "ESRCH") {
1541
+ console.warn(
1542
+ `stopOpenCode: kill failed: ${err instanceof Error ? err.message : String(err)}`
1543
+ );
1544
+ }
1515
1545
  }
1516
1546
  }
1517
1547
 
@@ -2292,6 +2322,31 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
2292
2322
  return { enabled, maxAgeMs, maxCount, intervalMs, warnings };
2293
2323
  }
2294
2324
 
2325
+ // src/lib/opencode/session-db-size.ts
2326
+ import { statSync as statSync2 } from "fs";
2327
+ import { join as join2 } from "path";
2328
+ var LARGE_DB_THRESHOLD_BYTES = 268435456;
2329
+ function statSessionDbBytes(homeDir) {
2330
+ const dbPath = join2(homeDir, ".local", "share", "opencode", "opencode.db");
2331
+ try {
2332
+ return statSync2(dbPath).size;
2333
+ } catch (err) {
2334
+ const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
2335
+ if (!isMissingFile) {
2336
+ console.error(
2337
+ `[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
2338
+ );
2339
+ }
2340
+ return null;
2341
+ }
2342
+ }
2343
+ function buildSessionStoreSizeWarning(input) {
2344
+ const { dbBytes, cleanupEnabled } = input;
2345
+ if (dbBytes === null || dbBytes <= LARGE_DB_THRESHOLD_BYTES || cleanupEnabled) return null;
2346
+ const mib = Math.round(dbBytes / 1024 / 1024);
2347
+ return `Session store is large: opencode.db is ${mib} MiB and automatic session cleanup is off. Enable it with --session-cleanup-max-age 24h (env EVIDENT_SESSION_CLEANUP_MAX_AGE) to stop it growing \u2014 a store much larger than this can exceed a hosted runner's session-history restore budget on the next start, losing this runner's session history.`;
2348
+ }
2349
+
2295
2350
  // src/lib/tunnel/connection.ts
2296
2351
  import WebSocket2 from "ws";
2297
2352
 
@@ -2727,7 +2782,7 @@ import { homedir as homedir2 } from "os";
2727
2782
  // src/lib/file-push.ts
2728
2783
  import { randomUUID } from "crypto";
2729
2784
  import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
2730
- import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2, sep } from "path";
2785
+ import { basename, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
2731
2786
  var FILE_MODE = 384;
2732
2787
  var DIRECTORY_MODE = 448;
2733
2788
  async function writePushedFile(request) {
@@ -2760,7 +2815,7 @@ async function writePushedFile(request) {
2760
2815
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
2761
2816
  dirname2(candidate)
2762
2817
  );
2763
- const realTarget = join2(existingAncestor, ...missingSegments, basename(candidate));
2818
+ const realTarget = join3(existingAncestor, ...missingSegments, basename(candidate));
2764
2819
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
2765
2820
  if (allowedDirectory === null) {
2766
2821
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -2796,7 +2851,7 @@ function expandAndValidate(requestedPath, homeDir) {
2796
2851
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
2797
2852
  return null;
2798
2853
  }
2799
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join2(homeDir, requestedPath.slice(2)) : requestedPath;
2854
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join3(homeDir, requestedPath.slice(2)) : requestedPath;
2800
2855
  if (expanded.split(/[/\\]/).includes("..")) {
2801
2856
  return null;
2802
2857
  }
@@ -2869,13 +2924,13 @@ function contains(realDirectory, realTarget) {
2869
2924
  async function createMissingDirectories(existingAncestor, missingSegments) {
2870
2925
  let current = existingAncestor;
2871
2926
  for (const segment of missingSegments) {
2872
- current = join2(current, segment);
2927
+ current = join3(current, segment);
2873
2928
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
2874
2929
  await chmod(current, DIRECTORY_MODE);
2875
2930
  }
2876
2931
  }
2877
2932
  async function writeAtomically(realTarget, content) {
2878
- const temporaryPath = join2(dirname2(realTarget), `.evident-push-${randomUUID()}.tmp`);
2933
+ const temporaryPath = join3(dirname2(realTarget), `.evident-push-${randomUUID()}.tmp`);
2879
2934
  let handle;
2880
2935
  try {
2881
2936
  handle = await open2(temporaryPath, "wx", FILE_MODE);
@@ -6632,7 +6687,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
6632
6687
  if (trimmed === "") {
6633
6688
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
6634
6689
  }
6635
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join3(homeDir, trimmed.slice(2)) : trimmed;
6690
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join4(homeDir, trimmed.slice(2)) : trimmed;
6636
6691
  if (!isAbsolute2(expanded)) {
6637
6692
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
6638
6693
  }
@@ -6826,7 +6881,9 @@ async function handleAuthError(state, error2) {
6826
6881
  );
6827
6882
  const newAuthHeader = getAuthHeader(credentials2);
6828
6883
  return { success: true, newAuthHeader };
6829
- } catch {
6884
+ } catch (error3) {
6885
+ const message = error3 instanceof Error ? error3.message : String(error3);
6886
+ logActivity(state, { type: "error", error: `Re-authentication failed: ${message}` });
6830
6887
  return { success: false };
6831
6888
  }
6832
6889
  }
@@ -6993,6 +7050,13 @@ function scheduleSessionCleanup(state, driver, options) {
6993
7050
  for (const warning2 of config.warnings) {
6994
7051
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
6995
7052
  }
7053
+ const sizeWarning = buildSessionStoreSizeWarning({
7054
+ dbBytes: statSessionDbBytes(homedir3()),
7055
+ cleanupEnabled: config.enabled
7056
+ });
7057
+ if (sizeWarning !== null) {
7058
+ logActivity(state, { type: "info", level: "warn", message: sizeWarning });
7059
+ }
6996
7060
  if (!config.enabled) return;
6997
7061
  logActivity(state, {
6998
7062
  type: "info",