@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 +4 -1
- package/dist/index.js +80 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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("~/") ?
|
|
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 =
|
|
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 =
|
|
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("~/") ?
|
|
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",
|