@evident-ai/cli 3.1.1-dev.9ff0701 → 3.1.1-dev.a99e616

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
@@ -85,6 +85,7 @@ Options:
85
85
 
86
86
  - `-a, --agent [id]` — Runner ID to connect to. Optional when `EVIDENT_AGENT_KEY`
87
87
  is set (the runner is then resolved automatically from the key).
88
+ - `--runner [id]` — Alias for `--agent` (preferred name; wins if both are given).
88
89
  - `-p, --port <port>` — OpenCode port (default: `4096`).
89
90
  - `--log-level <level>` — Log verbosity: `debug | info | warn | error` (default:
90
91
  `info`). Env: `EVIDENT_LOG_LEVEL`.
@@ -105,15 +106,18 @@ targets the **production** Evident platform by default.
105
106
 
106
107
  ## Environment variables
107
108
 
108
- - `EVIDENT_AGENT_KEY` — A runner key. When set, `evident run` authenticates as
109
- that runner and resolves the runner ID automatically, so `--agent` is not
110
- required. Ideal for CI/CD.
109
+ - `EVIDENT_RUNNER_KEY` — A runner key. When set, `evident run` authenticates as
110
+ that runner and resolves the runner ID automatically, so `--runner`/`--agent`
111
+ is not required. Ideal for CI/CD. Preferred name; wins over `EVIDENT_AGENT_KEY`
112
+ if both are set.
113
+ - `EVIDENT_AGENT_KEY` — Alias for `EVIDENT_RUNNER_KEY` (still fully supported).
111
114
  - `EVIDENT_TOKEN` — A user token used for authentication (alternative to a
112
115
  keychain login from `evident login`).
113
116
  - `EVIDENT_API_URL` — Override the API base URL (equivalent to `--endpoint`).
114
117
  - `EVIDENT_TUNNEL_URL` — Override the tunnel relay URL (equivalent to `--tunnel`).
115
118
 
116
- Authentication precedence for `run`: `EVIDENT_AGENT_KEY` → `EVIDENT_TOKEN` →
119
+ Authentication precedence for `run`: `EVIDENT_RUNNER_KEY`/`EVIDENT_AGENT_KEY`
120
+ (tied; `EVIDENT_RUNNER_KEY` wins if both are set) → `EVIDENT_TOKEN` →
117
121
  credentials stored by `evident login`. For the URL flags, an explicit
118
122
  `--endpoint` / `--tunnel` flag wins over the matching environment variable, which
119
123
  in turn overrides the production default.
@@ -150,6 +154,8 @@ For CI or unattended use, set `EVIDENT_AGENT_KEY` and omit `--agent`:
150
154
  EVIDENT_AGENT_KEY=<agent-key> evident run --idle-timeout 30
151
155
  ```
152
156
 
157
+ (`EVIDENT_RUNNER_KEY` is equivalent and the preferred name — use whichever you like.)
158
+
153
159
  ## How it works
154
160
 
155
161
  ```
package/dist/index.js CHANGED
@@ -650,7 +650,15 @@ var EventTypes = {
650
650
 
651
651
  // src/lib/auth.ts
652
652
  async function getAuthCredentials() {
653
+ const runnerKey = process.env.EVIDENT_RUNNER_KEY;
653
654
  const agentKey = process.env.EVIDENT_AGENT_KEY;
655
+ if (runnerKey) {
656
+ return {
657
+ token: runnerKey,
658
+ authType: "agent_key",
659
+ notice: agentKey ? "Both EVIDENT_RUNNER_KEY and EVIDENT_AGENT_KEY are set; using EVIDENT_RUNNER_KEY." : void 0
660
+ };
661
+ }
654
662
  if (agentKey) {
655
663
  return { token: agentKey, authType: "agent_key" };
656
664
  }
@@ -1370,6 +1378,72 @@ function findLastAssistantReplyFor(messages, userMessageId) {
1370
1378
  }
1371
1379
  return lastOk ?? last;
1372
1380
  }
1381
+ function messageUsage(messages, userMessageId) {
1382
+ if (!messages || messages.length === 0) return null;
1383
+ const byParentAll = messages.filter(
1384
+ (m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
1385
+ );
1386
+ const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
1387
+ const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
1388
+ let correlated;
1389
+ if (byParent.length > 0) {
1390
+ correlated = byParent;
1391
+ } else {
1392
+ const reply = findAssistantReplyAfter(messages, userMessageId);
1393
+ correlated = reply ? [reply] : [];
1394
+ }
1395
+ if (correlated.length === 0) return null;
1396
+ let sawAnyUsage = false;
1397
+ let inputSum = 0;
1398
+ let outputSum = 0;
1399
+ let reasoningSum = 0;
1400
+ let cacheReadSum = 0;
1401
+ let cacheWriteSum = 0;
1402
+ let costSum = 0;
1403
+ let sawCost = false;
1404
+ let modelId = null;
1405
+ let providerId = null;
1406
+ for (const m of correlated) {
1407
+ const info = m.info;
1408
+ if (!info) continue;
1409
+ const tokens = info.tokens;
1410
+ if (tokens) {
1411
+ sawAnyUsage = true;
1412
+ inputSum += tokens.input ?? 0;
1413
+ outputSum += tokens.output ?? 0;
1414
+ reasoningSum += tokens.reasoning ?? 0;
1415
+ cacheReadSum += tokens.cache?.read ?? 0;
1416
+ cacheWriteSum += tokens.cache?.write ?? 0;
1417
+ }
1418
+ if (typeof info.cost === "number") {
1419
+ sawAnyUsage = true;
1420
+ sawCost = true;
1421
+ costSum += info.cost;
1422
+ }
1423
+ if (typeof info.modelID === "string") {
1424
+ sawAnyUsage = true;
1425
+ modelId = info.modelID;
1426
+ }
1427
+ if (typeof info.providerID === "string") {
1428
+ sawAnyUsage = true;
1429
+ providerId = info.providerID;
1430
+ }
1431
+ }
1432
+ if (!sawAnyUsage) return null;
1433
+ return {
1434
+ usage_provider_id: providerId,
1435
+ usage_model_id: modelId,
1436
+ usage_tokens_input: inputSum,
1437
+ usage_tokens_output: outputSum,
1438
+ usage_tokens_reasoning: reasoningSum,
1439
+ usage_tokens_cache_read: cacheReadSum,
1440
+ usage_tokens_cache_write: cacheWriteSum,
1441
+ // NULL means "OpenCode never reported a cost" (never inferred from
1442
+ // tokens) — distinct from a genuine 0-cost turn, which would set
1443
+ // `sawCost` true with `costSum === 0`.
1444
+ usage_cost_usd: sawCost ? costSum : null
1445
+ };
1446
+ }
1373
1447
  function messageRunState(messages, userMessageId) {
1374
1448
  if (!messages || messages.length === 0) return "unknown";
1375
1449
  const hasUser = messages.some((m) => idOf(m) === userMessageId);
@@ -2445,7 +2519,7 @@ var ChannelDriver = class {
2445
2519
  }
2446
2520
  /**
2447
2521
  * Fetch ONE inbound image's bytes through Evident's WI-6 endpoint
2448
- * (`GET {apiUrl}/agents/{agentId}/attachments/{messageId}/{index}`) using the
2522
+ * (`GET {apiUrl}/runners/{agentId}/attachments/{messageId}/{index}`) using the
2449
2523
  * existing authenticated fetch, and base64-encode into a
2450
2524
  * `data:<mime>;base64,<…>` URL for the opencode `file` part's `url`.
2451
2525
  *
@@ -2458,7 +2532,7 @@ var ChannelDriver = class {
2458
2532
  async fetchAttachmentDataUrl(messageId, index, mime) {
2459
2533
  try {
2460
2534
  const res = await this.fetchImpl(
2461
- `${this.apiUrl}/agents/${this.agentId}/attachments/${messageId}/${index}`,
2535
+ `${this.apiUrl}/runners/${this.agentId}/attachments/${messageId}/${index}`,
2462
2536
  { headers: { Authorization: this.getAuthHeader() } }
2463
2537
  );
2464
2538
  if (!res.ok) {
@@ -2790,13 +2864,15 @@ var ChannelDriver = class {
2790
2864
  message_id: inFlight.evidentMessageId
2791
2865
  });
2792
2866
  const title = await this.resolveSessionTitle(sessionId, watcher.conv.id);
2867
+ const usage = messageUsage(messages, inFlight.opencodeMessageId);
2793
2868
  try {
2794
2869
  await this.markDone(
2795
2870
  conv.id,
2796
2871
  inFlight.evidentMessageId,
2797
2872
  sessionId,
2798
2873
  inFlight.opencodeMessageId,
2799
- title
2874
+ title,
2875
+ usage
2800
2876
  );
2801
2877
  } catch (err) {
2802
2878
  if (err instanceof ChannelAuthError) throw err;
@@ -2843,8 +2919,9 @@ var ChannelDriver = class {
2843
2919
  conversation_id: conv.id,
2844
2920
  message_id: inFlight.evidentMessageId
2845
2921
  });
2922
+ const usage = messageUsage(messages, inFlight.opencodeMessageId);
2846
2923
  try {
2847
- await this.markFailed(conv.id, inFlight.evidentMessageId, sessionId, error2);
2924
+ await this.markFailed(conv.id, inFlight.evidentMessageId, sessionId, error2, usage);
2848
2925
  } catch (err) {
2849
2926
  if (err instanceof ChannelAuthError) throw err;
2850
2927
  if (err instanceof ChannelTerminalError) {
@@ -3081,7 +3158,8 @@ var ChannelDriver = class {
3081
3158
  });
3082
3159
  try {
3083
3160
  const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
3084
- await this.markDone(row.conversation_id, row.id, sessionId, ocId, title);
3161
+ const usage = messageUsage(messages, ocId ?? "");
3162
+ await this.markDone(row.conversation_id, row.id, sessionId, ocId, title, usage);
3085
3163
  } catch (err) {
3086
3164
  if (err instanceof ChannelAuthError) throw err;
3087
3165
  if (err instanceof ChannelTerminalError) {
@@ -3109,6 +3187,7 @@ var ChannelDriver = class {
3109
3187
  }
3110
3188
  if (state === "failed") {
3111
3189
  const error2 = messageError(messages, ocId ?? "") ?? void 0;
3190
+ const usage = messageUsage(messages, ocId ?? "");
3112
3191
  this.log({
3113
3192
  level: "error",
3114
3193
  message: `Re-adopt: message ${row.id.slice(0, 8)} errored while unwatched \u2014 marking failed: ${error2 ?? "(no error text)"}`,
@@ -3116,7 +3195,7 @@ var ChannelDriver = class {
3116
3195
  message_id: row.id
3117
3196
  });
3118
3197
  try {
3119
- await this.markFailed(row.conversation_id, row.id, sessionId, error2);
3198
+ await this.markFailed(row.conversation_id, row.id, sessionId, error2, usage);
3120
3199
  } catch (err) {
3121
3200
  if (err instanceof ChannelAuthError) throw err;
3122
3201
  if (err instanceof ChannelTerminalError) {
@@ -3719,7 +3798,7 @@ var ChannelDriver = class {
3719
3798
  // Evident API calls (combinedAuth thread routes)
3720
3799
  async getPendingConversations() {
3721
3800
  const res = await this.fetchImpl(
3722
- `${this.apiUrl}/agents/${this.agentId}/conversations/pending`,
3801
+ `${this.apiUrl}/runners/${this.agentId}/conversations/pending`,
3723
3802
  {
3724
3803
  headers: { Authorization: this.getAuthHeader() }
3725
3804
  }
@@ -3737,7 +3816,7 @@ var ChannelDriver = class {
3737
3816
  }
3738
3817
  async getPendingMessages(conversationId) {
3739
3818
  const res = await this.fetchImpl(
3740
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages?status=pending`,
3819
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages?status=pending`,
3741
3820
  { headers: { Authorization: this.getAuthHeader() } }
3742
3821
  );
3743
3822
  this.assertAuth(res, "fetching pending messages");
@@ -3761,7 +3840,7 @@ var ChannelDriver = class {
3761
3840
  */
3762
3841
  async getProcessingMessages() {
3763
3842
  const res = await this.fetchImpl(
3764
- `${this.apiUrl}/agents/${this.agentId}/conversations/processing`,
3843
+ `${this.apiUrl}/runners/${this.agentId}/conversations/processing`,
3765
3844
  { headers: { Authorization: this.getAuthHeader() } }
3766
3845
  );
3767
3846
  this.assertAuth(res, "fetching processing messages");
@@ -3798,7 +3877,7 @@ var ChannelDriver = class {
3798
3877
  */
3799
3878
  async markProcessing(conversationId, messageId, sessionId, opencodeMessageId, title) {
3800
3879
  const res = await this.fetchImpl(
3801
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3880
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3802
3881
  {
3803
3882
  method: "PATCH",
3804
3883
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3845,9 +3924,9 @@ var ChannelDriver = class {
3845
3924
  * watcher retries next tick within the
3846
3925
  * deadline, Finding 4).
3847
3926
  */
3848
- async markDone(conversationId, messageId, sessionId, opencodeMessageId, title) {
3927
+ async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage) {
3849
3928
  const res = await this.fetchImpl(
3850
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3929
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3851
3930
  {
3852
3931
  method: "PATCH",
3853
3932
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3855,7 +3934,8 @@ var ChannelDriver = class {
3855
3934
  status: "done",
3856
3935
  opencode_session_id: sessionId,
3857
3936
  ...opencodeMessageId ? { opencode_message_id: opencodeMessageId } : {},
3858
- ...title ? { title } : {}
3937
+ ...title ? { title } : {},
3938
+ ...usage ? usage : {}
3859
3939
  })
3860
3940
  }
3861
3941
  );
@@ -3873,14 +3953,15 @@ var ChannelDriver = class {
3873
3953
  * OpenCode turn sends `{status:'failed', opencode_session_id, error}` so the
3874
3954
  * failure reason reaches the channel.
3875
3955
  */
3876
- async markFailed(conversationId, messageId, sessionId, error2) {
3956
+ async markFailed(conversationId, messageId, sessionId, error2, usage) {
3877
3957
  const body = { status: "failed" };
3878
3958
  if (sessionId !== void 0) body.opencode_session_id = sessionId;
3879
3959
  if (error2 !== void 0) body.error = error2;
3960
+ if (usage) Object.assign(body, usage);
3880
3961
  await this.callWithRetry(
3881
3962
  "marking message as failed",
3882
3963
  () => this.fetchImpl(
3883
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3964
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3884
3965
  {
3885
3966
  method: "PATCH",
3886
3967
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3907,7 +3988,7 @@ var ChannelDriver = class {
3907
3988
  async postSignal(conversationId, messageId, signal, extra) {
3908
3989
  try {
3909
3990
  const res = await this.fetchImpl(
3910
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}/signal`,
3991
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}/signal`,
3911
3992
  {
3912
3993
  method: "POST",
3913
3994
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3936,7 +4017,7 @@ var ChannelDriver = class {
3936
4017
  }
3937
4018
  async persistSession(conversationId, sessionId) {
3938
4019
  const res = await this.fetchImpl(
3939
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}`,
4020
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}`,
3940
4021
  {
3941
4022
  method: "PATCH",
3942
4023
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3962,7 +4043,7 @@ var ChannelDriver = class {
3962
4043
  await this.callWithRetry(
3963
4044
  "reporting interactive event",
3964
4045
  () => this.fetchImpl(
3965
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/interactive-event`,
4046
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/interactive-event`,
3966
4047
  {
3967
4048
  method: "POST",
3968
4049
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -4205,7 +4286,7 @@ async function resolveAgentIdFromKey(authHeader) {
4205
4286
  async function notifyAgentDisconnected(agentId, authHeader) {
4206
4287
  const apiUrl = getApiUrlConfig();
4207
4288
  try {
4208
- const response = await fetch(`${apiUrl}/agents/${agentId}/disconnect`, {
4289
+ const response = await fetch(`${apiUrl}/runners/${agentId}/disconnect`, {
4209
4290
  method: "POST",
4210
4291
  headers: { Authorization: authHeader }
4211
4292
  });
@@ -4224,7 +4305,7 @@ async function notifyAgentDisconnected(agentId, authHeader) {
4224
4305
  async function getAgentInfo(agentId, authHeader) {
4225
4306
  const apiUrl = getApiUrlConfig();
4226
4307
  try {
4227
- const response = await fetch(`${apiUrl}/agents/${agentId}`, {
4308
+ const response = await fetch(`${apiUrl}/runners/${agentId}`, {
4228
4309
  headers: { Authorization: authHeader }
4229
4310
  });
4230
4311
  if (response.status === 401) {
@@ -4611,7 +4692,7 @@ async function run(options) {
4611
4692
  return;
4612
4693
  }
4613
4694
  const state = {
4614
- agentId: options.agent || "",
4695
+ agentId: options.runner || options.agent || "",
4615
4696
  agentName: null,
4616
4697
  port: options.port ?? 4096,
4617
4698
  conversationFilter: options.conversation ?? null,
@@ -4661,7 +4742,9 @@ async function run(options) {
4661
4742
  if (!interactive) {
4662
4743
  printError("Authentication required");
4663
4744
  blank();
4664
- console.log(chalk6.dim("Set EVIDENT_AGENT_KEY environment variable for CI"));
4745
+ console.log(
4746
+ chalk6.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
4747
+ );
4665
4748
  console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
4666
4749
  blank();
4667
4750
  process.exit(1);
@@ -4675,6 +4758,12 @@ async function run(options) {
4675
4758
  );
4676
4759
  }
4677
4760
  state.authHeader = getAuthHeader(credentials2);
4761
+ if (credentials2.notice) {
4762
+ log2(state, credentials2.notice, "warn");
4763
+ if (state.interactive && !state.json) {
4764
+ logActivity(state, { type: "info", level: "warn", message: credentials2.notice });
4765
+ }
4766
+ }
4678
4767
  if (!state.agentId) {
4679
4768
  if (credentials2.authType === "agent_key") {
4680
4769
  const resolved = await resolveAgentIdFromKey(state.authHeader);
@@ -4692,9 +4781,15 @@ async function run(options) {
4692
4781
  process.exit(1);
4693
4782
  }
4694
4783
  } else {
4695
- printError("--agent is required when not using EVIDENT_AGENT_KEY");
4784
+ printError(
4785
+ "--runner (or --agent) is required when not using EVIDENT_RUNNER_KEY or EVIDENT_AGENT_KEY"
4786
+ );
4696
4787
  blank();
4697
- console.log(chalk6.dim("Either provide --agent <id> or set EVIDENT_AGENT_KEY"));
4788
+ console.log(
4789
+ chalk6.dim(
4790
+ "Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
4791
+ )
4792
+ );
4698
4793
  blank();
4699
4794
  process.exit(1);
4700
4795
  }
@@ -4909,7 +5004,7 @@ async function run(options) {
4909
5004
  }
4910
5005
  telemetry.error(EventTypes.CLI_ERROR, `Run command failed: ${message}`, {
4911
5006
  command: "run",
4912
- agentId: options.agent
5007
+ agentId: options.runner || options.agent
4913
5008
  });
4914
5009
  await shutdownTelemetry();
4915
5010
  process.exit(1);
@@ -4934,7 +5029,7 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
4934
5029
  program.command("login").description("Authenticate with Evident").option("--token", "Use token-based authentication (for CI/CD)").option("--no-browser", "Do not open the browser automatically").action(login);
4935
5030
  program.command("logout").description("Remove stored credentials for the current endpoint").option("--all", "Remove stored credentials for all endpoints").action((options) => logout({ all: options.all }));
4936
5031
  program.command("whoami").description("Show the currently logged in user").action(whoami);
4937
- program.command("run").description("Connect to Evident and process messages").option("-a, --agent [id]", "Runner ID to connect to (optional when EVIDENT_AGENT_KEY is set)").option("-p, --port <port>", "OpenCode port (default: 4096)", "4096").option(
5032
+ program.command("run").description("Connect to Evident and process messages").option("-a, --agent [id]", "Runner ID to connect to (optional when EVIDENT_AGENT_KEY is set)").option("--runner [id]", "Alias for --agent (preferred name; wins if both are given)").option("-p, --port <port>", "OpenCode port (default: 4096)", "4096").option(
4938
5033
  "--log-level <level>",
4939
5034
  "Log verbosity: debug | info | warn | error (default: info). Env: EVIDENT_LOG_LEVEL"
4940
5035
  ).option("-v, --verbose", "Alias for --log-level debug (ignored if --log-level is set)").option("-c, --conversation <id>", "Process only this specific conversation").option("--idle-timeout <seconds>", "Exit after N seconds idle").option("--json", "Output in JSON format").option(
@@ -4950,6 +5045,7 @@ program.command("run").description("Connect to Evident and process messages").op
4950
5045
  (options) => {
4951
5046
  run({
4952
5047
  agent: options.agent,
5048
+ runner: options.runner,
4953
5049
  port: parseInt(options.port, 10),
4954
5050
  // Raw string — validation/precedence is single-sourced in run.ts's
4955
5051
  // resolveLogLevel (flag > -v > EVIDENT_LOG_LEVEL > info).