@evident-ai/cli 3.3.1-dev.8abe4d1 → 3.3.1-dev.8e3d9ea

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
@@ -105,6 +105,11 @@ Options:
105
105
  keeps retrying) if no usable login is found; `off` disables it entirely — no
106
106
  Claude credential is ever read. An unrecognized value falls back to `auto` with
107
107
  a warning. Env: `EVIDENT_CLAUDE_USAGE_REPORTING`.
108
+ - `--no-resource-usage-reporting` — Don't report this machine's CPU
109
+ utilization, total and available memory, core count, disk space, and the
110
+ OpenCode session-store size to Evident, so it shows on the runner page. On
111
+ by default; nothing else about the machine leaves it. Env:
112
+ `EVIDENT_RESOURCE_USAGE_REPORTING=off` (the flag wins if both are set).
108
113
  - `--json` — Output in JSON format (forces non-interactive mode).
109
114
  - `--session-cleanup-max-age <duration>` — Delete OpenCode sessions idle longer
110
115
  than this window (format `<number><unit>`, unit one of `s, m, h, d` — e.g.
@@ -171,6 +176,9 @@ targets the **production** Evident platform by default.
171
176
  - `EVIDENT_TUNNEL_URL` — Override the tunnel relay URL (equivalent to `--tunnel`).
172
177
  - `EVIDENT_CLAUDE_USAGE_REPORTING` — Equivalent to `--claude-usage-reporting`; the
173
178
  flag wins if both are set.
179
+ - `EVIDENT_RESOURCE_USAGE_REPORTING` — Equivalent to
180
+ `--no-resource-usage-reporting`; set it to `off` to disable. The flag wins
181
+ if both are set.
174
182
  - `EVIDENT_LOG_LEVEL` — Equivalent to `--log-level`; the flag wins if both are
175
183
  set, and `-v`/`--verbose` also outranks this env var.
176
184
  - `EVIDENT_OPENCODE_START_TIMEOUT` — Equivalent to `--opencode-start-timeout`
package/dist/index.js CHANGED
@@ -746,6 +746,35 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
746
746
  return { ok: false, error: describeBestEffortError(error2) };
747
747
  }
748
748
  }
749
+ async function reportResourceUsage(agentId, authHeader, usage) {
750
+ try {
751
+ const apiUrl = getApiUrlConfig();
752
+ const response = await fetch(`${apiUrl}/runners/${agentId}/resource-usage`, {
753
+ method: "POST",
754
+ headers: { Authorization: authHeader, "Content-Type": "application/json" },
755
+ body: JSON.stringify({
756
+ cpu_percent: usage.cpuPercent,
757
+ cpu_count: usage.cpuCount,
758
+ memory_total_bytes: usage.memoryTotalBytes,
759
+ memory_available_bytes: usage.memoryAvailableBytes,
760
+ disk_total_bytes: usage.diskTotalBytes,
761
+ disk_free_bytes: usage.diskFreeBytes,
762
+ opencode_db_bytes: usage.opencodeDbBytes
763
+ }),
764
+ signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
765
+ });
766
+ if (!response.ok) {
767
+ const serverMessage = await readErrorMessage(response);
768
+ return {
769
+ ok: false,
770
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
771
+ };
772
+ }
773
+ return { ok: true };
774
+ } catch (error2) {
775
+ return { ok: false, error: describeBestEffortError(error2) };
776
+ }
777
+ }
749
778
  async function getAgentInfo(agentId, authHeader) {
750
779
  const apiUrl = getApiUrlConfig();
751
780
  try {
@@ -2962,6 +2991,21 @@ function writeTunnelReadyMarker(path, agentId) {
2962
2991
  }
2963
2992
  }
2964
2993
 
2994
+ // src/lib/reporting-schedule.ts
2995
+ function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
2996
+ const jitterRangeMs = baseMs * jitterFraction;
2997
+ return baseMs - jitterRangeMs + random() * (2 * jitterRangeMs);
2998
+ }
2999
+ function firstReportDelayMs(random = Math.random) {
3000
+ return 5e3 + random() * 1e4;
3001
+ }
3002
+ function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
3003
+ return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
3004
+ }
3005
+ function failureStreakSuffix(consecutiveFailures) {
3006
+ return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
3007
+ }
3008
+
2965
3009
  // src/lib/claude-usage-reporting.ts
2966
3010
  var VALID_MODES = ["auto", "on", "off"];
2967
3011
  function resolveClaudeUsageReportingMode(flagValue, env) {
@@ -2984,13 +3028,167 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
2984
3028
  var BASE_REPORT_DELAY_MS = 10 * 6e4;
2985
3029
  var REPORT_DELAY_JITTER_FRACTION = 0.2;
2986
3030
  function nextReportDelayMs(random = Math.random) {
2987
- const jitterRangeMs = BASE_REPORT_DELAY_MS * REPORT_DELAY_JITTER_FRACTION;
2988
- return BASE_REPORT_DELAY_MS - jitterRangeMs + random() * (2 * jitterRangeMs);
3031
+ return jitteredDelayMs(BASE_REPORT_DELAY_MS, REPORT_DELAY_JITTER_FRACTION, random);
2989
3032
  }
2990
- var FIRST_REPORT_DELAY_MS = 5e3 + Math.random() * 1e4;
3033
+ var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
2991
3034
  var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
2992
3035
  function claudeUsageFailureLogLevel(consecutiveFailures) {
2993
- return consecutiveFailures === 1 || consecutiveFailures % CLAUDE_USAGE_FAILURE_REESCALATION_TICKS === 0 ? "warn" : "debug";
3036
+ return reportFailureLogLevel(consecutiveFailures, CLAUDE_USAGE_FAILURE_REESCALATION_TICKS);
3037
+ }
3038
+
3039
+ // src/lib/resource-usage-reporting.ts
3040
+ var ENABLED_VALUES = /* @__PURE__ */ new Set(["on", "true", "1"]);
3041
+ var DISABLED_VALUES = /* @__PURE__ */ new Set(["off", "false", "0"]);
3042
+ function resolveResourceUsageReportingEnabled(flagValue, env) {
3043
+ if (flagValue === false) {
3044
+ return { enabled: false, warnings: [] };
3045
+ }
3046
+ const raw = env.EVIDENT_RESOURCE_USAGE_REPORTING;
3047
+ if (raw === void 0 || raw === "") {
3048
+ return { enabled: true, warnings: [] };
3049
+ }
3050
+ const normalized = raw.trim().toLowerCase();
3051
+ if (DISABLED_VALUES.has(normalized)) {
3052
+ return { enabled: false, warnings: [] };
3053
+ }
3054
+ if (ENABLED_VALUES.has(normalized)) {
3055
+ return { enabled: true, warnings: [] };
3056
+ }
3057
+ return {
3058
+ enabled: true,
3059
+ warnings: [
3060
+ `Ignoring invalid EVIDENT_RESOURCE_USAGE_REPORTING "${raw}": expected on or off; leaving reporting on`
3061
+ ]
3062
+ };
3063
+ }
3064
+
3065
+ // src/lib/resource-usage.ts
3066
+ import { cpus, totalmem, freemem } from "os";
3067
+ import { statfsSync as statfsSync2 } from "fs";
3068
+
3069
+ // src/lib/ecs-task-metadata.ts
3070
+ var ECS_METADATA_TIMEOUT_MS = 2e3;
3071
+ function parseEcsTaskLimits(payload) {
3072
+ if (typeof payload !== "object" || payload === null) return null;
3073
+ const limits = payload.Limits;
3074
+ if (typeof limits !== "object" || limits === null) return null;
3075
+ const cpu = limits.CPU;
3076
+ const memory = limits.Memory;
3077
+ if (typeof cpu !== "number" || !Number.isFinite(cpu) || cpu <= 0) return null;
3078
+ if (typeof memory !== "number" || !Number.isFinite(memory) || memory <= 0) return null;
3079
+ return {
3080
+ cpuCount: Math.max(1, Math.round(cpu)),
3081
+ memoryTotalBytes: memory * 1024 * 1024
3082
+ };
3083
+ }
3084
+ async function readEcsTaskLimits(env) {
3085
+ const uri = env.ECS_CONTAINER_METADATA_URI_V4;
3086
+ if (!uri) {
3087
+ return { limits: null };
3088
+ }
3089
+ const url = `${uri}/task`;
3090
+ try {
3091
+ const response = await fetch(url, { signal: AbortSignal.timeout(ECS_METADATA_TIMEOUT_MS) });
3092
+ if (!response.ok) {
3093
+ return {
3094
+ limits: null,
3095
+ warning: `ECS task metadata fetch (${url}) returned HTTP ${response.status}`
3096
+ };
3097
+ }
3098
+ const payload = await response.json();
3099
+ const limits = parseEcsTaskLimits(payload);
3100
+ if (limits === null) {
3101
+ return {
3102
+ limits: null,
3103
+ warning: `ECS task metadata fetch (${url}) returned an unexpected payload`
3104
+ };
3105
+ }
3106
+ return { limits };
3107
+ } catch (error2) {
3108
+ const message = error2 instanceof Error ? error2.message : String(error2);
3109
+ return { limits: null, warning: `ECS task metadata fetch (${url}) failed: ${message}` };
3110
+ }
3111
+ }
3112
+
3113
+ // src/lib/resource-usage.ts
3114
+ function readCpuSample() {
3115
+ let busyMs = 0;
3116
+ let idleMs = 0;
3117
+ for (const cpu of cpus()) {
3118
+ busyMs += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq;
3119
+ idleMs += cpu.times.idle;
3120
+ }
3121
+ return { busyMs, idleMs };
3122
+ }
3123
+ function cpuPercentBetween(previous, current) {
3124
+ const deltaBusy = current.busyMs - previous.busyMs;
3125
+ const deltaIdle = current.idleMs - previous.idleMs;
3126
+ const total = deltaBusy + deltaIdle;
3127
+ if (total === 0) return null;
3128
+ return Math.round((deltaBusy / total * 100 + Number.EPSILON) * 100) / 100;
3129
+ }
3130
+ function clamp(value, min, max) {
3131
+ return Math.min(Math.max(value, min), max);
3132
+ }
3133
+ function round2(value) {
3134
+ return Math.round((value + Number.EPSILON) * 100) / 100;
3135
+ }
3136
+ function readDisk(homeDir) {
3137
+ try {
3138
+ const stats = statfsSync2(homeDir);
3139
+ return {
3140
+ totalBytes: stats.bsize * stats.blocks,
3141
+ freeBytes: stats.bsize * stats.bavail
3142
+ };
3143
+ } catch (error2) {
3144
+ const message = error2 instanceof Error ? error2.message : String(error2);
3145
+ return {
3146
+ totalBytes: null,
3147
+ freeBytes: null,
3148
+ warning: `Could not read disk usage for ${homeDir}: ${message}`
3149
+ };
3150
+ }
3151
+ }
3152
+ function createResourceUsageCollector(homeDir) {
3153
+ let previous = readCpuSample();
3154
+ return async () => {
3155
+ const current = readCpuSample();
3156
+ const hostCpuPercent = cpuPercentBetween(previous, current);
3157
+ const hostCpuCount = cpus().length;
3158
+ previous = current;
3159
+ const disk = readDisk(homeDir);
3160
+ const opencodeDbBytes = statSessionDbBytes(homeDir);
3161
+ const { limits, warning: ecsWarning } = await readEcsTaskLimits(process.env);
3162
+ const warnings = [];
3163
+ if (disk.warning) warnings.push(disk.warning);
3164
+ if (ecsWarning) warnings.push(ecsWarning);
3165
+ let cpuPercent = hostCpuPercent;
3166
+ let cpuCount = hostCpuCount;
3167
+ let memoryTotalBytes = totalmem();
3168
+ let memoryAvailableBytes = freemem();
3169
+ if (limits !== null) {
3170
+ cpuCount = limits.cpuCount;
3171
+ memoryTotalBytes = limits.memoryTotalBytes;
3172
+ memoryAvailableBytes = clamp(
3173
+ limits.memoryTotalBytes - (totalmem() - freemem()),
3174
+ 0,
3175
+ limits.memoryTotalBytes
3176
+ );
3177
+ cpuPercent = hostCpuPercent === null ? null : clamp(round2(hostCpuPercent * hostCpuCount / limits.cpuCount), 0, 100);
3178
+ }
3179
+ return {
3180
+ usage: {
3181
+ cpuPercent,
3182
+ cpuCount,
3183
+ memoryTotalBytes,
3184
+ memoryAvailableBytes,
3185
+ diskTotalBytes: disk.totalBytes,
3186
+ diskFreeBytes: disk.freeBytes,
3187
+ opencodeDbBytes
3188
+ },
3189
+ warnings
3190
+ };
3191
+ };
2994
3192
  }
2995
3193
 
2996
3194
  // src/lib/channels/driver.ts
@@ -7746,9 +7944,6 @@ function scheduleSessionCleanup(state, driver, options) {
7746
7944
  );
7747
7945
  state.sessionCleanupTimers.push(interval, firstSweep);
7748
7946
  }
7749
- function claudeUsageFailureStreakSuffix(consecutiveFailures) {
7750
- return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
7751
- }
7752
7947
  function scheduleClaudeUsageReporting(state, options) {
7753
7948
  const { mode, warnings } = resolveClaudeUsageReportingMode(
7754
7949
  options.claudeUsageReporting,
@@ -7830,7 +8025,7 @@ function scheduleClaudeUsageReporting(state, options) {
7830
8025
  logActivity(state, {
7831
8026
  type: "info",
7832
8027
  level: claudeUsageFailureLogLevel(consecutiveFailures),
7833
- message: `Failed to report Claude usage: ${result.error}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
8028
+ message: `Failed to report Claude usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
7834
8029
  });
7835
8030
  }
7836
8031
  scheduleNextTick();
@@ -7865,7 +8060,7 @@ function scheduleClaudeUsageReporting(state, options) {
7865
8060
  logActivity(state, {
7866
8061
  type: "info",
7867
8062
  level: claudeUsageFailureLogLevel(consecutiveFailures),
7868
- message: `Claude usage reporting failed: ${message}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
8063
+ message: `Claude usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
7869
8064
  });
7870
8065
  scheduleNextTick();
7871
8066
  }
@@ -7874,6 +8069,90 @@ function scheduleClaudeUsageReporting(state, options) {
7874
8069
  armProbe();
7875
8070
  return rearm;
7876
8071
  }
8072
+ var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
8073
+ var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
8074
+ var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
8075
+ function scheduleResourceUsageReporting(state, options) {
8076
+ const { enabled, warnings } = resolveResourceUsageReportingEnabled(
8077
+ options.resourceUsageReporting,
8078
+ process.env
8079
+ );
8080
+ for (const warning2 of warnings) {
8081
+ logActivity(state, {
8082
+ type: "info",
8083
+ level: "warn",
8084
+ message: `Resource usage reporting: ${warning2}`
8085
+ });
8086
+ }
8087
+ if (!enabled) {
8088
+ logActivity(state, {
8089
+ type: "info",
8090
+ level: "debug",
8091
+ message: "Resource usage reporting is off (--no-resource-usage-reporting)"
8092
+ });
8093
+ return;
8094
+ }
8095
+ const collect = createResourceUsageCollector(homedir3());
8096
+ let consecutiveFailures = 0;
8097
+ const tick = async () => {
8098
+ try {
8099
+ const { usage, warnings: collectWarnings } = await collect();
8100
+ for (const warning2 of collectWarnings) {
8101
+ logActivity(state, {
8102
+ type: "info",
8103
+ level: "debug",
8104
+ message: `Resource usage collection: ${warning2}`
8105
+ });
8106
+ }
8107
+ const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
8108
+ if (result.ok) {
8109
+ if (consecutiveFailures > 0) {
8110
+ logActivity(state, {
8111
+ type: "info",
8112
+ level: "info",
8113
+ message: "Resource usage reporting recovered"
8114
+ });
8115
+ }
8116
+ consecutiveFailures = 0;
8117
+ logActivity(state, {
8118
+ type: "info",
8119
+ level: "debug",
8120
+ message: "Reported resource usage to Evident"
8121
+ });
8122
+ } else {
8123
+ consecutiveFailures++;
8124
+ logActivity(state, {
8125
+ type: "info",
8126
+ level: reportFailureLogLevel(
8127
+ consecutiveFailures,
8128
+ RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
8129
+ ),
8130
+ message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
8131
+ });
8132
+ }
8133
+ } catch (error2) {
8134
+ consecutiveFailures++;
8135
+ const message = error2 instanceof Error ? error2.message : String(error2);
8136
+ logActivity(state, {
8137
+ type: "info",
8138
+ level: reportFailureLogLevel(
8139
+ consecutiveFailures,
8140
+ RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
8141
+ ),
8142
+ message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
8143
+ });
8144
+ } finally {
8145
+ state.resourceUsageTimer = setTimeout(
8146
+ () => void tick(),
8147
+ jitteredDelayMs(
8148
+ RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
8149
+ RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
8150
+ )
8151
+ );
8152
+ }
8153
+ };
8154
+ state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
8155
+ }
7877
8156
  async function notifyOffline(state) {
7878
8157
  if (!state.agentId || !state.authHeader) return;
7879
8158
  if (!state.connected) {
@@ -7914,6 +8193,10 @@ async function cleanup(state, opts = {}) {
7914
8193
  state.claudeUsageTimer = null;
7915
8194
  }
7916
8195
  state.claudeUsageRearm = null;
8196
+ if (state.resourceUsageTimer) {
8197
+ clearTimeout(state.resourceUsageTimer);
8198
+ state.resourceUsageTimer = null;
8199
+ }
7917
8200
  if (opts.graceful && state.channelDriver) {
7918
8201
  state.channelDriver.stop();
7919
8202
  log2(state, "Draining in-flight channel work before shutdown...");
@@ -7996,6 +8279,7 @@ async function run(options) {
7996
8279
  sessionCleanupTimers: [],
7997
8280
  claudeUsageTimer: null,
7998
8281
  claudeUsageRearm: null,
8282
+ resourceUsageTimer: null,
7999
8283
  authHeader: ""
8000
8284
  };
8001
8285
  setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
@@ -8395,6 +8679,7 @@ async function run(options) {
8395
8679
  }
8396
8680
  scheduleSessionCleanup(state, channelDriver, options);
8397
8681
  state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
8682
+ scheduleResourceUsageReporting(state, options);
8398
8683
  if (!interactive || state.json) {
8399
8684
  log2(state, "Driving channel messages...");
8400
8685
  }
@@ -8475,6 +8760,9 @@ program.command("run").description("Connect to Evident and process messages").op
8475
8760
  ).option(
8476
8761
  "--claude-usage-reporting <mode>",
8477
8762
  "Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
8763
+ ).option(
8764
+ "--no-resource-usage-reporting",
8765
+ "Don't report this machine's CPU and memory usage to Evident (reporting is on by default). Env: EVIDENT_RESOURCE_USAGE_REPORTING=off"
8478
8766
  ).option(
8479
8767
  "--enable-file-sync-to <dir>",
8480
8768
  "Allow Evident to write files into this directory (repeatable). Omit to disable file sync entirely.",
@@ -8507,6 +8795,9 @@ program.command("run").description("Connect to Evident and process messages").op
8507
8795
  // Raw string — the resolver in run.ts single-sources parsing
8508
8796
  // (resolveClaudeUsageReportingMode).
8509
8797
  claudeUsageReporting: options.claudeUsageReporting,
8798
+ // Raw value — resolution is single-sourced in run.ts's
8799
+ // resolveResourceUsageReportingEnabled.
8800
+ resourceUsageReporting: options.resourceUsageReporting,
8510
8801
  // Raw values — expansion/validation is single-sourced in run.ts's
8511
8802
  // resolveFileSyncDirectories.
8512
8803
  enableFileSyncTo: options.enableFileSyncTo,